Skip to content

Commit 5f5ae14

Browse files
authored
Merge pull request #43 from owenv/repl
Add REPL support
2 parents 4efb328 + fe66145 commit 5f5ae14

File tree

12 files changed

+183
-15
lines changed

12 files changed

+183
-15
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ extension Driver {
575575
return
576576
}
577577

578+
if jobs.contains(where: { $0.requiresInPlaceExecution }) {
579+
assert(jobs.count == 1, "Cannot execute in place for multi-job build plans")
580+
return try executeJobInPlace(jobs[0], resolver: resolver)
581+
}
582+
578583
// Create and use the tool execution delegate if one is not provided explicitly.
579584
let executorDelegate = executorDelegate ?? createToolExecutionDelegate()
580585

@@ -606,6 +611,19 @@ extension Driver {
606611

607612
return ToolExecutionDelegate(mode: mode)
608613
}
614+
615+
/// Execute a single job in-place.
616+
private func executeJobInPlace(_ job: Job, resolver: ArgsResolver) throws {
617+
let tool = try resolver.resolve(.path(job.tool))
618+
let commandLine = try job.commandLine.map{ try resolver.resolve($0) }
619+
let arguments = [tool] + commandLine
620+
621+
for (envVar, value) in job.extraEnvironment {
622+
try ProcessEnv.setVar(envVar, value: value)
623+
}
624+
625+
return try exec(path: tool, args: arguments)
626+
}
609627
}
610628

611629
extension Diagnostic.Message {

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ extension Array where Element == Job.ArgTemplate {
311311
)
312312
appendFlag(isTrue ? trueFlag : falseFlag)
313313
}
314+
315+
var joinedArguments: String {
316+
return self.map {
317+
switch $0 {
318+
case .flag(let string):
319+
return string.spm_shellEscaped()
320+
case .path(let path):
321+
return path.name.spm_shellEscaped()
322+
}
323+
}.joined(separator: " ")
324+
}
314325
}
315326

316327

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ extension Driver {
160160
commandLine.appendPath(importedObjCHeader)
161161
}
162162

163-
commandLine.appendFlags("-module-name", moduleName)
163+
// Repl Jobs may include -module-name depending on the selected REPL (LLDB or integrated).
164+
if compilerMode != .repl {
165+
commandLine.appendFlags("-module-name", moduleName)
166+
}
164167
}
165168

166169
mutating func addFrontendSupplementaryOutputArguments(commandLine: inout [Job.ArgTemplate], primaryInputs: [TypedVirtualPath]) throws -> [TypedVirtualPath] {

Sources/SwiftDriver/Jobs/InterpretJob.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ extension Driver {
4646
commandLine: commandLine,
4747
inputs:inputs,
4848
outputs: [],
49-
extraEnvironment: extraEnvironment
49+
extraEnvironment: extraEnvironment,
50+
requiresInPlaceExecution: true
5051
)
5152
}
5253
}

Sources/SwiftDriver/Jobs/Job.swift

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public struct Job: Codable, Equatable {
2121
case autolinkExtract = "autolink-extract"
2222
case emitModule = "emit-module"
2323
case interpret
24+
case repl
2425
}
2526

2627
public enum ArgTemplate: Equatable {
@@ -49,6 +50,9 @@ public struct Job: Codable, Equatable {
4950
/// Any extra environment variables which should be set while running the job.
5051
public var extraEnvironment: [String: String]
5152

53+
/// Whether or not the job must be executed in place, replacing the current driver process.
54+
public var requiresInPlaceExecution: Bool
55+
5256
/// The kind of job.
5357
public var kind: Kind
5458

@@ -59,7 +63,8 @@ public struct Job: Codable, Equatable {
5963
displayInputs: [TypedVirtualPath]? = nil,
6064
inputs: [TypedVirtualPath],
6165
outputs: [TypedVirtualPath],
62-
extraEnvironment: [String: String] = [:]
66+
extraEnvironment: [String: String] = [:],
67+
requiresInPlaceExecution: Bool = false
6368
) {
6469
self.kind = kind
6570
self.tool = tool
@@ -68,22 +73,13 @@ public struct Job: Codable, Equatable {
6873
self.inputs = inputs
6974
self.outputs = outputs
7075
self.extraEnvironment = extraEnvironment
76+
self.requiresInPlaceExecution = requiresInPlaceExecution
7177
}
7278
}
7379

7480
extension Job: CustomStringConvertible {
7581
public var description: String {
76-
var result: String = tool.name
77-
78-
for arg in commandLine {
79-
result += " "
80-
switch arg {
81-
case .flag(let string):
82-
result += string.spm_shellEscaped()
83-
case .path(let path):
84-
result += path.name.spm_shellEscaped()
85-
}
86-
}
82+
var result: String = "\(tool.name) \(commandLine.joinedArguments)"
8783

8884
if !self.extraEnvironment.isEmpty {
8985
result += " #"

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
13+
public enum PlanningError: Error, DiagnosticData {
14+
case replReceivedInput
15+
16+
public var description: String {
17+
switch self {
18+
case .replReceivedInput:
19+
return "REPL mode requires no input files"
20+
}
21+
}
22+
}
23+
1224
/// Planning for builds
1325
extension Driver {
1426
/// Plan a standard compilation, which produces jobs for compiling separate
@@ -145,7 +157,10 @@ extension Driver {
145157
// Plan the build.
146158
switch compilerMode {
147159
case .repl:
148-
fatalError("Not yet supported")
160+
if !inputFiles.isEmpty {
161+
throw PlanningError.replReceivedInput
162+
}
163+
return [try replJob()]
149164

150165
case .immediate:
151166
return [try interpretJob(inputs: inputFiles)]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===--------------- ReplJob.swift - Swift REPL ---------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
extension Driver {
14+
mutating func replJob() throws -> Job {
15+
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
16+
17+
try addCommonFrontendOptions(commandLine: &commandLine)
18+
// FIXME: MSVC runtime flags
19+
20+
try commandLine.appendLast(.importObjcHeader, from: &parsedOptions)
21+
try commandLine.appendAll(.l, .framework, .L, from: &parsedOptions)
22+
23+
// Look for -lldb-repl or -deprecated-integrated-repl to determine which
24+
// REPL to use. If neither is provided, prefer LLDB if it can be found.
25+
if parsedOptions.hasFlag(positive: .lldbRepl,
26+
negative: .deprecatedIntegratedRepl,
27+
default: (try? toolchain.getToolPath(.lldb)) != nil) {
28+
// Squash important frontend options into a single argument for LLDB.
29+
let lldbArg = "--repl=\(commandLine.joinedArguments)"
30+
return Job(
31+
kind: .repl,
32+
tool: .absolute(try toolchain.getToolPath(.lldb)),
33+
commandLine: [Job.ArgTemplate.flag(lldbArg)],
34+
inputs: [],
35+
outputs: [],
36+
requiresInPlaceExecution: true
37+
)
38+
} else {
39+
// Invoke the integrated REPL, which is part of the frontend.
40+
commandLine = [.flag("-frontend"), .flag("-repl")] + commandLine
41+
commandLine.appendFlags("-module-name", moduleName)
42+
return Job(
43+
kind: .repl,
44+
tool: .absolute(try toolchain.getToolPath(.swiftCompiler)),
45+
commandLine: commandLine,
46+
inputs: [],
47+
outputs: [],
48+
requiresInPlaceExecution: true
49+
)
50+
}
51+
}
52+
}

Sources/SwiftDriver/Toolchains/DarwinToolchain.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public final class DarwinToolchain: Toolchain {
4444
return AbsolutePath(result)
4545
case .swiftAutolinkExtract:
4646
return try lookup(executable: "swift-autolink-extract")
47+
case .lldb:
48+
return try lookup(executable: "lldb")
4749
}
4850
}
4951

Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public final class GenericUnixToolchain: Toolchain {
4242
return try lookup(executable: "swift-autolink-extract")
4343
case .dsymutil:
4444
return try lookup(executable: "dsymutil")
45+
case .lldb:
46+
return try lookup(executable: "lldb")
4547
}
4648
}
4749

Sources/SwiftDriver/Toolchains/Toolchain.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public enum Tool {
1919
case clang
2020
case swiftAutolinkExtract
2121
case dsymutil
22+
case lldb
2223
}
2324

2425
/// Describes a toolchain, which includes information about compilers, linkers

Sources/SwiftDriver/Utilities/Diagnostics.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import TSCBasic
1313

1414
public typealias Diagnostic = TSCBasic.Diagnostic
15+
public typealias DiagnosticData = TSCBasic.DiagnosticData
1516

1617
extension Diagnostic.Message {
1718
static var error_static_emit_executable_disallowed: Diagnostic.Message {

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,12 +885,76 @@ final class SwiftDriverTests: XCTestCase {
885885

886886
}
887887

888+
func testRepl() throws {
889+
890+
func isLLDBREPLFlag(_ arg: Job.ArgTemplate) -> Bool {
891+
if case .flag(let replString) = arg {
892+
return replString.hasPrefix("--repl=") &&
893+
!replString.contains("-module-name")
894+
}
895+
return false
896+
}
897+
898+
do {
899+
var driver = try Driver(args: ["swift"])
900+
let plannedJobs = try driver.planBuild()
901+
XCTAssertEqual(plannedJobs.count, 1)
902+
let replJob = plannedJobs.first!
903+
XCTAssertTrue(replJob.tool.name.contains("lldb"))
904+
XCTAssertTrue(replJob.requiresInPlaceExecution)
905+
XCTAssert(replJob.commandLine.contains(where: { isLLDBREPLFlag($0) }))
906+
}
907+
908+
do {
909+
var driver = try Driver(args: ["swift", "-repl"])
910+
let plannedJobs = try driver.planBuild()
911+
XCTAssertEqual(plannedJobs.count, 1)
912+
let replJob = plannedJobs.first!
913+
XCTAssertTrue(replJob.tool.name.contains("lldb"))
914+
XCTAssertTrue(replJob.requiresInPlaceExecution)
915+
XCTAssert(replJob.commandLine.contains(where: { isLLDBREPLFlag($0) }))
916+
}
917+
918+
do {
919+
let (mode, args) = try Driver.invocationRunMode(forArgs: ["swift", "repl"])
920+
XCTAssertEqual(mode, .normal(isRepl: true))
921+
var driver = try Driver(args: args)
922+
let plannedJobs = try driver.planBuild()
923+
XCTAssertEqual(plannedJobs.count, 1)
924+
let replJob = plannedJobs.first!
925+
XCTAssertTrue(replJob.tool.name.contains("lldb"))
926+
XCTAssertTrue(replJob.requiresInPlaceExecution)
927+
XCTAssert(replJob.commandLine.contains(where: { isLLDBREPLFlag($0) }))
928+
}
929+
930+
do {
931+
var driver = try Driver(args: ["swift", "-deprecated-integrated-repl"])
932+
let plannedJobs = try driver.planBuild()
933+
XCTAssertEqual(plannedJobs.count, 1)
934+
let replJob = plannedJobs.first!
935+
XCTAssertTrue(replJob.tool.name.contains("swift"))
936+
XCTAssertTrue(replJob.requiresInPlaceExecution)
937+
XCTAssertTrue(replJob.commandLine.count >= 2)
938+
XCTAssertEqual(replJob.commandLine[0], .flag("-frontend"))
939+
XCTAssertEqual(replJob.commandLine[1], .flag("-repl"))
940+
XCTAssert(replJob.commandLine.contains(.flag("-module-name")))
941+
}
942+
943+
do {
944+
var driver = try Driver(args: ["swift", "-repl", "/foo/bar/Test.swift"])
945+
XCTAssertThrowsError(try driver.planBuild()) { error in
946+
XCTAssertEqual(error as? PlanningError, .replReceivedInput)
947+
}
948+
}
949+
}
950+
888951
func testImmediateMode() throws {
889952
do {
890953
var driver = try Driver(args: ["swift", "foo.swift"])
891954
let plannedJobs = try driver.planBuild()
892955
XCTAssertEqual(plannedJobs.count, 1)
893956
let job = plannedJobs[0]
957+
XCTAssertTrue(job.requiresInPlaceExecution)
894958
XCTAssertEqual(job.inputs.count, 1)
895959
XCTAssertEqual(job.inputs[0].file, .relative(RelativePath("foo.swift")))
896960
XCTAssertEqual(job.outputs.count, 0)
@@ -908,6 +972,7 @@ final class SwiftDriverTests: XCTestCase {
908972
let plannedJobs = try driver.planBuild()
909973
XCTAssertEqual(plannedJobs.count, 1)
910974
let job = plannedJobs[0]
975+
XCTAssertTrue(job.requiresInPlaceExecution)
911976
XCTAssertEqual(job.inputs.count, 1)
912977
XCTAssertEqual(job.inputs[0].file, .relative(RelativePath("foo.swift")))
913978
XCTAssertEqual(job.outputs.count, 0)
@@ -926,6 +991,7 @@ final class SwiftDriverTests: XCTestCase {
926991
let plannedJobs = try driver.planBuild()
927992
XCTAssertEqual(plannedJobs.count, 1)
928993
let job = plannedJobs[0]
994+
XCTAssertTrue(job.requiresInPlaceExecution)
929995
XCTAssertEqual(job.inputs.count, 1)
930996
XCTAssertEqual(job.inputs[0].file, .relative(RelativePath("foo.swift")))
931997
XCTAssertEqual(job.outputs.count, 0)

0 commit comments

Comments
 (0)