Skip to content

Commit a0fbf35

Browse files
authored
Minor Codable conformance improvements in SwiftBuildSupport (#8449)
### Motivation: Some of the `SwiftBuildSupport/PIFBuilder.swift` encoding/decoding code was using unsafe string keys. This replaces with proper typesafe keys. This part of the ground work to support the new PIF builder in `SwiftBuildSupport` (i.e., rdar://147527170). ### Modifications: When conforming to `Codable`, replace string keys with `enum` based keys instead. ### Result: The resulting code is a tad safer and easier to understand :-) Tracked by rdar://148546582.
1 parent 4bd3f0e commit a0fbf35

File tree

1 file changed

+82
-61
lines changed

1 file changed

+82
-61
lines changed

Sources/SwiftBuildSupport/PIF.swift

Lines changed: 82 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,25 @@ public enum PIF {
6060
}
6161
}
6262

63-
public class TypedObject: Codable {
63+
/// Represents a high-level PIF object.
64+
///
65+
/// For instance, a JSON serialized *workspace* might look like this:
66+
/// ```json
67+
/// {
68+
/// "type" : "workspace",
69+
/// "signature" : "22e9436958aec481799",
70+
/// "contents" : {
71+
/// "guid" : "Workspace:/Users/foo/BarPackage",
72+
/// "name" : "BarPackage",
73+
/// "path" : "/Users/foo/BarPackage",
74+
/// "projects" : [
75+
/// "70a588f37dcfcddbc1f",
76+
/// "c1d9cb257bd42cafbb8"
77+
/// ]
78+
/// }
79+
/// }
80+
/// ```
81+
public class HighLevelObject: Codable {
6482
class var type: String {
6583
fatalError("\(self) missing implementation")
6684
}
@@ -71,8 +89,9 @@ public enum PIF {
7189
type = Swift.type(of: self).type
7290
}
7391

74-
private enum CodingKeys: CodingKey {
92+
fileprivate enum CodingKeys: CodingKey {
7593
case type
94+
case signature, contents // Used by subclasses.
7695
}
7796

7897
public func encode(to encoder: Encoder) throws {
@@ -86,7 +105,7 @@ public enum PIF {
86105
}
87106
}
88107

89-
public final class Workspace: TypedObject {
108+
public final class Workspace: HighLevelObject {
90109
override class var type: String { "workspace" }
91110

92111
public let guid: GUID
@@ -113,8 +132,8 @@ public enum PIF {
113132

114133
public override func encode(to encoder: Encoder) throws {
115134
try super.encode(to: encoder)
116-
var container = encoder.container(keyedBy: StringKey.self)
117-
var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
135+
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
136+
var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
118137
try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid)
119138
try contents.encode(name, forKey: .name)
120139
try contents.encode(path, forKey: .path)
@@ -123,29 +142,29 @@ public enum PIF {
123142
guard let signature else {
124143
throw InternalError("Expected to have workspace signature when encoding for SwiftBuild")
125144
}
126-
try container.encode(signature, forKey: "signature")
145+
try superContainer.encode(signature, forKey: .signature)
127146
try contents.encode(projects.map({ $0.signature }), forKey: .projects)
128147
} else {
129148
try contents.encode(projects, forKey: .projects)
130149
}
131150
}
132151

133152
public required init(from decoder: Decoder) throws {
134-
let superContainer = try decoder.container(keyedBy: StringKey.self)
135-
let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
153+
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
154+
let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
136155

137-
let guidString = try container.decode(GUID.self, forKey: .guid)
156+
let guidString = try contents.decode(GUID.self, forKey: .guid)
138157
self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1))
139-
self.name = try container.decode(String.self, forKey: .name)
140-
self.path = try container.decode(AbsolutePath.self, forKey: .path)
141-
self.projects = try container.decode([Project].self, forKey: .projects)
158+
self.name = try contents.decode(String.self, forKey: .name)
159+
self.path = try contents.decode(AbsolutePath.self, forKey: .path)
160+
self.projects = try contents.decode([Project].self, forKey: .projects)
142161
try super.init(from: decoder)
143162
}
144163
}
145164

146165
/// A PIF project, consisting of a tree of groups and file references, a list of targets, and some additional
147166
/// information.
148-
public final class Project: TypedObject {
167+
public final class Project: HighLevelObject {
149168
override class var type: String { "project" }
150169

151170
public let guid: GUID
@@ -191,8 +210,8 @@ public enum PIF {
191210

192211
public override func encode(to encoder: Encoder) throws {
193212
try super.encode(to: encoder)
194-
var container = encoder.container(keyedBy: StringKey.self)
195-
var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
213+
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
214+
var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
196215
try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid)
197216
try contents.encode(name, forKey: .projectName)
198217
try contents.encode("true", forKey: .projectIsPackage)
@@ -206,7 +225,7 @@ public enum PIF {
206225
guard let signature else {
207226
throw InternalError("Expected to have project signature when encoding for SwiftBuild")
208227
}
209-
try container.encode(signature, forKey: "signature")
228+
try superContainer.encode(signature, forKey: .signature)
210229
try contents.encode(targets.map{ $0.signature }, forKey: .targets)
211230
} else {
212231
try contents.encode(targets, forKey: .targets)
@@ -216,19 +235,19 @@ public enum PIF {
216235
}
217236

218237
public required init(from decoder: Decoder) throws {
219-
let superContainer = try decoder.container(keyedBy: StringKey.self)
220-
let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
238+
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
239+
let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
221240

222-
let guidString = try container.decode(GUID.self, forKey: .guid)
241+
let guidString = try contents.decode(GUID.self, forKey: .guid)
223242
self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1))
224-
self.name = try container.decode(String.self, forKey: .projectName)
225-
self.path = try container.decode(AbsolutePath.self, forKey: .path)
226-
self.projectDirectory = try container.decode(AbsolutePath.self, forKey: .projectDirectory)
227-
self.developmentRegion = try container.decode(String.self, forKey: .developmentRegion)
228-
self.buildConfigurations = try container.decode([BuildConfiguration].self, forKey: .buildConfigurations)
229-
230-
let untypedTargets = try container.decode([UntypedTarget].self, forKey: .targets)
231-
var targetContainer = try container.nestedUnkeyedContainer(forKey: .targets)
243+
self.name = try contents.decode(String.self, forKey: .projectName)
244+
self.path = try contents.decode(AbsolutePath.self, forKey: .path)
245+
self.projectDirectory = try contents.decode(AbsolutePath.self, forKey: .projectDirectory)
246+
self.developmentRegion = try contents.decode(String.self, forKey: .developmentRegion)
247+
self.buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations)
248+
249+
let untypedTargets = try contents.decode([UntypedTarget].self, forKey: .targets)
250+
var targetContainer = try contents.nestedUnkeyedContainer(forKey: .targets)
232251
self.targets = try untypedTargets.map { target in
233252
let type = target.contents.type
234253
switch type {
@@ -241,13 +260,13 @@ public enum PIF {
241260
}
242261
}
243262

244-
self.groupTree = try container.decode(Group.self, forKey: .groupTree)
263+
self.groupTree = try contents.decode(Group.self, forKey: .groupTree)
245264
try super.init(from: decoder)
246265
}
247266
}
248267

249268
/// Abstract base class for all items in the group hierarchy.
250-
public class Reference: TypedObject {
269+
public class Reference: HighLevelObject {
251270
/// Determines the base path for a reference's relative path.
252271
public enum SourceTree: String, Codable {
253272

@@ -387,7 +406,7 @@ public enum PIF {
387406
public required init(from decoder: Decoder) throws {
388407
let container = try decoder.container(keyedBy: CodingKeys.self)
389408

390-
let untypedChildren = try container.decode([TypedObject].self, forKey: .children)
409+
let untypedChildren = try container.decode([HighLevelObject].self, forKey: .children)
391410
var childrenContainer = try container.nestedUnkeyedContainer(forKey: .children)
392411

393412
self.children = try untypedChildren.map { child in
@@ -440,7 +459,7 @@ public enum PIF {
440459
}
441460
}
442461

443-
public class BaseTarget: TypedObject {
462+
public class BaseTarget: HighLevelObject {
444463
class override var type: String { "target" }
445464
public let guid: GUID
446465
public var name: String
@@ -500,8 +519,8 @@ public enum PIF {
500519

501520
public override func encode(to encoder: Encoder) throws {
502521
try super.encode(to: encoder)
503-
var container = encoder.container(keyedBy: StringKey.self)
504-
var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
522+
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
523+
var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
505524
try contents.encode("aggregate", forKey: .type)
506525
try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid)
507526
try contents.encode(name, forKey: .name)
@@ -514,22 +533,22 @@ public enum PIF {
514533
guard let signature else {
515534
throw InternalError("Expected to have \(Swift.type(of: self)) signature when encoding for SwiftBuild")
516535
}
517-
try container.encode(signature, forKey: "signature")
536+
try superContainer.encode(signature, forKey: .signature)
518537
}
519538
}
520539

521540
public required init(from decoder: Decoder) throws {
522-
let superContainer = try decoder.container(keyedBy: StringKey.self)
523-
let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
541+
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
542+
let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
524543

525-
let guidString = try container.decode(GUID.self, forKey: .guid)
544+
let guidString = try contents.decode(GUID.self, forKey: .guid)
526545
let guid = String(guidString.dropLast("\(schemaVersion)".count + 1))
527546

528-
let name = try container.decode(String.self, forKey: .name)
529-
let buildConfigurations = try container.decode([BuildConfiguration].self, forKey: .buildConfigurations)
547+
let name = try contents.decode(String.self, forKey: .name)
548+
let buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations)
530549

531-
let untypedBuildPhases = try container.decode([TypedObject].self, forKey: .buildPhases)
532-
var buildPhasesContainer = try container.nestedUnkeyedContainer(forKey: .buildPhases)
550+
let untypedBuildPhases = try contents.decode([HighLevelObject].self, forKey: .buildPhases)
551+
var buildPhasesContainer = try contents.nestedUnkeyedContainer(forKey: .buildPhases)
533552

534553
let buildPhases: [BuildPhase] = try untypedBuildPhases.map {
535554
guard let type = $0.type else {
@@ -538,8 +557,8 @@ public enum PIF {
538557
return try BuildPhase.decode(container: &buildPhasesContainer, type: type)
539558
}
540559

541-
let dependencies = try container.decode([TargetDependency].self, forKey: .dependencies)
542-
let impartedBuildProperties = try container.decode(BuildSettings.self, forKey: .impartedBuildProperties)
560+
let dependencies = try contents.decode([TargetDependency].self, forKey: .dependencies)
561+
let impartedBuildProperties = try contents.decode(BuildSettings.self, forKey: .impartedBuildProperties)
543562

544563
super.init(
545564
guid: guid,
@@ -600,8 +619,8 @@ public enum PIF {
600619

601620
override public func encode(to encoder: Encoder) throws {
602621
try super.encode(to: encoder)
603-
var container = encoder.container(keyedBy: StringKey.self)
604-
var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
622+
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
623+
var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
605624
try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid)
606625
try contents.encode(name, forKey: .name)
607626
try contents.encode(dependencies, forKey: .dependencies)
@@ -611,7 +630,7 @@ public enum PIF {
611630
guard let signature else {
612631
throw InternalError("Expected to have \(Swift.type(of: self)) signature when encoding for SwiftBuild")
613632
}
614-
try container.encode(signature, forKey: "signature")
633+
try superContainer.encode(signature, forKey: .signature)
615634
}
616635

617636
if productType == .packageProduct {
@@ -639,34 +658,34 @@ public enum PIF {
639658
}
640659

641660
public required init(from decoder: Decoder) throws {
642-
let superContainer = try decoder.container(keyedBy: StringKey.self)
643-
let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents")
661+
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
662+
let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
644663

645-
let guidString = try container.decode(GUID.self, forKey: .guid)
664+
let guidString = try contents.decode(GUID.self, forKey: .guid)
646665
let guid = String(guidString.dropLast("\(schemaVersion)".count + 1))
647-
let name = try container.decode(String.self, forKey: .name)
648-
let buildConfigurations = try container.decode([BuildConfiguration].self, forKey: .buildConfigurations)
649-
let dependencies = try container.decode([TargetDependency].self, forKey: .dependencies)
666+
let name = try contents.decode(String.self, forKey: .name)
667+
let buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations)
668+
let dependencies = try contents.decode([TargetDependency].self, forKey: .dependencies)
650669

651-
let type = try container.decode(String.self, forKey: .type)
670+
let type = try contents.decode(String.self, forKey: .type)
652671

653672
let buildPhases: [BuildPhase]
654673
let impartedBuildProperties: ImpartedBuildProperties
655674

656675
if type == "packageProduct" {
657676
self.productType = .packageProduct
658677
self.productName = ""
659-
let fwkBuildPhase = try container.decodeIfPresent(FrameworksBuildPhase.self, forKey: .frameworksBuildPhase)
678+
let fwkBuildPhase = try contents.decodeIfPresent(FrameworksBuildPhase.self, forKey: .frameworksBuildPhase)
660679
buildPhases = fwkBuildPhase.map{ [$0] } ?? []
661680
impartedBuildProperties = ImpartedBuildProperties(settings: BuildSettings())
662681
} else if type == "standard" {
663-
self.productType = try container.decode(ProductType.self, forKey: .productTypeIdentifier)
682+
self.productType = try contents.decode(ProductType.self, forKey: .productTypeIdentifier)
664683

665-
let productReference = try container.decode([String: String].self, forKey: .productReference)
684+
let productReference = try contents.decode([String: String].self, forKey: .productReference)
666685
self.productName = productReference["name"]!
667686

668-
let untypedBuildPhases = try container.decodeIfPresent([TypedObject].self, forKey: .buildPhases) ?? []
669-
var buildPhasesContainer = try container.nestedUnkeyedContainer(forKey: .buildPhases)
687+
let untypedBuildPhases = try contents.decodeIfPresent([HighLevelObject].self, forKey: .buildPhases) ?? []
688+
var buildPhasesContainer = try contents.nestedUnkeyedContainer(forKey: .buildPhases)
670689

671690
buildPhases = try untypedBuildPhases.map {
672691
guard let type = $0.type else {
@@ -675,7 +694,7 @@ public enum PIF {
675694
return try BuildPhase.decode(container: &buildPhasesContainer, type: type)
676695
}
677696

678-
impartedBuildProperties = try container.decode(ImpartedBuildProperties.self, forKey: .impartedBuildProperties)
697+
impartedBuildProperties = try contents.decode(ImpartedBuildProperties.self, forKey: .impartedBuildProperties)
679698
} else {
680699
throw InternalError("Unhandled target type \(type)")
681700
}
@@ -693,7 +712,7 @@ public enum PIF {
693712
}
694713

695714
/// Abstract base class for all build phases in a target.
696-
public class BuildPhase: TypedObject {
715+
public class BuildPhase: HighLevelObject {
697716
static func decode(container: inout UnkeyedDecodingContainer, type: String) throws -> BuildPhase {
698717
switch type {
699718
case HeadersBuildPhase.type:
@@ -1158,7 +1177,7 @@ public struct SwiftBuildFileType: CaseIterable {
11581177
}
11591178
}
11601179

1161-
struct StringKey: CodingKey, ExpressibleByStringInterpolation {
1180+
fileprivate struct StringKey: CodingKey, ExpressibleByStringInterpolation {
11621181
var stringValue: String
11631182
var intValue: Int?
11641183

@@ -1253,6 +1272,8 @@ private struct UntypedTarget: Decodable {
12531272
let contents: TargetContents
12541273
}
12551274

1275+
// MARK: - PIF Signature Support
1276+
12561277
protocol PIFSignableObject: AnyObject {
12571278
var signature: String? { get set }
12581279
}

0 commit comments

Comments
 (0)