Skip to content

Commit 3f9e6ca

Browse files
committed
Run Command: Add SwiftBuild repl support
The `swift run --repl` was explicitely 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 repsult 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 integation 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. Fixes: #8846 issue: rdar://153822861
1 parent 5c57a39 commit 3f9e6ca

File tree

16 files changed

+1245
-649
lines changed

16 files changed

+1245
-649
lines changed

Sources/Build/BuildOperation.swift

Lines changed: 17 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,11 @@ 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+
packageGraph: try await self.getPackageGraph(),
403+
replArguments: nil,
404+
)
401405

402406
guard !self.config.shouldSkipBuilding(for: .target) else {
403407
return result
@@ -455,10 +459,16 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
455459
)
456460
guard success else { throw Diagnostics.fatalError }
457461

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

465+
result = BuildResult(
466+
serializedDiagnosticPathsByTargetName: result.serializedDiagnosticPathsByTargetName,
467+
packageGraph: result.packageGraph,
468+
symbolGraph: result.symbolGraph,
469+
buildPlan: buildResultBuildPlan,
470+
replArguments: buildResultReplArgs,
471+
)
462472
var serializedDiagnosticPaths: [String: [AbsolutePath]] = [:]
463473
do {
464474
for module in try buildPlan.buildModules {
@@ -701,7 +711,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
701711

702712
// Create the build plan based on the modules graph and any information from plugins.
703713
return try await BuildPlan(
704-
destinationBuildParameters: self.config.destinationBuildParameters,
714+
destinationBuildParameters: self.config.buildParameters(for: .target),
705715
toolsBuildParameters: self.config.buildParameters(for: .host),
706716
graph: graph,
707717
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: 17 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,28 @@ 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+
packageGraph: ModulesGraph,
101+
symbolGraph: SymbolGraphResult? = nil,
102+
buildPlan: BuildPlan? = nil,
103+
replArguments: CLIArguments?
104+
) {
96105
self.serializedDiagnosticPathsByTargetName = serializedDiagnosticPathsByTargetName
106+
self.packageGraph = packageGraph
97107
self.symbolGraph = symbolGraph
98108
self.buildPlan = buildPlan
109+
self.replArguments = replArguments
99110
}
100111

101-
public var symbolGraph: SymbolGraphResult?
102-
public var buildPlan: BuildPlan?
112+
public let replArguments: CLIArguments?
113+
public let packageGraph: ModulesGraph
114+
public let symbolGraph: SymbolGraphResult?
115+
public let buildPlan: BuildPlan?
116+
103117
public var serializedDiagnosticPathsByTargetName: Result<[String: [AbsolutePath]], Error>
104118
}
105119

Sources/SwiftBuildSupport/SwiftBuildSystem.swift

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,24 +276,96 @@ 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+
packageGraph: try await self.getPackageGraph(),
349+
replArguments: nil,
350+
)
287351
}
288352

289353
try await writePIF(buildParameters: buildParameters)
290354

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

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

@@ -418,7 +490,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
418490
case .note: .info
419491
case .remark: .debug
420492
}
421-
self.observabilityScope.emit(severity: severity, message: message)
493+
self.observabilityScope.emit(severity: severity, message: "\(message)\n")
422494

423495
for childDiagnostic in info.childDiagnostics {
424496
emitInfoAsDiagnostic(info: childDiagnostic)
@@ -502,6 +574,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
502574
self.observabilityScope.emit(error: "Unexpected build state")
503575
throw Diagnostics.fatalError
504576
}
577+
578+
replArguments = generateReplArguments ? try await self.createREPLArguments(session: session, request: request) : nil
505579
}
506580
} catch let sessError as SessionFailedError {
507581
for diagnostic in sessError.diagnostics {
@@ -512,9 +586,16 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
512586
throw error
513587
}
514588

515-
return BuildResult(serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPathsByTargetName), symbolGraph: SymbolGraphResult(outputLocationForTarget: { target, buildParameters in
516-
return ["\(buildParameters.triple.archName)", "\(target).symbolgraphs"]
517-
}))
589+
return BuildResult(
590+
serializedDiagnosticPathsByTargetName: .success(serializedDiagnosticPathsByTargetName),
591+
packageGraph: try await self.getPackageGraph(),
592+
symbolGraph: SymbolGraphResult(
593+
outputLocationForTarget: { target, buildParameters in
594+
return ["\(buildParameters.triple.archName)", "\(target).symbolgraphs"]
595+
}
596+
),
597+
replArguments: replArguments,
598+
)
518599
}
519600
}
520601

Sources/XCBuildSupport/XcodeBuildSystem.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@ 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+
packageGraph: try await self.getPackageGraph(),
167+
replArguments: nil,
168+
)
165169

166170
guard !buildParameters.shouldSkipBuilding else {
167171
return buildResult

0 commit comments

Comments
 (0)