Skip to content

Commit 3fbec0b

Browse files
authored
Run Command: Add SwiftBuild repl support (#8971)
The `swift run --repl` was explicitly using the Native build system. This change modified the `swift run --repl` command to respect the `--build-system <arg>` command line option. The change introduces a new build output request `replArguments`. When provided, the build system is responsible for providing the arguments required for the REPL in the BuildResult. The Swift Run Command will inspect the build result for this property. If it's unavailable, the command provides an error indicating repl support is unavailable. A caveat, more work is required to get proper REPL integration with the Package. At the moment, the System Library paths are not provided by the SwiftBuild System when a repl session is requested via the `run` command. Relates to: #8846 issue: rdar://153822861 Depends on : #8942
1 parent 5398153 commit 3fbec0b

File tree

16 files changed

+1237
-649
lines changed

16 files changed

+1237
-649
lines changed

Sources/Build/BuildOperation.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ import SwiftDriver
3737
#endif
3838

3939
package struct LLBuildSystemConfiguration {
40-
let toolsBuildParameters: BuildParameters
41-
let destinationBuildParameters: BuildParameters
40+
fileprivate let toolsBuildParameters: BuildParameters
41+
fileprivate let destinationBuildParameters: BuildParameters
4242

4343
let scratchDirectory: AbsolutePath
4444

@@ -397,7 +397,10 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
397397

398398
/// Perform a build using the given build description and subset.
399399
public func build(subset: BuildSubset, buildOutputs: [BuildOutput]) async throws -> BuildResult {
400-
var result = BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("Building was skipped")))
400+
var result = BuildResult(
401+
serializedDiagnosticPathsByTargetName: .failure(StringError("Building was skipped")),
402+
replArguments: nil,
403+
)
401404

402405
guard !self.config.shouldSkipBuilding(for: .target) else {
403406
return result
@@ -455,10 +458,15 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
455458
)
456459
guard success else { throw Diagnostics.fatalError }
457460

458-
if buildOutputs.contains(.buildPlan) {
459-
result.buildPlan = try buildPlan
460-
}
461+
let buildResultBuildPlan = buildOutputs.contains(.buildPlan) ? try buildPlan : nil
462+
let buildResultReplArgs = buildOutputs.contains(.replArguments) ? try buildPlan.createREPLArguments() : nil
461463

464+
result = BuildResult(
465+
serializedDiagnosticPathsByTargetName: result.serializedDiagnosticPathsByTargetName,
466+
symbolGraph: result.symbolGraph,
467+
buildPlan: buildResultBuildPlan,
468+
replArguments: buildResultReplArgs,
469+
)
462470
var serializedDiagnosticPaths: [String: [AbsolutePath]] = [:]
463471
do {
464472
for module in try buildPlan.buildModules {
@@ -701,7 +709,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
701709

702710
// Create the build plan based on the modules graph and any information from plugins.
703711
return try await BuildPlan(
704-
destinationBuildParameters: self.config.destinationBuildParameters,
712+
destinationBuildParameters: self.config.buildParameters(for: .target),
705713
toolsBuildParameters: self.config.buildParameters(for: .host),
706714
graph: graph,
707715
pluginConfiguration: self.pluginConfiguration,

Sources/Build/BuildPlan/BuildPlan.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,13 +637,12 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
637637

638638
/// Creates arguments required to launch the Swift REPL that will allow
639639
/// importing the modules in the package graph.
640-
public func createREPLArguments() throws -> [String] {
640+
public func createREPLArguments() throws -> CLIArguments {
641641
let buildPath = self.toolsBuildParameters.buildPath.pathString
642642
var arguments = ["repl", "-I" + buildPath, "-L" + buildPath]
643643

644644
// Link the special REPL product that contains all of the library targets.
645-
let replProductName = self.graph.rootPackages[self.graph.rootPackages.startIndex].identity.description +
646-
Product.replProductSuffix
645+
let replProductName = try self.graph.getReplProductName()
647646
arguments.append("-l" + replProductName)
648647

649648
// The graph should have the REPL product.

Sources/Commands/SwiftRunCommand.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,24 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
134134
// Construct the build operation.
135135
// FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the REPL. rdar://86112934
136136
let buildSystem = try await swiftCommandState.createBuildSystem(
137-
explicitBuildSystem: .native,
138137
cacheBuildManifest: false,
139138
packageGraphLoader: asyncUnsafeGraphLoader
140139
)
141140

142141
// Perform build.
143-
let buildResult = try await buildSystem.build(subset: .allExcludingTests, buildOutputs: [.buildPlan])
144-
guard let buildPlan = buildResult.buildPlan else {
142+
let buildResult = try await buildSystem.build(subset: .allExcludingTests, buildOutputs: [.replArguments])
143+
guard let arguments = buildResult.replArguments else {
144+
swiftCommandState.observabilityScope.emit(error: "\(globalOptions.build.buildSystem) build system does not support this command")
145145
throw ExitCode.failure
146146
}
147147

148148
// Execute the REPL.
149-
let arguments = try buildPlan.createREPLArguments()
150-
print("Launching Swift REPL with arguments: \(arguments.joined(separator: " "))")
149+
let interpreterPath = try swiftCommandState.getTargetToolchain().swiftInterpreterPath
150+
swiftCommandState.outputStream.send("Launching Swift (interpreter at \(interpreterPath)) REPL with arguments: \(arguments.joined(separator: " "))\n")
151+
swiftCommandState.outputStream.flush()
151152
try self.run(
152153
fileSystem: swiftCommandState.fileSystem,
153-
executablePath: swiftCommandState.getTargetToolchain().swiftInterpreterPath,
154+
executablePath: interpreterPath,
154155
originalWorkingDirectory: swiftCommandState.originalWorkingDirectory,
155156
arguments: arguments
156157
)
@@ -197,7 +198,7 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
197198
case .run:
198199
// Detect deprecated uses of swift run to interpret scripts.
199200
if let executable = options.executable, try isValidSwiftFilePath(fileSystem: swiftCommandState.fileSystem, path: executable) {
200-
swiftCommandState.observabilityScope.emit(.runFileDeprecation)
201+
swiftCommandState.observabilityScope.emit(.runFileDeprecation(filePath: executable))
201202
// Redirect execution to the toolchain's swift executable.
202203
let swiftInterpreterPath = try swiftCommandState.getTargetToolchain().swiftInterpreterPath
203204
// Prepend the script to interpret to the arguments.
@@ -364,8 +365,8 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
364365
}
365366

366367
private extension Basics.Diagnostic {
367-
static var runFileDeprecation: Self {
368-
.warning("'swift run file.swift' command to interpret swift files is deprecated; use 'swift file.swift' instead")
368+
static func runFileDeprecation(filePath: String) -> Self {
369+
.warning("'swift run \(filePath)' command to interpret swift files is deprecated; use 'swift \(filePath)' instead")
369370
}
370371
}
371372

Sources/PackageGraph/ModulesGraph.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,14 @@ public struct ModulesGraph {
285285

286286
return result
287287
}
288+
289+
public func getReplProductName() throws -> String {
290+
if self.rootPackages.isEmpty {
291+
throw StringError("Root package does not exist.")
292+
}
293+
return self.rootPackages[self.rootPackages.startIndex].identity.description +
294+
Product.replProductSuffix
295+
}
288296
}
289297

290298
extension PackageGraphError: CustomStringConvertible {

Sources/SPMBuildCore/BuildSystem/BuildSystem.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public enum BuildOutput {
4747
// "-emit-extension-block-symbols"
4848
// "-emit-synthesized-members"
4949
case buildPlan
50+
case replArguments
5051
}
5152

5253
/// A protocol that represents a build system used by SwiftPM for all build operations. This allows factoring out the
@@ -91,15 +92,25 @@ public struct SymbolGraphResult {
9192
public let outputLocationForTarget: (String, BuildParameters) -> [String]
9293
}
9394

95+
public typealias CLIArguments = [String]
96+
9497
public struct BuildResult {
95-
package init(serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>, symbolGraph: SymbolGraphResult? = nil, buildPlan: BuildPlan? = nil) {
98+
package init(
99+
serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>,
100+
symbolGraph: SymbolGraphResult? = nil,
101+
buildPlan: BuildPlan? = nil,
102+
replArguments: CLIArguments?
103+
) {
96104
self.serializedDiagnosticPathsByTargetName = serializedDiagnosticPathsByTargetName
97105
self.symbolGraph = symbolGraph
98106
self.buildPlan = buildPlan
107+
self.replArguments = replArguments
99108
}
100109

101-
public var symbolGraph: SymbolGraphResult?
102-
public var buildPlan: BuildPlan?
110+
public let replArguments: CLIArguments?
111+
public let symbolGraph: SymbolGraphResult?
112+
public let buildPlan: BuildPlan?
113+
103114
public var serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>
104115
}
105116

Sources/SwiftBuildSupport/SwiftBuildSystem.swift

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,24 +276,95 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
276276
self.delegate = delegate
277277
}
278278

279+
private func createREPLArguments(
280+
session: SWBBuildServiceSession,
281+
request: SWBBuildRequest
282+
) async throws -> CLIArguments {
283+
self.outputStream.send("Gathering repl arguments...")
284+
self.outputStream.flush()
285+
286+
func getUniqueBuildSettingsIncludingDependencies(of targetGuid: [SWBConfiguredTarget], buildSettings: [String]) async throws -> Set<String> {
287+
let dependencyGraph = try await session.computeDependencyGraph(
288+
targetGUIDs: request.configuredTargets.map { SWBTargetGUID(rawValue: $0.guid)},
289+
buildParameters: request.parameters,
290+
includeImplicitDependencies: true,
291+
)
292+
var uniquePaths = Set<String>()
293+
for setting in buildSettings {
294+
self.outputStream.send(".")
295+
self.outputStream.flush()
296+
for (target, targetDependencies) in dependencyGraph {
297+
for t in [target] + targetDependencies {
298+
try await session.evaluateMacroAsStringList(
299+
setting,
300+
level: .target(t.rawValue),
301+
buildParameters: request.parameters,
302+
overrides: nil,
303+
).forEach({
304+
uniquePaths.insert($0)
305+
})
306+
}
307+
}
308+
309+
}
310+
return uniquePaths
311+
}
312+
313+
// TODO: Need to determine how to get the inlude path of package system library dependencies
314+
let includePaths = try await getUniqueBuildSettingsIncludingDependencies(
315+
of: request.configuredTargets,
316+
buildSettings: [
317+
"BUILT_PRODUCTS_DIR",
318+
"HEADER_SEARCH_PATHS",
319+
"USER_HEADER_SEARCH_PATHS",
320+
"FRAMEWORK_SEARCH_PATHS",
321+
]
322+
)
323+
324+
let graph = try await self.getPackageGraph()
325+
// Link the special REPL product that contains all of the library targets.
326+
let replProductName: String = try graph.getReplProductName()
327+
328+
// The graph should have the REPL product.
329+
assert(graph.product(for: replProductName) != nil)
330+
331+
let arguments = ["repl", "-l\(replProductName)"] + includePaths.map {
332+
"-I\($0)"
333+
}
334+
335+
self.outputStream.send("Done.\n")
336+
return arguments
337+
}
338+
279339
private func supportedSwiftVersions() throws -> [SwiftLanguageVersion] {
280340
// Swift Build should support any of the supported language versions of SwiftPM and the rest of the toolchain
281341
SwiftLanguageVersion.supportedSwiftLanguageVersions
282342
}
283343

284344
public func build(subset: BuildSubset, buildOutputs: [BuildOutput]) async throws -> BuildResult {
285345
guard !buildParameters.shouldSkipBuilding else {
286-
return BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("Building was skipped")))
346+
return BuildResult(
347+
serializedDiagnosticPathsByTargetName: .failure(StringError("Building was skipped")),
348+
replArguments: nil,
349+
)
287350
}
288351

289352
try await writePIF(buildParameters: buildParameters)
290353

291-
return try await startSWBuildOperation(pifTargetName: subset.pifTargetName, genSymbolGraph: buildOutputs.contains(.symbolGraph))
354+
return try await startSWBuildOperation(
355+
pifTargetName: subset.pifTargetName,
356+
genSymbolGraph: buildOutputs.contains(.symbolGraph),
357+
generateReplArguments: buildOutputs.contains(.replArguments),
358+
)
292359
}
293360

294-
private func startSWBuildOperation(pifTargetName: String, genSymbolGraph: Bool) async throws -> BuildResult {
361+
private func startSWBuildOperation(
362+
pifTargetName: String,
363+
genSymbolGraph: Bool,
364+
generateReplArguments: Bool
365+
) async throws -> BuildResult {
295366
let buildStartTime = ContinuousClock.Instant.now
296-
367+
var replArguments: CLIArguments?
297368
return try await withService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint)) { service in
298369
let derivedDataPath = self.buildParameters.dataPath
299370

@@ -418,7 +489,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
418489
case .note: .info
419490
case .remark: .debug
420491
}
421-
self.observabilityScope.emit(severity: severity, message: message)
492+
self.observabilityScope.emit(severity: severity, message: "\(message)\n")
422493

423494
for childDiagnostic in info.childDiagnostics {
424495
emitInfoAsDiagnostic(info: childDiagnostic)
@@ -502,6 +573,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
502573
self.observabilityScope.emit(error: "Unexpected build state")
503574
throw Diagnostics.fatalError
504575
}
576+
577+
replArguments = generateReplArguments ? try await self.createREPLArguments(session: session, request: request) : nil
505578
}
506579
} catch let sessError as SessionFailedError {
507580
for diagnostic in sessError.diagnostics {
@@ -512,9 +585,15 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
512585
throw error
513586
}
514587

515-
return BuildResult(serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPathsByTargetName), symbolGraph: SymbolGraphResult(outputLocationForTarget: { target, buildParameters in
516-
return ["\(buildParameters.triple.archName)", "\(target).symbolgraphs"]
517-
}))
588+
return BuildResult(
589+
serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPathsByTargetName),
590+
symbolGraph: SymbolGraphResult(
591+
outputLocationForTarget: { target, buildParameters in
592+
return ["\(buildParameters.triple.archName)", "\(target).symbolgraphs"]
593+
}
594+
),
595+
replArguments: replArguments,
596+
)
518597
}
519598
}
520599

Sources/XCBuildSupport/XcodeBuildSystem.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem {
161161
}
162162

163163
public func build(subset: BuildSubset, buildOutputs: [BuildOutput]) async throws -> BuildResult {
164-
let buildResult = BuildResult(serializedDiagnosticPathsByTargetName: .failure(StringError("XCBuild does not support reporting serialized diagnostics.")))
164+
let buildResult = BuildResult(
165+
serializedDiagnosticPathsByTargetName: .failure(StringError("XCBuild does not support reporting serialized diagnostics.")),
166+
replArguments: nil,
167+
)
165168

166169
guard !buildParameters.shouldSkipBuilding else {
167170
return buildResult

0 commit comments

Comments
 (0)