Skip to content

Commit 918c91b

Browse files
committed
WIP: Adopt new PIF builder in SwiftBuildSupport
1 parent e303b89 commit 918c91b

File tree

7 files changed

+540
-214
lines changed

7 files changed

+540
-214
lines changed

Sources/SwiftBuildSupport/BuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension BuildSubset {
1414
var pifTargetName: String {
1515
switch self {
1616
case .product(let name, _):
17-
_PackagePIFProjectBuilder.targetName(for: name)
17+
PackagePIFBuilder.targetName(forProductName: name)
1818
case .target(let name, _):
1919
name
2020
case .allExcludingTests:

Sources/SwiftBuildSupport/PIF.swift

Lines changed: 198 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import PackageModel
1717

1818
import struct TSCBasic.ByteString
1919

20+
import enum SwiftBuild.ProjectModel
21+
2022
/// The Project Interchange Format (PIF) is a structured representation of the
2123
/// project model created by clients to send to SwiftBuild.
2224
///
@@ -31,35 +33,41 @@ public enum PIF {
3133
/// are represented by the objects which do not use a content-based signature scheme (workspaces and projects,
3234
/// currently).
3335
static let schemaVersion = 11
34-
36+
3537
/// The type used for identifying PIF objects.
3638
public typealias GUID = String
37-
39+
3840
/// The top-level PIF object.
3941
public struct TopLevelObject: Encodable {
4042
public let workspace: PIF.Workspace
41-
43+
4244
public init(workspace: PIF.Workspace) {
4345
self.workspace = workspace
4446
}
45-
47+
4648
public func encode(to encoder: Encoder) throws {
4749
var container = encoder.unkeyedContainer()
48-
50+
4951
// Encode the workspace.
5052
try container.encode(workspace)
51-
53+
5254
// Encode the projects and their targets.
5355
for project in workspace.projects {
5456
try container.encode(project)
55-
56-
for target in project.targets {
57-
try container.encode(target)
57+
let targets = project.underlying.targets
58+
59+
for target in targets where !target.id.hasSuffix(.dynamic) {
60+
try container.encode(Target(wrapping: target))
61+
}
62+
63+
// Add *dynamic variants* at the end just to have a clear split from other targets.
64+
for target in targets where target.id.hasSuffix(.dynamic) {
65+
try container.encode(Target(wrapping: target))
5866
}
5967
}
6068
}
6169
}
62-
70+
6371
/// Represents a high-level PIF object.
6472
///
6573
/// For instance, a JSON serialized *workspace* might look like this:
@@ -82,89 +90,235 @@ public enum PIF {
8290
class var type: String {
8391
fatalError("\(self) missing implementation")
8492
}
85-
86-
let type: String?
87-
93+
94+
let type: String
95+
8896
fileprivate init() {
89-
type = Swift.type(of: self).type
97+
type = Self.type
9098
}
91-
99+
92100
fileprivate enum CodingKeys: CodingKey {
93101
case type
94102
case signature, contents // Used by subclasses.
95103
}
96-
104+
97105
public func encode(to encoder: Encoder) throws {
98106
var container = encoder.container(keyedBy: CodingKeys.self)
99-
try container.encode(Swift.type(of: self).type, forKey: .type)
107+
try container.encode(type, forKey: .type)
100108
}
101-
109+
102110
required public init(from decoder: Decoder) throws {
103111
let container = try decoder.container(keyedBy: CodingKeys.self)
104-
type = try container.decode(String.self, forKey: .type)
112+
self.type = try container.decode(String.self, forKey: .type)
113+
114+
guard self.type == Self.type else {
115+
throw InternalError("Expected same type for high-level object: \(self.type)")
116+
}
105117
}
106118
}
107-
119+
108120
public final class Workspace: HighLevelObject {
109121
override class var type: String { "workspace" }
110-
122+
111123
public let guid: GUID
112124
public var name: String
113125
public var path: AbsolutePath
114126
public var projects: [Project]
115127
var signature: String?
116128

117-
public init(guid: GUID, name: String, path: AbsolutePath, projects: [Project]) {
129+
public init(guid: GUID, name: String, path: AbsolutePath, projects: [ProjectModel.Project]) {
118130
precondition(!guid.isEmpty)
119131
precondition(!name.isEmpty)
120-
precondition(Set(projects.map({ $0.guid })).count == projects.count)
121-
132+
precondition(Set(projects.map(\.id)).count == projects.count)
133+
122134
self.guid = guid
123135
self.name = name
124136
self.path = path
125-
self.projects = projects
137+
self.projects = projects.map { Project(wrapping: $0) }
126138
super.init()
127139
}
128-
140+
129141
private enum CodingKeys: CodingKey {
130-
case guid, name, path, projects, signature
142+
case guid, name, path, projects
131143
}
132-
144+
133145
public override func encode(to encoder: Encoder) throws {
134146
try super.encode(to: encoder)
147+
135148
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
136149
var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
150+
137151
try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid)
138152
try contents.encode(name, forKey: .name)
139153
try contents.encode(path, forKey: .path)
140-
154+
try contents.encode(projects.map(\.signature), forKey: .projects)
155+
141156
if encoder.userInfo.keys.contains(.encodeForSwiftBuild) {
142157
guard let signature else {
143-
throw InternalError("Expected to have workspace signature when encoding for SwiftBuild")
158+
throw InternalError("Expected to have workspace *signature* when encoding for SwiftBuild")
144159
}
145160
try superContainer.encode(signature, forKey: .signature)
146-
try contents.encode(projects.map({ $0.signature }), forKey: .projects)
147-
} else {
148-
try contents.encode(projects, forKey: .projects)
149161
}
150162
}
151-
163+
152164
public required init(from decoder: Decoder) throws {
153165
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
154166
let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents)
155-
167+
156168
let guidString = try contents.decode(GUID.self, forKey: .guid)
157169
self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1))
158170
self.name = try contents.decode(String.self, forKey: .name)
159171
self.path = try contents.decode(AbsolutePath.self, forKey: .path)
160172
self.projects = try contents.decode([Project].self, forKey: .projects)
173+
174+
try super.init(from: decoder)
175+
}
176+
}
177+
178+
public final class Project: HighLevelObject {
179+
override class var type: String { "project" }
180+
181+
public fileprivate(set) var underlying: ProjectModel.Project
182+
var signature: String?
183+
184+
var id: ProjectModel.GUID { underlying.id }
185+
186+
public init(wrapping underlying: ProjectModel.Project) {
187+
precondition(!underlying.name.isEmpty)
188+
precondition(!underlying.id.value.isEmpty)
189+
precondition(!underlying.path.isEmpty)
190+
precondition(!underlying.projectDir.isEmpty)
191+
/* precondition(!underlying.developmentRegion!.isEmpty) */
192+
193+
precondition(Set(underlying.targets.map(\.id)).count == underlying.targets.count)
194+
precondition(Set(underlying.buildConfigs.map(\.id)).count == underlying.buildConfigs.count)
195+
196+
self.underlying = underlying
197+
super.init()
198+
}
199+
200+
public override func encode(to encoder: any Encoder) throws {
201+
try super.encode(to: encoder)
202+
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
203+
try superContainer.encode(underlying, forKey: .contents)
204+
205+
if encoder.userInfo.keys.contains(.encodeForSwiftBuild) {
206+
guard let signature else {
207+
throw InternalError("Expected to have project *signature* when encoding for SwiftBuild")
208+
}
209+
try superContainer.encode(signature, forKey: .signature)
210+
}
211+
}
212+
213+
public required init(from decoder: Decoder) throws {
214+
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
215+
self.underlying = try superContainer.decode(ProjectModel.Project.self, forKey: .contents)
216+
217+
try super.init(from: decoder)
218+
}
219+
}
220+
221+
private final class Target: HighLevelObject {
222+
override class var type: String { "target" }
223+
224+
public fileprivate(set) var underlying: ProjectModel.BaseTarget
225+
226+
var id: ProjectModel.GUID { underlying.id }
227+
228+
public init(wrapping underlying: ProjectModel.BaseTarget) {
229+
precondition(!underlying.id.value.isEmpty)
230+
precondition(!underlying.common.name.isEmpty)
231+
232+
self.underlying = underlying
233+
super.init()
234+
}
235+
236+
public override func encode(to encoder: any Encoder) throws {
237+
try super.encode(to: encoder)
238+
var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self)
239+
try superContainer.encode(underlying, forKey: .contents)
240+
241+
if encoder.userInfo.keys.contains(.encodeForSwiftBuild) {
242+
guard let signature = underlying.common.signature else {
243+
throw InternalError("Expected to have target *signature* when encoding for SwiftBuild")
244+
}
245+
try superContainer.encode(signature, forKey: .signature)
246+
}
247+
}
248+
249+
public required init(from decoder: Decoder) throws {
250+
fatalError("Decoding not implemented")
251+
/*
252+
let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self)
253+
self.underlying = try superContainer.decode(ProjectModel.BaseTarget.self, forKey: .contents)
254+
161255
try super.init(from: decoder)
256+
*/
257+
}
258+
}
259+
}
260+
261+
extension CodingUserInfoKey {
262+
public static let encodingPIFSignature: CodingUserInfoKey = CodingUserInfoKey(rawValue: "encodingPIFSignature")!
263+
264+
/// Perform the encoding for SwiftBuild consumption.
265+
public static let encodeForSwiftBuild: CodingUserInfoKey = CodingUserInfoKey(rawValue: "encodeForXCBuild")!
266+
}
267+
268+
// MARK: - PIF Signature Support
269+
270+
protocol PIFSignableObject {
271+
var signature: String? { get set }
272+
var name: String { get }
273+
}
274+
275+
extension PIF.Workspace: PIFSignableObject {}
276+
extension SwiftBuild.ProjectModel.Project: PIFSignableObject {
277+
var signature: String? { get { "" } set { } }
278+
279+
}
280+
extension SwiftBuild.ProjectModel.TargetCommon: PIFSignableObject {}
281+
282+
extension PIF {
283+
/// Add signature to workspace and its high-level subobjects.
284+
static func sign(workspace: PIF.Workspace) throws {
285+
let encoder = JSONEncoder.makeWithDefaults()
286+
287+
func signature(of obj: some Encodable) throws -> String {
288+
let signatureContent = try encoder.encode(obj)
289+
let signatureBytes = ByteString(signatureContent)
290+
let signature = signatureBytes.sha256Checksum
291+
return signature
292+
}
293+
294+
for project in workspace.projects {
295+
for targetIndex in project.underlying.targets.indices {
296+
let targetSignature = try signature(of: project.underlying.targets[targetIndex])
297+
project.underlying.targets[targetIndex].common.signature = targetSignature
298+
}
299+
project.signature = try signature(of: project)
300+
}
301+
workspace.signature = try signature(of: workspace)
302+
}
303+
304+
static func ____sign(workspace: PIF.Workspace) throws {
305+
for project in workspace.projects {
306+
for targetIndex in project.underlying.targets.indices {
307+
let targetSignature = project.underlying.targets[targetIndex].id.value
308+
project.underlying.targets[targetIndex].common.signature = targetSignature
309+
}
310+
project.signature = project.id.value
162311
}
312+
workspace.signature = workspace.guid
163313
}
314+
}
164315

316+
317+
/*
318+
165319
/// A PIF project, consisting of a tree of groups and file references, a list of targets, and some additional
166320
/// information.
167-
public final class Project: HighLevelObject {
321+
public final class __Project: HighLevelObject {
168322
override class var type: String { "project" }
169323

170324
public let guid: GUID
@@ -1134,8 +1288,7 @@ public enum PIF {
11341288
multipleValueSettings = try container.decodeIfPresent(OrderedDictionary<MultipleValueSetting, [String]>.self, forKey: .multipleValueSettings) ?? [:]
11351289
}
11361290
}
1137-
}
1138-
1291+
11391292
/// Represents a filetype recognized by the Xcode build system.
11401293
public struct SwiftBuildFileType: CaseIterable {
11411294
public static let xcassets: SwiftBuildFileType = SwiftBuildFileType(
@@ -1258,43 +1411,11 @@ extension PIF.FileReference {
12581411
}
12591412
}
12601413

1261-
extension CodingUserInfoKey {
1262-
public static let encodingPIFSignature: CodingUserInfoKey = CodingUserInfoKey(rawValue: "encodingPIFSignature")!
1263-
1264-
/// Perform the encoding for SwiftBuild consumption.
1265-
public static let encodeForSwiftBuild: CodingUserInfoKey = CodingUserInfoKey(rawValue: "encodeForXCBuild")!
1266-
}
1267-
1268-
private struct UntypedTarget: Decodable {
1269-
struct TargetContents: Decodable {
1270-
let type: String
1271-
}
1272-
let contents: TargetContents
1273-
}
1274-
1275-
// MARK: - PIF Signature Support
1276-
1277-
protocol PIFSignableObject: AnyObject {
1278-
var signature: String? { get set }
1279-
}
1280-
extension PIF.Workspace: PIFSignableObject {}
1281-
extension PIF.Project: PIFSignableObject {}
1282-
extension PIF.BaseTarget: PIFSignableObject {}
1414+
private struct UntypedTarget: Decodable {
1415+
struct TargetContents: Decodable {
1416+
let type: String
1417+
}
1418+
let contents: TargetContents
1419+
}
12831420

1284-
extension PIF {
1285-
/// Add signature to workspace and its subobjects.
1286-
public static func sign(_ workspace: PIF.Workspace) throws {
1287-
let encoder = JSONEncoder.makeWithDefaults()
1288-
1289-
func sign<T: PIFSignableObject & Encodable>(_ obj: T) throws {
1290-
let signatureContent = try encoder.encode(obj)
1291-
let bytes = ByteString(signatureContent)
1292-
obj.signature = bytes.sha256Checksum
1293-
}
1294-
1295-
let projects = workspace.projects
1296-
try projects.flatMap{ $0.targets }.forEach(sign)
1297-
try projects.forEach(sign)
1298-
try sign(workspace)
1299-
}
1300-
}
1421+
*/

0 commit comments

Comments
 (0)