Skip to content

Commit 7046c68

Browse files
authored
Use SwiftBuild API to reliably compute built artifacts (#9143)
Depends on swiftlang/swift-build#801 By querying the build system for the artifacts, we ensure we only report any we will actually produce in the current build. At the same time, pull in an alternative fix for #9121 which restores the original behavior of the native build system when considering products in dependency packages.
1 parent 84c6db8 commit 7046c68

File tree

11 files changed

+268
-104
lines changed

11 files changed

+268
-104
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "Dep",
7+
products: [
8+
.library(
9+
name: "MyDynamicLibrary",
10+
type: .dynamic,
11+
targets: ["MyDynamicLibrary"]
12+
),
13+
.executable(
14+
name: "MySupportExecutable",
15+
targets: ["MySupportExecutable"]
16+
)
17+
],
18+
targets: [
19+
.target(
20+
name: "MyDynamicLibrary"
21+
),
22+
.executableTarget(
23+
name: "MySupportExecutable",
24+
dependencies: ["MyDynamicLibrary"]
25+
)
26+
]
27+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public func sayHello() {
2+
print("hello!")
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import MyDynamicLibrary
2+
3+
@main struct Entry {
4+
static func main() {
5+
print("running support tool")
6+
sayHello()
7+
}
8+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "PartiallyUnusedDependency",
7+
products: [
8+
.executable(
9+
name: "MyExecutable",
10+
targets: ["MyExecutable"]
11+
),
12+
],
13+
dependencies: [
14+
.package(path: "Dep")
15+
],
16+
targets: [
17+
.executableTarget(
18+
name: "MyExecutable",
19+
dependencies: [.product(name: "MyDynamicLibrary", package: "Dep")]
20+
),
21+
.plugin(
22+
name: "dump-artifacts-plugin",
23+
capability: .command(
24+
intent: .custom(verb: "dump-artifacts-plugin", description: "Dump Artifacts"),
25+
permissions: []
26+
)
27+
)
28+
]
29+
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import PackagePlugin
2+
3+
@main
4+
struct DumpArtifactsPlugin: CommandPlugin {
5+
func performCommand(
6+
context: PluginContext,
7+
arguments: [String]
8+
) throws {
9+
do {
10+
var parameters = PackageManager.BuildParameters()
11+
parameters.configuration = .debug
12+
parameters.logging = .concise
13+
let result = try packageManager.build(.all(includingTests: false), parameters: parameters)
14+
print("succeeded: \(result.succeeded)")
15+
for artifact in result.builtArtifacts {
16+
print("artifact-path: \(artifact.path.string)")
17+
print("artifact-kind: \(artifact.kind)")
18+
}
19+
}
20+
catch {
21+
print("error from the plugin host: \\(error)")
22+
}
23+
}
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import MyDynamicLibrary
2+
3+
@main struct Entry {
4+
static func main() {
5+
print("Hello, world!")
6+
sayHello()
7+
}
8+
}

Sources/Build/BuildOperation.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,11 +461,37 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
461461
let buildResultBuildPlan = buildOutputs.contains(.buildPlan) ? try buildPlan : nil
462462
let buildResultReplArgs = buildOutputs.contains(.replArguments) ? try buildPlan.createREPLArguments() : nil
463463

464+
let artifacts: [(String, PluginInvocationBuildResult.BuiltArtifact)]?
465+
if buildOutputs.contains(.builtArtifacts) {
466+
let builtProducts = try buildPlan.buildProducts
467+
artifacts = try builtProducts.compactMap {
468+
switch $0.product.type {
469+
case .library(let kind):
470+
let artifactKind: PluginInvocationBuildResult.BuiltArtifact.Kind
471+
switch kind {
472+
case .dynamic: artifactKind = .dynamicLibrary
473+
case .static, .automatic: artifactKind = .staticLibrary
474+
}
475+
return try ($0.product.name, .init(
476+
path: $0.binaryPath.pathString,
477+
kind: artifactKind)
478+
)
479+
case .executable:
480+
return try ($0.product.name, .init(path: $0.binaryPath.pathString, kind: .executable))
481+
default:
482+
return nil
483+
}
484+
}
485+
} else {
486+
artifacts = nil
487+
}
488+
464489
result = BuildResult(
465490
serializedDiagnosticPathsByTargetName: result.serializedDiagnosticPathsByTargetName,
466491
symbolGraph: result.symbolGraph,
467492
buildPlan: buildResultBuildPlan,
468493
replArguments: buildResultReplArgs,
494+
builtArtifacts: artifacts
469495
)
470496
var serializedDiagnosticPaths: [String: [AbsolutePath]] = [:]
471497
do {

Sources/Commands/Utilities/PluginDelegate.swift

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -178,40 +178,21 @@ final class PluginDelegate: PluginInvocationDelegate {
178178
)
179179

180180
// Run the build. This doesn't return until the build is complete.
181-
let success = await buildSystem.buildIgnoringError(subset: buildSubset)
181+
let result = await buildSystem.buildIgnoringError(subset: buildSubset, buildOutputs: [.builtArtifacts])
182+
let success = result != nil
182183

183184
let packageGraph = try await buildSystem.getPackageGraph()
184185

185-
var builtArtifacts: [PluginInvocationBuildResult.BuiltArtifact] = []
186-
187-
for rootPkg in packageGraph.rootPackages {
188-
let builtProducts = rootPkg.products.filter {
189-
switch subset {
190-
case .all(let includingTests):
191-
return includingTests ? true : $0.type != .test
192-
case .product(let name):
193-
return $0.name == name
194-
case .target(let name):
195-
return $0.name == name
196-
}
197-
}
198-
199-
let artifacts: [PluginInvocationBuildResult.BuiltArtifact] = try builtProducts.compactMap {
200-
switch $0.type {
201-
case .library(let kind):
202-
return .init(
203-
path: try buildParameters.binaryPath(for: $0).pathString,
204-
kind: (kind == .dynamic) ? .dynamicLibrary : .staticLibrary
205-
)
206-
case .executable:
207-
return .init(path: try buildParameters.binaryPath(for: $0).pathString, kind: .executable)
208-
default:
209-
return nil
210-
}
186+
var builtArtifacts: [PluginInvocationBuildResult.BuiltArtifact] = (result?.builtArtifacts ?? []).filter { (name, _) in
187+
switch subset {
188+
case .all(let includingTests):
189+
return true
190+
case .product(let productName):
191+
return name == productName
192+
case .target(let targetName):
193+
return name == targetName
211194
}
212-
213-
builtArtifacts.append(contentsOf: artifacts)
214-
}
195+
}.map(\.1)
215196

216197
return PluginInvocationBuildResult(
217198
succeeded: success,
@@ -495,12 +476,11 @@ final class PluginDelegate: PluginInvocationDelegate {
495476
}
496477

497478
extension BuildSystem {
498-
fileprivate func buildIgnoringError(subset: BuildSubset) async -> Bool {
479+
fileprivate func buildIgnoringError(subset: BuildSubset, buildOutputs: [BuildOutput]) async -> BuildResult? {
499480
do {
500-
try await self.build(subset: subset, buildOutputs: [])
501-
return true
481+
return try await self.build(subset: subset, buildOutputs: buildOutputs)
502482
} catch {
503-
return false
483+
return nil
504484
}
505485
}
506486
}
@@ -533,4 +513,4 @@ fileprivate extension BuildOutput.SymbolGraphAccessLevel {
533513
.open
534514
}
535515
}
536-
}
516+
}

Sources/SPMBuildCore/BuildSystem/BuildSystem.swift

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,6 @@ public enum BuildSubset {
3939
/// build systems can produce all possible build outputs. Check the build
4040
/// result for indication that the output was produced.
4141
public enum BuildOutput: Equatable {
42-
public static func == (lhs: BuildOutput, rhs: BuildOutput) -> Bool {
43-
switch lhs {
44-
case .symbolGraph(let leftOptions):
45-
switch rhs {
46-
case .symbolGraph(let rightOptions):
47-
return leftOptions == rightOptions
48-
default:
49-
return false
50-
}
51-
case .buildPlan:
52-
switch rhs {
53-
case .buildPlan:
54-
return true
55-
default:
56-
return false
57-
}
58-
case .replArguments:
59-
switch rhs {
60-
case .replArguments:
61-
return true
62-
default:
63-
return false
64-
}
65-
}
66-
}
67-
6842
public enum SymbolGraphAccessLevel: String {
6943
case `private`, `fileprivate`, `internal`, `package`, `public`, `open`
7044
}
@@ -93,6 +67,7 @@ public enum BuildOutput: Equatable {
9367
case symbolGraph(SymbolGraphOptions)
9468
case buildPlan
9569
case replArguments
70+
case builtArtifacts
9671
}
9772

9873
/// A protocol that represents a build system used by SwiftPM for all build operations. This allows factoring out the
@@ -144,19 +119,22 @@ public struct BuildResult {
144119
serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>,
145120
symbolGraph: SymbolGraphResult? = nil,
146121
buildPlan: BuildPlan? = nil,
147-
replArguments: CLIArguments?
122+
replArguments: CLIArguments?,
123+
builtArtifacts: [(String, PluginInvocationBuildResult.BuiltArtifact)]? = nil
148124
) {
149125
self.serializedDiagnosticPathsByTargetName = serializedDiagnosticPathsByTargetName
150126
self.symbolGraph = symbolGraph
151127
self.buildPlan = buildPlan
152128
self.replArguments = replArguments
129+
self.builtArtifacts = builtArtifacts
153130
}
154131

155132
public let replArguments: CLIArguments?
156133
public let symbolGraph: SymbolGraphResult?
157134
public let buildPlan: BuildPlan?
158135

159136
public var serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>
137+
public var builtArtifacts: [(String, PluginInvocationBuildResult.BuiltArtifact)]?
160138
}
161139

162140
public protocol ProductBuildDescription {

0 commit comments

Comments
 (0)