Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## Master

### Fixed
- Fix nested target attributes (e.g. `attributes.SystemCapabilities`) being serialized as a stringified Swift `Dictionary` description instead of a proper nested plist dictionary, which also caused non-deterministic key ordering in generated `project.pbxproj` files across runs @imadaan

## 2.46.0

### Added
Expand Down
8 changes: 8 additions & 0 deletions Sources/ProjectSpec/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ extension ProjectAttribute {
self = .array(array)
} else if let object = value as? PBXObject {
self = .targetReference(object)
} else if let dictionary = value as? [String: [String: Any]] {
// e.g. `SystemCapabilities`, which is a dictionary of dictionaries.
// Without this case, such values fall through to the `.string("\(value)")`
// branch below, which interpolates the raw Swift `Dictionary`. Its
// `description` iterates in the process's random hash-seed order, so the
// same input can non-deterministically produce a different key order (and
// isn't a valid nested plist dictionary in the first place).
self = .attributeDictionary(dictionary.mapValues { $0.mapValues { ProjectAttribute(any: $0) } })
} else {
self = .string("\(value)")
}
Expand Down
21 changes: 21 additions & 0 deletions Tests/XcodeGenKitTests/ProjectGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,27 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetAttributes[appTarget]?["DevelopmentTeam"]?.stringValue) == "123"
}

$0.it("generates nested target attributes such as SystemCapabilities") {
var appTargetWithAttributes = app
appTargetWithAttributes.attributes = [
"SystemCapabilities": [
"com.apple.Push": ["enabled": 1],
"com.apple.Keychain": ["enabled": 1],
],
]
let project = Project(name: "test", targets: [appTargetWithAttributes, framework])
let pbxProject = try project.generatePbxProj()

let targetAttributes = try unwrap(pbxProject.projects.first?.targetAttributes)
let appTarget = try unwrap(pbxProject.targets(named: app.name).first)

guard case let .attributeDictionary(systemCapabilities)? = targetAttributes[appTarget]?["SystemCapabilities"] else {
throw failure("Expected SystemCapabilities to be a nested attribute dictionary, not a string")
}
try expect(systemCapabilities["com.apple.Push"]?["enabled"]?.stringValue) == "1"
try expect(systemCapabilities["com.apple.Keychain"]?["enabled"]?.stringValue) == "1"
}

$0.it("generates platform version") {
let target = Target(name: "Target", type: .application, platform: .watchOS, deploymentTarget: "2.0")
let project = Project(name: "", targets: [target], options: .init(deploymentTarget: DeploymentTarget(iOS: "10.0", watchOS: "3.0")))
Expand Down