Skip to content

Commit b43f345

Browse files
committed
Use SwiftBuild API to reliably compute built artifacts
1 parent ab60142 commit b43f345

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)