Skip to content

Commit cadd652

Browse files
authored
Merge pull request #595 from artemcm/ProcessInputSergio
Add support for compile jobs that read source input from standard input.
2 parents 33c33e9 + cc35dca commit cadd652

File tree

5 files changed

+153
-17
lines changed

5 files changed

+153
-17
lines changed

Package.resolved

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftDriver/Execution/ProcessProtocol.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
import TSCBasic
13+
import Foundation
1314

1415
/// Abstraction for functionality that allows working with subprocesses.
1516
public protocol ProcessProtocol {
@@ -19,7 +20,7 @@ public protocol ProcessProtocol {
1920
/// a negative number to represent a "quasi-pid".
2021
///
2122
/// - SeeAlso: https://github.com/apple/swift/blob/main/docs/DriverParseableOutput.rst#quasi-pids
22-
var processID: Process.ProcessID { get }
23+
var processID: TSCBasic.Process.ProcessID { get }
2324

2425
/// Wait for the process to finish execution.
2526
@discardableResult
@@ -29,15 +30,39 @@ public protocol ProcessProtocol {
2930
arguments: [String],
3031
env: [String: String]
3132
) throws -> Self
33+
34+
static func launchProcessAndWriteInput(
35+
arguments: [String],
36+
env: [String: String],
37+
inputFileHandle: FileHandle
38+
) throws -> Self
3239
}
3340

34-
extension Process: ProcessProtocol {
41+
extension TSCBasic.Process: ProcessProtocol {
3542
public static func launchProcess(
3643
arguments: [String],
3744
env: [String: String]
38-
) throws -> Process {
45+
) throws -> TSCBasic.Process {
3946
let process = Process(arguments: arguments, environment: env)
4047
try process.launch()
4148
return process
4249
}
50+
51+
public static func launchProcessAndWriteInput(
52+
arguments: [String],
53+
env: [String: String],
54+
inputFileHandle: FileHandle
55+
) throws -> TSCBasic.Process {
56+
let process = Process(arguments: arguments, environment: env)
57+
let processInputStream = try process.launch()
58+
var input: Data
59+
// Write out the contents of the input handle and close the input stream
60+
repeat {
61+
input = inputFileHandle.availableData
62+
processInputStream.write(input)
63+
} while (input.count > 0)
64+
processInputStream.flush()
65+
try processInputStream.close()
66+
return process
67+
}
4368
}

Sources/SwiftDriverExecution/MultiJobExecutor.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public final class MultiJobExecutor {
8282
/// The type to use when launching new processes. This mostly serves as an override for testing.
8383
let processType: ProcessProtocol.Type
8484

85+
/// The standard input `FileHandle` override for testing.
86+
let testInputHandle: FileHandle?
87+
8588
/// If a job fails, the driver needs to stop running jobs.
8689
private(set) var isBuildCancelled = false
8790

@@ -100,7 +103,8 @@ public final class MultiJobExecutor {
100103
forceResponseFiles: Bool,
101104
recordedInputModificationDates: [TypedVirtualPath: Date],
102105
diagnosticsEngine: DiagnosticsEngine,
103-
processType: ProcessProtocol.Type = Process.self
106+
processType: ProcessProtocol.Type = Process.self,
107+
inputHandleOverride: FileHandle? = nil
104108
) {
105109
(
106110
jobs: self.jobs,
@@ -121,6 +125,7 @@ public final class MultiJobExecutor {
121125
self.recordedInputModificationDates = recordedInputModificationDates
122126
self.diagnosticsEngine = diagnosticsEngine
123127
self.processType = processType
128+
self.testInputHandle = inputHandleOverride
124129
}
125130

126131
private static func fillInJobsAndProducers(_ workload: DriverExecutorWorkload
@@ -248,6 +253,9 @@ public final class MultiJobExecutor {
248253
/// The type to use when launching new processes. This mostly serves as an override for testing.
249254
private let processType: ProcessProtocol.Type
250255

256+
/// The standard input `FileHandle` override for testing.
257+
let testInputHandle: FileHandle?
258+
251259
public init(
252260
workload: DriverExecutorWorkload,
253261
resolver: ArgsResolver,
@@ -257,7 +265,8 @@ public final class MultiJobExecutor {
257265
processSet: ProcessSet? = nil,
258266
forceResponseFiles: Bool = false,
259267
recordedInputModificationDates: [TypedVirtualPath: Date] = [:],
260-
processType: ProcessProtocol.Type = Process.self
268+
processType: ProcessProtocol.Type = Process.self,
269+
inputHandleOverride: FileHandle? = nil
261270
) {
262271
self.workload = workload
263272
self.argsResolver = resolver
@@ -268,6 +277,7 @@ public final class MultiJobExecutor {
268277
self.forceResponseFiles = forceResponseFiles
269278
self.recordedInputModificationDates = recordedInputModificationDates
270279
self.processType = processType
280+
self.testInputHandle = inputHandleOverride
271281
}
272282

273283
/// Execute all jobs.
@@ -314,7 +324,8 @@ public final class MultiJobExecutor {
314324
forceResponseFiles: forceResponseFiles,
315325
recordedInputModificationDates: recordedInputModificationDates,
316326
diagnosticsEngine: diagnosticsEngine,
317-
processType: processType
327+
processType: processType,
328+
inputHandleOverride: testInputHandle
318329
)
319330
}
320331
}
@@ -566,9 +577,20 @@ class ExecuteJobRule: LLBuildRule {
566577
let arguments: [String] = try resolver.resolveArgumentList(for: job,
567578
forceResponseFiles: context.forceResponseFiles)
568579

569-
let process = try context.processType.launchProcess(
570-
arguments: arguments, env: env
571-
)
580+
581+
let process : ProcessProtocol
582+
// If the input comes from standard input, forward the driver's input to the compile job.
583+
if job.inputs.contains(TypedVirtualPath(file: .standardInput, type: .swift)) {
584+
let inputFileHandle = context.testInputHandle ?? FileHandle.standardInput
585+
process = try context.processType.launchProcessAndWriteInput(
586+
arguments: arguments, env: env, inputFileHandle: inputFileHandle
587+
)
588+
} else {
589+
process = try context.processType.launchProcess(
590+
arguments: arguments, env: env
591+
)
592+
}
593+
572594
pid = Int(process.processID)
573595

574596
// Add it to the process set if it's a real process.

Sources/SwiftDriverExecution/SwiftDriverExecutor.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ public final class SwiftDriverExecutor: DriverExecutor {
5151
} else {
5252
var childEnv = env
5353
childEnv.merge(job.extraEnvironment, uniquingKeysWith: { (_, new) in new })
54-
55-
let process = try Process.launchProcess(arguments: arguments, env: childEnv)
54+
let process : ProcessProtocol
55+
if job.inputs.contains(TypedVirtualPath(file: .standardInput, type: .swift)) {
56+
process = try Process.launchProcessAndWriteInput(
57+
arguments: arguments, env: childEnv, inputFileHandle: FileHandle.standardInput
58+
)
59+
} else {
60+
process = try Process.launchProcess(arguments: arguments, env: childEnv)
61+
}
5662
return try process.waitUntilExit()
5763
}
5864
}

Tests/SwiftDriverTests/JobExecutorTests.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class JobCollectingDelegate: JobExecutionDelegate {
2929
return .init()
3030
}
3131

32+
static func launchProcessAndWriteInput(arguments: [String], env: [String : String],
33+
inputFileHandle: FileHandle) throws -> StubProcess {
34+
return .init()
35+
}
36+
3237
var processID: TSCBasic.Process.ProcessID { .init(-1) }
3338

3439
func waitUntilExit() throws -> ProcessResult {
@@ -213,6 +218,84 @@ final class JobExecutorTests: XCTestCase {
213218
#endif
214219
}
215220

221+
/// Ensure the executor is capable of forwarding its standard input to the compile job that requires it.
222+
func testInputForwarding() throws {
223+
#if os(macOS)
224+
let executor = try SwiftDriverExecutor(diagnosticsEngine: DiagnosticsEngine(),
225+
processSet: ProcessSet(),
226+
fileSystem: localFileSystem,
227+
env: ProcessEnv.vars)
228+
let toolchain = DarwinToolchain(env: ProcessEnv.vars, executor: executor)
229+
try withTemporaryDirectory { path in
230+
let exec = path.appending(component: "main")
231+
let compile = Job(
232+
moduleName: "main",
233+
kind: .compile,
234+
tool: .absolute(try toolchain.getToolPath(.swiftCompiler)),
235+
commandLine: [
236+
"-frontend",
237+
"-c",
238+
"-primary-file",
239+
// This compile job must read the input from STDIN
240+
"-",
241+
"-target", "x86_64-apple-darwin18.7.0",
242+
"-enable-objc-interop",
243+
"-sdk",
244+
.path(.absolute(try toolchain.sdk.get())),
245+
"-module-name", "main",
246+
"-o", .path(.temporary(RelativePath("main.o"))),
247+
],
248+
inputs: [TypedVirtualPath(file: .standardInput, type: .swift )],
249+
primaryInputs: [TypedVirtualPath(file: .standardInput, type: .swift )],
250+
outputs: [.init(file: VirtualPath.temporary(RelativePath("main.o")).intern(),
251+
type: .object)]
252+
)
253+
let link = Job(
254+
moduleName: "main",
255+
kind: .link,
256+
tool: .absolute(try toolchain.getToolPath(.dynamicLinker)),
257+
commandLine: [
258+
.path(.temporary(RelativePath("main.o"))),
259+
.path(.absolute(try toolchain.clangRT.get())),
260+
"-syslibroot", .path(.absolute(try toolchain.sdk.get())),
261+
"-lobjc", "-lSystem", "-arch", "x86_64",
262+
"-force_load", .path(.absolute(try toolchain.compatibility50.get())),
263+
"-force_load", .path(.absolute(try toolchain.compatibilityDynamicReplacements.get())),
264+
"-L", .path(.absolute(try toolchain.resourcesDirectory.get())),
265+
"-L", .path(.absolute(try toolchain.sdkStdlib(sdk: toolchain.sdk.get()))),
266+
"-rpath", "/usr/lib/swift", "-macosx_version_min", "10.14.0", "-no_objc_category_merging",
267+
"-o", .path(.absolute(exec)),
268+
],
269+
inputs: [
270+
.init(file: VirtualPath.temporary(RelativePath("main.o")).intern(), type: .object),
271+
],
272+
primaryInputs: [],
273+
outputs: [.init(file: VirtualPath.relative(RelativePath("main")).intern(), type: .image)]
274+
)
275+
276+
// Create a file with inpuit
277+
let inputFile = path.appending(component: "main.swift")
278+
try localFileSystem.writeFileContents(inputFile) {
279+
$0 <<< "print(\"Hello, World\")"
280+
}
281+
// We are going to override he executors standard input FileHandle to the above
282+
// input file, to simulate it being piped over standard input to this compilation.
283+
let testFile: FileHandle = FileHandle(forReadingAtPath: inputFile.description)!
284+
let delegate = JobCollectingDelegate()
285+
let resolver = try ArgsResolver(fileSystem: localFileSystem)
286+
let executor = MultiJobExecutor(workload: .all([compile, link]),
287+
resolver: resolver, executorDelegate: delegate,
288+
diagnosticsEngine: DiagnosticsEngine(),
289+
inputHandleOverride: testFile)
290+
try executor.execute(env: toolchain.env, fileSystem: localFileSystem)
291+
292+
// Execute the resulting program
293+
let output = try TSCBasic.Process.checkNonZeroExit(args: exec.pathString)
294+
XCTAssertEqual(output, "Hello, World\n")
295+
}
296+
#endif
297+
}
298+
216299
func testStubProcessProtocol() throws {
217300
// This test fails intermittently on Linux
218301
// rdar://70067844

0 commit comments

Comments
 (0)