Skip to content

Commit e9ebafd

Browse files
authored
Merge branch 'master' into synced_folder
2 parents 5519fb0 + 53cb43c commit e9ebafd

16 files changed

Lines changed: 191 additions & 14 deletions

.github/workflows/ci.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ on:
44
pull_request: {}
55
jobs:
66
run:
7-
runs-on: ${{ matrix.macos }}
7+
runs-on: macos-15
88
name: Xcode ${{ matrix.xcode }}
99
strategy:
1010
matrix:
11-
xcode: ["16.0"]
12-
include:
13-
- xcode: "16.0"
14-
macos: macos-15
11+
xcode: ["16.0", "16.3"]
1512
env:
1613
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
1714
steps:

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Next Version
44

5+
### Fixed
6+
- 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
7+
8+
## 2.43.0
9+
510
### Added
611

712
- Added `excludeFromProject` from local packages #1512 @maximkrouk

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
TOOL_NAME = XcodeGen
22
export EXECUTABLE_NAME = xcodegen
3-
VERSION = 2.42.0
3+
VERSION = 2.43.0
44

55
PREFIX = /usr/local
66
INSTALL_PATH = $(PREFIX)/bin/$(EXECUTABLE_NAME)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ swift run xcodegen
112112
Add the following to your Package.swift file's dependencies:
113113

114114
```swift
115-
.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.42.0"),
115+
.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.43.0"),
116116
```
117117

118118
And then import wherever needed: `import XcodeGenKit`

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/Project.swift

Lines changed: 5 additions & 3 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 }

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/SpecParsingError.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public enum SpecParsingError: Error, CustomStringConvertible {
1515
case unknownBreakpointActionType(String)
1616
case unknownBreakpointActionConveyanceType(String)
1717
case unknownBreakpointActionSoundName(String)
18+
case invalidConfigsMappingFormat(keys: Set<String>)
1819

1920
public var description: String {
2021
switch self {
@@ -46,6 +47,8 @@ public enum SpecParsingError: Error, CustomStringConvertible {
4647
return "Unknown Breakpoint Action conveyance type: \(type)"
4748
case let .unknownBreakpointActionSoundName(name):
4849
return "Unknown Breakpoint Action sound name: \(name)"
50+
case let .invalidConfigsMappingFormat(keys):
51+
return "Invalid format: The value for \"\(keys.sorted().joined(separator: ", "))\" in `configs` must be mapping format"
4952
}
5053
}
5154
}

Sources/ProjectSpec/Target.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ extension Target: NamedJSONDictionaryConvertible {
316316
deploymentTarget = nil
317317
}
318318

319-
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
319+
settings = try BuildSettingsParser(jsonDictionary: jsonDictionary).parse()
320320
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
321321
if let source: String = jsonDictionary.json(atKeyPath: "sources") {
322322
sources = [TargetSource(path: source)]

0 commit comments

Comments
 (0)