Skip to content

Commit d661ade

Browse files
committed
Support swift package migrate with --build-system swiftbuild
1 parent 04ebe8b commit d661ade

File tree

12 files changed

+161
-61
lines changed

12 files changed

+161
-61
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// swift-tools-version:5.8
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "ExistentialAnyMigration",
7+
targets: [
8+
.target(name: "Library", plugins: [.plugin(name: "Plugin")]),
9+
.plugin(name: "Plugin", capability: .buildTool, dependencies: ["Tool"]),
10+
.executableTarget(name: "Tool"),
11+
]
12+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import PackagePlugin
2+
import Foundation
3+
4+
@main struct Plugin: BuildToolPlugin {
5+
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
6+
let tool = try context.tool(named: "Tool")
7+
let output = context.pluginWorkDirectory.appending(["generated.swift"])
8+
return [
9+
.buildCommand(
10+
displayName: "Plugin",
11+
executable: tool.path,
12+
arguments: [output],
13+
inputFiles: [],
14+
outputFiles: [output])
15+
]
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
protocol P {
2+
}
3+
4+
func test1(_: P) {
5+
}
6+
7+
func test2(_: P.Protocol) {
8+
}
9+
10+
func test3() {
11+
let _: [P?] = []
12+
}
13+
14+
func test4() {
15+
var x = 42
16+
}
17+
18+
func bar() {
19+
generatedFunction()
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Foundation
2+
3+
@main struct Entry {
4+
public static func main() async throws {
5+
let outputPath = CommandLine.arguments[1]
6+
let contents = """
7+
func generatedFunction() {}
8+
func dontmodifyme(_: P) {}
9+
"""
10+
FileManager.default.createFile(atPath: outputPath, contents: contents.data(using: .utf8))
11+
}
12+
}

Sources/Build/BuildOperation.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,9 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
396396
}
397397

398398
/// Perform a build using the given build description and subset.
399-
public func build(subset: BuildSubset) async throws {
399+
public func build(subset: BuildSubset) async throws -> BuildResult {
400400
guard !self.config.shouldSkipBuilding(for: .target) else {
401-
return
401+
return BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("Building was skipped")))
402402
}
403403

404404
let buildStartTime = DispatchTime.now()
@@ -422,7 +422,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
422422
// any errors up-front. Returns true if we should proceed with the build
423423
// or false if not. It will already have thrown any appropriate error.
424424
guard try await self.compilePlugins(in: subset) else {
425-
return
425+
return BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("Plugin compilation failed")))
426426
}
427427

428428
let configuration = self.config.configuration(for: .target)
@@ -452,6 +452,11 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
452452
)
453453
guard success else { throw Diagnostics.fatalError }
454454

455+
var serializedDiagnosticPaths: [String: [AbsolutePath]] = [:]
456+
for module in try buildPlan.buildModules {
457+
serializedDiagnosticPaths[module.module.name, default: []].append(contentsOf: module.diagnosticFiles)
458+
}
459+
455460
// Create backwards-compatibility symlink to old build path.
456461
let oldBuildPath = self.config.dataPath(for: .target).parentDirectory.appending(
457462
component: configuration.dirname
@@ -463,7 +468,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
463468
warning: "unable to delete \(oldBuildPath), skip creating symbolic link",
464469
underlyingError: error
465470
)
466-
return
471+
return BuildResult(serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPaths))
467472
}
468473
}
469474

@@ -479,6 +484,8 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
479484
underlyingError: error
480485
)
481486
}
487+
488+
return BuildResult(serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPaths))
482489
}
483490

484491
/// Compiles any plugins specified or implied by the build subset, returning

Sources/Commands/PackageCommands/Migrate.swift

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ extension SwiftPackageCommand {
9090
features.append(feature)
9191
}
9292

93-
let targets = self.options.targets
93+
var targets = self.options.targets
9494

9595
let buildSystem = try await createBuildSystem(
9696
swiftCommandState,
@@ -100,49 +100,29 @@ extension SwiftPackageCommand {
100100

101101
// Next, let's build all of the individual targets or the
102102
// whole project to get diagnostic files.
103-
104103
print("> Starting the build")
104+
var diagnosticsPaths: [String: [AbsolutePath]] = [:]
105105
if !targets.isEmpty {
106106
for target in targets {
107-
try await buildSystem.build(subset: .target(target))
107+
let buildResult = try await buildSystem.build(subset: .target(target))
108+
diagnosticsPaths.merge(try buildResult.serializedDiagnosticPathsByTargetName.get(), uniquingKeysWith: { $0 + $1 })
108109
}
109110
} else {
110-
try await buildSystem.build(subset: .allIncludingTests)
111+
diagnosticsPaths = try await buildSystem.build(subset: .allIncludingTests).serializedDiagnosticPathsByTargetName.get()
111112
}
112113

113-
// Determine all of the targets we need up update.
114-
let buildPlan = try buildSystem.buildPlan
115-
116-
var modules: [any ModuleBuildDescription] = []
117-
if !targets.isEmpty {
118-
for buildDescription in buildPlan.buildModules
119-
where targets.contains(buildDescription.module.name) {
120-
modules.append(buildDescription)
121-
}
122-
} else {
123-
let graph = try await buildSystem.getPackageGraph()
124-
for buildDescription in buildPlan.buildModules
125-
where graph.isRootPackage(buildDescription.package)
126-
{
127-
let module = buildDescription.module
128-
guard module.type != .plugin, !module.implicit else {
129-
continue
130-
}
131-
modules.append(buildDescription)
132-
}
114+
var summary = SwiftFixIt.Summary(numberOfFixItsApplied: 0, numberOfFilesChanged: 0)
115+
let graph = try await buildSystem.getPackageGraph()
116+
if targets.isEmpty {
117+
targets = OrderedSet(graph.rootPackages.flatMap { $0.manifest.targets.filter { $0.type != .plugin }.map(\.name) })
133118
}
134-
135-
// If the build suceeded, let's extract all of the diagnostic
136-
// files from build plan and feed them to the fix-it tool.
137-
138119
print("> Applying fix-its")
139-
140-
var summary = SwiftFixIt.Summary(numberOfFixItsApplied: 0, numberOfFilesChanged: 0)
141120
let fixItDuration = try ContinuousClock().measure {
142-
for module in modules {
121+
for target in targets {
143122
let fixit = try SwiftFixIt(
144-
diagnosticFiles: module.diagnosticFiles,
123+
diagnosticFiles: Array(diagnosticsPaths[target] ?? []),
145124
categories: Set(features.flatMap(\.categories)),
125+
excludedSourceDirectories: [swiftCommandState.scratchDirectory],
146126
fileSystem: swiftCommandState.fileSystem
147127
)
148128
summary += try fixit.applyFixIts()
@@ -176,10 +156,10 @@ extension SwiftPackageCommand {
176156
// manifest with newly adopted feature settings.
177157

178158
print("> Updating manifest")
179-
for module in modules.map(\.module) {
180-
swiftCommandState.observabilityScope.emit(debug: "Adding feature(s) to '\(module.name)'")
159+
for target in targets {
160+
swiftCommandState.observabilityScope.emit(debug: "Adding feature(s) to '\(target)'")
181161
try self.updateManifest(
182-
for: module.name,
162+
for: target,
183163
add: features,
184164
using: swiftCommandState
185165
)

Sources/SPMBuildCore/BuildSystem/BuildSystem.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public protocol BuildSystem: Cancellable {
5050
/// Builds a subset of the package graph.
5151
/// - Parameters:
5252
/// - subset: The subset of the package graph to build.
53-
func build(subset: BuildSubset) async throws
53+
@discardableResult
54+
func build(subset: BuildSubset) async throws -> BuildResult
5455

5556
var buildPlan: BuildPlan { get throws }
5657

@@ -59,11 +60,20 @@ public protocol BuildSystem: Cancellable {
5960

6061
extension BuildSystem {
6162
/// Builds the default subset: all targets excluding tests.
62-
public func build() async throws {
63+
@discardableResult
64+
public func build() async throws -> BuildResult {
6365
try await build(subset: .allExcludingTests)
6466
}
6567
}
6668

69+
public struct BuildResult {
70+
package init(serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>) {
71+
self.serializedDiagnosticPathsByTargetName = serializedDiagnosticPathsByTargetName
72+
}
73+
74+
public var serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>
75+
}
76+
6777
public protocol ProductBuildDescription {
6878
/// The reference to the product.
6979
var package: ResolvedPackage { get }

Sources/SwiftBuildSupport/SwiftBuildSystem.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,22 @@ struct SessionFailedError: Error {
4141
var diagnostics: [SwiftBuild.SwiftBuildMessage.DiagnosticInfo]
4242
}
4343

44-
func withService(
44+
func withService<T>(
4545
connectionMode: SWBBuildServiceConnectionMode = .default,
4646
variant: SWBBuildServiceVariant = .default,
4747
serviceBundleURL: URL? = nil,
48-
body: @escaping (_ service: SWBBuildService) async throws -> Void
49-
) async throws {
48+
body: @escaping (_ service: SWBBuildService) async throws -> T
49+
) async throws -> T {
5050
let service = try await SWBBuildService(connectionMode: connectionMode, variant: variant, serviceBundleURL: serviceBundleURL)
51+
let result: T
5152
do {
52-
try await body(service)
53+
result = try await body(service)
5354
} catch {
5455
await service.close()
5556
throw error
5657
}
5758
await service.close()
59+
return result
5860
}
5961

6062
public func createSession(
@@ -279,20 +281,20 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
279281
SwiftLanguageVersion.supportedSwiftLanguageVersions
280282
}
281283

282-
public func build(subset: BuildSubset) async throws {
284+
public func build(subset: BuildSubset) async throws -> BuildResult {
283285
guard !buildParameters.shouldSkipBuilding else {
284-
return
286+
return BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("Building was skipped")))
285287
}
286288

287289
try await writePIF(buildParameters: buildParameters)
288290

289-
try await startSWBuildOperation(pifTargetName: subset.pifTargetName)
291+
return try await startSWBuildOperation(pifTargetName: subset.pifTargetName)
290292
}
291293

292-
private func startSWBuildOperation(pifTargetName: String) async throws {
294+
private func startSWBuildOperation(pifTargetName: String) async throws -> BuildResult {
293295
let buildStartTime = ContinuousClock.Instant.now
294296

295-
try await withService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint)) { service in
297+
return try await withService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint)) { service in
296298
let derivedDataPath = self.buildParameters.dataPath
297299

298300
let progressAnimation = ProgressAnimation.percent(
@@ -302,6 +304,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
302304
isColorized: self.buildParameters.outputParameters.isColorized
303305
)
304306

307+
var serializedDiagnosticPathsByTargetName: [String: [Basics.AbsolutePath]] = [:]
305308
do {
306309
try await withSession(service: service, name: self.buildParameters.pifManifest.pathString, toolchainPath: self.buildParameters.toolchain.toolchainDir, packageManagerResourcesDirectory: self.packageManagerResourcesDirectory) { session, _ in
307310
self.outputStream.send("Building for \(self.buildParameters.configuration == .debug ? "debugging" : "production")...\n")
@@ -451,6 +454,11 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
451454
}
452455
let targetInfo = try buildState.target(for: startedInfo)
453456
self.delegate?.buildSystem(self, didFinishCommand: BuildSystemCommand(startedInfo, targetInfo: targetInfo))
457+
if let targetName = targetInfo?.targetName {
458+
serializedDiagnosticPathsByTargetName[targetName, default: []].append(contentsOf: startedInfo.serializedDiagnosticsPaths.compactMap {
459+
try? Basics.AbsolutePath(validating: $0.pathString)
460+
})
461+
}
454462
case .targetStarted(let info):
455463
try buildState.started(target: info)
456464
case .planningOperationStarted, .planningOperationCompleted, .reportBuildDescription, .reportPathMap, .preparedForIndex, .backtraceFrame, .buildStarted, .preparationComplete, .targetUpToDate, .targetComplete, .taskUpToDate:
@@ -503,6 +511,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
503511
} catch {
504512
throw error
505513
}
514+
return BuildResult(serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPathsByTargetName))
506515
}
507516
}
508517

Sources/SwiftFixIt/SwiftFixIt.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ private struct PrimaryDiagnosticFilter<Diagnostic: AnyDiagnostic>: ~Copyable {
124124
private var uniquePrimaryDiagnostics: Set<DiagnosticID> = []
125125

126126
let categories: Set<String>
127+
let excludedSourceDirectories: Set<AbsolutePath>
127128

128-
init(categories: Set<String>) {
129+
init(categories: Set<String>, excludedSourceDirectories: Set<AbsolutePath>) {
129130
self.categories = categories
131+
self.excludedSourceDirectories = excludedSourceDirectories
130132
}
131133

132134
/// Returns a Boolean value indicating whether to skip the given primary
@@ -153,6 +155,13 @@ private struct PrimaryDiagnosticFilter<Diagnostic: AnyDiagnostic>: ~Copyable {
153155
}
154156
}
155157

158+
// Skip if the source file the diagnostic appears in is in an excluded directory.
159+
if let sourceFilePath = try? diagnostic.location.map({ try AbsolutePath(validating: $0.filename) }) {
160+
guard !self.excludedSourceDirectories.contains(where: { $0.isAncestor(of: sourceFilePath) }) else {
161+
return true
162+
}
163+
}
164+
156165
let notes = primaryDiagnosticWithNotes.dropFirst()
157166
precondition(notes.allSatisfy(\.isNote))
158167

@@ -201,6 +210,7 @@ package struct SwiftFixIt /*: ~Copyable */ { // TODO: Crashes with ~Copyable
201210
package init(
202211
diagnosticFiles: [AbsolutePath],
203212
categories: Set<String> = [],
213+
excludedSourceDirectories: Set<AbsolutePath> = [],
204214
fileSystem: any FileSystem
205215
) throws {
206216
// Deserialize the diagnostics.
@@ -212,18 +222,20 @@ package struct SwiftFixIt /*: ~Copyable */ { // TODO: Crashes with ~Copyable
212222
self = try SwiftFixIt(
213223
diagnostics: diagnostics,
214224
categories: categories,
225+
excludedSourceDirectories: excludedSourceDirectories,
215226
fileSystem: fileSystem
216227
)
217228
}
218229

219230
init<Diagnostic: AnyDiagnostic>(
220231
diagnostics: some Collection<Diagnostic>,
221232
categories: Set<String>,
233+
excludedSourceDirectories: Set<AbsolutePath>,
222234
fileSystem: any FileSystem
223235
) throws {
224236
self.fileSystem = fileSystem
225237

226-
var filter = PrimaryDiagnosticFilter<Diagnostic>(categories: categories)
238+
var filter = PrimaryDiagnosticFilter<Diagnostic>(categories: categories, excludedSourceDirectories: excludedSourceDirectories)
227239
_ = consume categories
228240

229241
// Build a map from source files to `SwiftDiagnostics` diagnostics.

Sources/XCBuildSupport/XcodeBuildSystem.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem {
160160
return []
161161
}
162162

163-
public func build(subset: BuildSubset) async throws {
163+
public func build(subset: BuildSubset) async throws -> BuildResult {
164164
guard !buildParameters.shouldSkipBuilding else {
165-
return
165+
return BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("XCBuild does not support reporting serialized diagnostics.")))
166166
}
167167

168168
let pifBuilder = try await getPIFBuilder()
@@ -244,9 +244,12 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem {
244244
throw Diagnostics.fatalError
245245
}
246246

247-
guard !self.logLevel.isQuiet else { return }
248-
self.outputStream.send("Build complete!\n")
249-
self.outputStream.flush()
247+
if !logLevel.isQuiet {
248+
self.outputStream.send("Build complete!\n")
249+
self.outputStream.flush()
250+
}
251+
252+
return BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("XCBuild does not support reporting serialized diagnostics.")))
250253
}
251254

252255
func createBuildParametersFile() throws -> AbsolutePath {

0 commit comments

Comments
 (0)