Skip to content

Commit 18ea3ea

Browse files
authored
[NFC] Build/PackageModel: split Target.swift and BuildPlan.swift (#6972)
### Motivation: The `Build` module is not very well documented and the overall flow of build planning is not obvious when trying to read its code. ### Modifications: `Target.swift` and `BuildPlan.swift` are split into separate files that contain either separate types or extensions with corresponding functionality. Now that both files are split, their extensions mostly correspond to types of targets that SwiftPM supports: * Clang * Swift * Plugin * System Library This separation is also closely reflected in `BuildPlan` extensions: * Clang * Product * Swift * Test Also moved product-related build manifest code to `LLBuildManifestBuilder+Product.swift`. ### Result: Now it's much easier to navigate across the `Build` module codebase, and each step of build planning has a separate directory that describes the overall flow: 1. Build description is formed (the existing `BuildDescription` directory); 2. Build planning assigns necessary flags, options, and arguments (the new `BuildPlan` directory); 3. Build manifest is written out for llbuild (the existing `BuildManifest` directory). As a bonus, these files can now be built in parallel, but I don't think it's noticeable when building. Another benefit is that imports for each new file are explicit and don't pollute the corresponding namespace with wildcard imports of whole modules.
1 parent 38e21a2 commit 18ea3ea

19 files changed

+2348
-2165
lines changed

Sources/Build/BuildDescription/ProductBuildDescription.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
124124
// during GC, and it removes Swift metadata sections like swift5_protocols
125125
// We should add support of SHF_GNU_RETAIN-like flag for __attribute__((retain))
126126
// to LLVM and wasm-ld
127-
// This workaround is required for not only WASI but also all WebAssembly archs
127+
// This workaround is required for not only WASI but also all WebAssembly triples
128128
// using wasm-ld (e.g. wasm32-unknown-unknown). So this branch is conditioned by
129129
// arch == .wasm32
130130
return []
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2015-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import struct Basics.AbsolutePath
14+
import struct LLBuildManifest.Node
15+
16+
extension LLBuildManifestBuilder {
17+
func createProductCommand(_ buildProduct: ProductBuildDescription) throws {
18+
let cmdName = try buildProduct.product.getCommandName(config: self.buildConfig)
19+
20+
// Add dependency on Info.plist generation on Darwin platforms.
21+
let testInputs: [AbsolutePath]
22+
if buildProduct.product.type == .test
23+
&& buildProduct.buildParameters.targetTriple.isDarwin()
24+
&& buildProduct.buildParameters.experimentalTestOutput {
25+
let testBundleInfoPlistPath = try buildProduct.binaryPath.parentDirectory.parentDirectory.appending(component: "Info.plist")
26+
testInputs = [testBundleInfoPlistPath]
27+
28+
self.manifest.addWriteInfoPlistCommand(
29+
principalClass: "\(buildProduct.product.targets[0].c99name).SwiftPMXCTestObserver",
30+
outputPath: testBundleInfoPlistPath
31+
)
32+
} else {
33+
testInputs = []
34+
}
35+
36+
switch buildProduct.product.type {
37+
case .library(.static):
38+
try self.manifest.addShellCmd(
39+
name: cmdName,
40+
description: "Archiving \(buildProduct.binaryPath.prettyPath())",
41+
inputs: (buildProduct.objects + [buildProduct.linkFileListPath]).map(Node.file),
42+
outputs: [.file(buildProduct.binaryPath)],
43+
arguments: try buildProduct.archiveArguments()
44+
)
45+
46+
default:
47+
let inputs = try buildProduct.objects
48+
+ buildProduct.dylibs.map { try $0.binaryPath }
49+
+ [buildProduct.linkFileListPath]
50+
+ testInputs
51+
52+
try self.manifest.addShellCmd(
53+
name: cmdName,
54+
description: "Linking \(buildProduct.binaryPath.prettyPath())",
55+
inputs: inputs.map(Node.file),
56+
outputs: [.file(buildProduct.binaryPath)],
57+
arguments: try buildProduct.linkArguments()
58+
)
59+
}
60+
61+
// Create a phony node to represent the entire target.
62+
let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig)
63+
let output: Node = .virtual(targetName)
64+
65+
self.manifest.addNode(output, toTarget: targetName)
66+
try self.manifest.addPhonyCmd(
67+
name: output.name,
68+
inputs: [.file(buildProduct.binaryPath)],
69+
outputs: [output]
70+
)
71+
72+
if self.plan.graph.reachableProducts.contains(buildProduct.product) {
73+
if buildProduct.product.type != .test {
74+
self.addNode(output, toTarget: .main)
75+
}
76+
self.addNode(output, toTarget: .test)
77+
}
78+
79+
self.manifest.addWriteLinkFileListCommand(
80+
objects: Array(buildProduct.objects),
81+
linkFileListPath: buildProduct.linkFileListPath
82+
)
83+
}
84+
}

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
@_implementationOnly import SwiftDriver
1515
import struct Basics.InternalError
1616
import struct Basics.AbsolutePath
17+
import struct Basics.RelativePath
1718
import struct Basics.TSCAbsolutePath
1819
import struct LLBuildManifest.Node
1920
import struct LLBuildManifest.BuildManifest
2021
import struct SPMBuildCore.BuildParameters
2122
import class PackageGraph.ResolvedTarget
23+
import protocol TSCBasic.FileSystem
2224
import enum TSCBasic.ProcessEnv
2325
import func TSCBasic.topologicalSort
2426

@@ -556,3 +558,22 @@ private class UniqueExplicitDependencyJobTracker {
556558
return new
557559
}
558560
}
561+
562+
extension TypedVirtualPath {
563+
/// Resolve a typed virtual path provided by the Swift driver to
564+
/// a node in the build graph.
565+
fileprivate func resolveToNode(fileSystem: some FileSystem) throws -> Node {
566+
if let absolutePath = (file.absolutePath.flatMap { AbsolutePath($0) }) {
567+
return Node.file(absolutePath)
568+
} else if let relativePath = (file.relativePath.flatMap { RelativePath($0) }) {
569+
guard let workingDirectory: AbsolutePath = fileSystem.currentWorkingDirectory else {
570+
throw InternalError("unknown working directory")
571+
}
572+
return Node.file(workingDirectory.appending(relativePath))
573+
} else if let temporaryFileName = file.temporaryFileName {
574+
return Node.virtual(temporaryFileName.pathString)
575+
} else {
576+
throw InternalError("Cannot resolve VirtualPath: \(file)")
577+
}
578+
}
579+
}

Sources/Build/BuildManifest/LLBuildManifestBuilder.swift

Lines changed: 3 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ extension LLBuildManifestBuilder {
130130

131131
// Add the output paths of any prebuilds that were run, so that we redo the plan if they change.
132132
var derivedSourceDirPaths: [AbsolutePath] = []
133-
for result in plan.prebuildCommandResults.values.flatMap({ $0 }) {
133+
for result in self.plan.prebuildCommandResults.values.flatMap({ $0 }) {
134134
derivedSourceDirPaths.append(contentsOf: result.outputDirectories)
135135
}
136136
inputs.append(contentsOf: derivedSourceDirPaths.sorted().map { Node.directoryStructure($0) })
@@ -213,7 +213,7 @@ extension LLBuildManifestBuilder {
213213
private func addTestDiscoveryGenerationCommand() throws {
214214
for testDiscoveryTarget in self.plan.targets.compactMap(\.testDiscoveryTargetBuildDescription) {
215215
let testTargets = testDiscoveryTarget.target.dependencies
216-
.compactMap(\.target).compactMap { plan.targetMap[$0] }
216+
.compactMap(\.target).compactMap { self.plan.targetMap[$0] }
217217
let objectFiles = try testTargets.flatMap { try $0.objects }.sorted().map(Node.file)
218218
let outputs = testDiscoveryTarget.target.sources.paths
219219

@@ -241,7 +241,7 @@ extension LLBuildManifestBuilder {
241241
// depends on.
242242
let discoveredTargetDependencyBuildDescriptions = testEntryPointTarget.target.dependencies
243243
.compactMap(\.target)
244-
.compactMap { plan.targetMap[$0] }
244+
.compactMap { self.plan.targetMap[$0] }
245245
.compactMap(\.testDiscoveryTargetBuildDescription)
246246

247247
// The module outputs of the discovery targets this synthesized entry point target depends on are
@@ -273,70 +273,6 @@ extension TargetBuildDescription {
273273
}
274274
}
275275

276-
// MARK: - Product Command
277-
278-
extension LLBuildManifestBuilder {
279-
private func createProductCommand(_ buildProduct: ProductBuildDescription) throws {
280-
let cmdName = try buildProduct.product.getCommandName(config: self.buildConfig)
281-
282-
// Add dependency on Info.plist generation on Darwin platforms.
283-
let testInputs: [AbsolutePath]
284-
if buildProduct.product.type == .test, buildProduct.buildParameters.targetTriple.isDarwin(), buildProduct.buildParameters.experimentalTestOutput {
285-
let testBundleInfoPlistPath = try buildProduct.binaryPath.parentDirectory.parentDirectory.appending(component: "Info.plist")
286-
testInputs = [testBundleInfoPlistPath]
287-
288-
self.manifest.addWriteInfoPlistCommand(principalClass: "\(buildProduct.product.targets[0].c99name).SwiftPMXCTestObserver", outputPath: testBundleInfoPlistPath)
289-
} else {
290-
testInputs = []
291-
}
292-
293-
switch buildProduct.product.type {
294-
case .library(.static):
295-
try self.manifest.addShellCmd(
296-
name: cmdName,
297-
description: "Archiving \(buildProduct.binaryPath.prettyPath())",
298-
inputs: (buildProduct.objects + [buildProduct.linkFileListPath]).map(Node.file),
299-
outputs: [.file(buildProduct.binaryPath)],
300-
arguments: try buildProduct.archiveArguments()
301-
)
302-
303-
default:
304-
let inputs = try buildProduct.objects
305-
+ buildProduct.dylibs.map { try $0.binaryPath }
306-
+ [buildProduct.linkFileListPath]
307-
+ testInputs
308-
309-
try self.manifest.addShellCmd(
310-
name: cmdName,
311-
description: "Linking \(buildProduct.binaryPath.prettyPath())",
312-
inputs: inputs.map(Node.file),
313-
outputs: [.file(buildProduct.binaryPath)],
314-
arguments: try buildProduct.linkArguments()
315-
)
316-
}
317-
318-
// Create a phony node to represent the entire target.
319-
let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig)
320-
let output: Node = .virtual(targetName)
321-
322-
self.manifest.addNode(output, toTarget: targetName)
323-
try self.manifest.addPhonyCmd(
324-
name: output.name,
325-
inputs: [.file(buildProduct.binaryPath)],
326-
outputs: [output]
327-
)
328-
329-
if self.plan.graph.reachableProducts.contains(buildProduct.product) {
330-
if buildProduct.product.type != .test {
331-
self.addNode(output, toTarget: .main)
332-
}
333-
self.addNode(output, toTarget: .test)
334-
}
335-
336-
self.manifest.addWriteLinkFileListCommand(objects: Array(buildProduct.objects), linkFileListPath: buildProduct.linkFileListPath)
337-
}
338-
}
339-
340276
extension ResolvedTarget {
341277
public func getCommandName(config: String) -> String {
342278
"C." + self.getLLBuildTargetName(config: config)
@@ -404,25 +340,6 @@ extension LLBuildManifestBuilder {
404340
}
405341
}
406342

407-
extension TypedVirtualPath {
408-
/// Resolve a typed virtual path provided by the Swift driver to
409-
/// a node in the build graph.
410-
func resolveToNode(fileSystem: FileSystem) throws -> Node {
411-
if let absolutePath = (file.absolutePath.flatMap{ AbsolutePath($0) }) {
412-
return Node.file(absolutePath)
413-
} else if let relativePath = (file.relativePath.flatMap{ RelativePath($0) }) {
414-
guard let workingDirectory: AbsolutePath = fileSystem.currentWorkingDirectory else {
415-
throw InternalError("unknown working directory")
416-
}
417-
return Node.file(workingDirectory.appending(relativePath))
418-
} else if let temporaryFileName = file.temporaryFileName {
419-
return Node.virtual(temporaryFileName.pathString)
420-
} else {
421-
throw InternalError("Cannot resolve VirtualPath: \(file)")
422-
}
423-
}
424-
}
425-
426343
extension Sequence where Element: Hashable {
427344
/// Unique the elements in a sequence.
428345
func uniqued() -> [Element] {

0 commit comments

Comments
 (0)