Skip to content

Commit 95e13aa

Browse files
authored
Merge branch 'master' into scheme_asan_options
2 parents 516f551 + 38d76e7 commit 95e13aa

38 files changed

Lines changed: 366 additions & 51 deletions

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
## Next Version
44

5-
- Added Address Sanitizer & similar options in run/test schemes #1550 @hi-kumar
5+
### Added
6+
- Added sanitizer options to run and test actions in Scheme #1550 @hi-kumar
7+
8+
### Fixed
9+
- Added validation to ensure that all values in `settings.configs` are mappings. Previously, passing non-mapping values did not raise an error, making it difficult to detect misconfigurations. Now, `SpecParsingError.invalidConfigsMappingFormat` is thrown if misused. #1547 @Ryu0118
610

711
## 2.43.0
812

Docs/ProjectSpec.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ Note that target names can also be changed by adding a `name` property to a targ
153153
- [ ] **postGenCommand**: **String** - A bash command to run after the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like `pod install` only if the project is actually regenerated.
154154
- [ ] **useBaseInternationalization**: **Bool** If this is `false` and your project does not include resources located in a **Base.lproj** directory then `Base` will not be included in the projects 'known regions'. The default value is `true`.
155155
- [ ] **schemePathPrefix**: **String** - A path prefix for relative paths in schemes, such as StoreKitConfiguration. The default is `"../../"`, which is suitable for non-workspace projects. For use in workspaces, use `"../"`.
156+
- [ ] **defaultSourceDirectoryType**: **String** - When a [Target source](#target-source) doesn't specify a type and is a directory, this is the type that will be used. If nothing is specified for either then `group` will be used.
157+
- `group` (default)
158+
- `folder`
159+
- `syncedFolder`
156160

157161
```yaml
158162
options:
@@ -542,6 +546,7 @@ A source can be provided via a string (the path) or an object of the form:
542546
- `file`: a file reference with a parent group will be created (Default for files or directories with extensions)
543547
- `group`: a group with all it's containing files. (Default for directories without extensions)
544548
- `folder`: a folder reference.
549+
- `syncedFolder`: Xcode 16's synchronized folders, also knows as buildable folders
545550
- [ ] **headerVisibility**: **String** - The visibility of any headers. This defaults to `public`, but can be either:
546551
- `public`
547552
- `private`

Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ let package = Package(
1616
.package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"),
1717
.package(url: "https://github.com/kylef/Spectre.git", from: "0.9.2"),
1818
.package(url: "https://github.com/onevcat/Rainbow.git", from: "4.0.0"),
19-
.package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.24.3"),
19+
.package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.27.7"),
2020
.package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.3"),
2121
.package(url: "https://github.com/mxcl/Version", from: "2.0.0"),
2222
.package(url: "https://github.com/freddi-kit/ArtifactBundleGen", exact: "0.0.6")

Sources/ProjectSpec/AggregateTarget.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ extension AggregateTarget: NamedJSONDictionaryConvertible {
6060
public init(name: String, jsonDictionary: JSONDictionary) throws {
6161
self.name = jsonDictionary.json(atKeyPath: "name") ?? name
6262
targets = jsonDictionary.json(atKeyPath: "targets") ?? []
63-
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
63+
settings = try BuildSettingsParser(jsonDictionary: jsonDictionary).parse()
6464
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
6565
buildScripts = jsonDictionary.json(atKeyPath: "buildScripts") ?? []
6666
buildToolPlugins = jsonDictionary.json(atKeyPath: "buildToolPlugins") ?? []
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Foundation
2+
import JSONUtilities
3+
4+
/// A helper for extracting and validating the `Settings` object from a JSON dictionary.
5+
struct BuildSettingsParser {
6+
let jsonDictionary: JSONDictionary
7+
8+
/// Attempts to extract and parse the `Settings` from the dictionary.
9+
///
10+
/// - Returns: A valid `Settings` object
11+
func parse() throws -> Settings {
12+
do {
13+
return try jsonDictionary.json(atKeyPath: "settings")
14+
} catch let specParsingError as SpecParsingError {
15+
// Re-throw `SpecParsingError` to prevent the misuse of settings.configs.
16+
throw specParsingError
17+
} catch {
18+
// Ignore all errors except `SpecParsingError`
19+
return .empty
20+
}
21+
}
22+
23+
/// Attempts to extract and parse setting groups from the dictionary with fallback defaults.
24+
///
25+
/// - Returns: Parsed setting groups or default groups if parsing fails
26+
func parseSettingGroups() throws -> [String: Settings] {
27+
do {
28+
return try jsonDictionary.json(atKeyPath: "settingGroups", invalidItemBehaviour: .fail)
29+
} catch let specParsingError as SpecParsingError {
30+
// Re-throw `SpecParsingError` to prevent the misuse of settingGroups.
31+
throw specParsingError
32+
} catch {
33+
// Ignore all errors except `SpecParsingError`
34+
return jsonDictionary.json(atKeyPath: "settingPresets") ?? [:]
35+
}
36+
}
37+
}

Sources/ProjectSpec/CacheFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class CacheFile {
1010

1111
guard #available(OSX 10.13, *) else { return nil }
1212

13-
let files = Set(project.allFiles)
13+
let files = Set(project.allTrackedFiles)
1414
.map { ((try? $0.relativePath(from: project.basePath)) ?? $0).string }
1515
.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
1616
.joined(separator: "\n")

Sources/ProjectSpec/Project.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,13 @@ extension Project {
171171
self.basePath = basePath
172172

173173
let jsonDictionary = Project.resolveProject(jsonDictionary: jsonDictionary)
174+
let buildSettingsParser = BuildSettingsParser(jsonDictionary: jsonDictionary)
174175

175176
name = try jsonDictionary.json(atKeyPath: "name")
176-
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
177-
settingGroups = jsonDictionary.json(atKeyPath: "settingGroups")
178-
?? jsonDictionary.json(atKeyPath: "settingPresets") ?? [:]
177+
178+
settings = try buildSettingsParser.parse()
179+
settingGroups = try buildSettingsParser.parseSettingGroups()
180+
179181
let configs: [String: String] = jsonDictionary.json(atKeyPath: "configs") ?? [:]
180182
self.configs = configs.isEmpty ? Config.defaultConfigs :
181183
configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name }
@@ -250,7 +252,7 @@ extension Project: PathContainer {
250252

251253
extension Project {
252254

253-
public var allFiles: [Path] {
255+
public var allTrackedFiles: [Path] {
254256
var files: [Path] = []
255257
files.append(contentsOf: configFilePaths)
256258
for fileGroup in fileGroups {
@@ -268,15 +270,31 @@ extension Project {
268270
files.append(contentsOf: target.configFilePaths)
269271
for source in target.sources {
270272
let sourcePath = basePath + source.path
271-
let sourceChildren = (try? sourcePath.recursiveChildren()) ?? []
272-
files.append(contentsOf: sourceChildren)
273+
274+
let type = source.type ?? options.defaultSourceDirectoryType ?? .group
275+
if type.projectTracksChildren {
276+
let sourceChildren = (try? sourcePath.recursiveChildren()) ?? []
277+
files.append(contentsOf: sourceChildren)
278+
}
273279
files.append(sourcePath)
274280
}
275281
}
276282
return files
277283
}
278284
}
279285

286+
extension SourceType {
287+
288+
var projectTracksChildren: Bool {
289+
switch self {
290+
case .file: false
291+
case .folder: false
292+
case .group: true
293+
case .syncedFolder: false
294+
}
295+
}
296+
}
297+
280298
extension BuildSettingsContainer {
281299

282300
fileprivate var configFilePaths: [Path] {

Sources/ProjectSpec/Settings.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,35 @@ public struct Settings: Equatable, JSONObjectConvertible, CustomStringConvertibl
2828
groups = jsonDictionary.json(atKeyPath: "groups") ?? jsonDictionary.json(atKeyPath: "presets") ?? []
2929
let buildSettingsDictionary: JSONDictionary = jsonDictionary.json(atKeyPath: "base") ?? [:]
3030
buildSettings = buildSettingsDictionary
31-
configSettings = jsonDictionary.json(atKeyPath: "configs") ?? [:]
31+
32+
self.configSettings = try Self.extractValidConfigs(from: jsonDictionary)
3233
} else {
3334
buildSettings = jsonDictionary
3435
configSettings = [:]
3536
groups = []
3637
}
3738
}
3839

40+
/// Extracts and validates the `configs` mapping from the given JSON dictionary.
41+
/// - Parameter jsonDictionary: The JSON dictionary to extract `configs` from.
42+
/// - Returns: A dictionary mapping configuration names to `Settings` objects.
43+
private static func extractValidConfigs(from jsonDictionary: JSONDictionary) throws -> [String: Settings] {
44+
guard let configSettings = jsonDictionary["configs"] as? JSONDictionary else {
45+
return [:]
46+
}
47+
48+
let invalidConfigKeys = Set(
49+
configSettings.filter { !($0.value is JSONDictionary) }
50+
.map(\.key)
51+
)
52+
53+
guard invalidConfigKeys.isEmpty else {
54+
throw SpecParsingError.invalidConfigsMappingFormat(keys: invalidConfigKeys)
55+
}
56+
57+
return try jsonDictionary.json(atKeyPath: "configs")
58+
}
59+
3960
public static func == (lhs: Settings, rhs: Settings) -> Bool {
4061
NSDictionary(dictionary: lhs.buildSettings).isEqual(to: rhs.buildSettings) &&
4162
lhs.configSettings == rhs.configSettings &&

Sources/ProjectSpec/SourceType.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ public enum SourceType: String {
1111
case group
1212
case file
1313
case folder
14+
case syncedFolder
1415
}

0 commit comments

Comments
 (0)