-
Couldn't load subscription status.
- Fork 213
Handle -continue-building-after-errors and add mechanism for stopping build on failure #371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -27,6 +27,7 @@ public protocol DriverExecutor { | |
| func execute(workload: DriverExecutorWorkload, | ||
| delegate: JobExecutionDelegate, | ||
| numParallelJobs: Int, | ||
| continueBuildingAfterErrors: Bool, | ||
| forceResponseFiles: Bool, | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will require a corresponding update to SPM's executor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's why in my version, I put this bit in the workload parameter. Seemed like one could defend that the decision to run jobs after an error was a workload parameter. | ||
| recordedInputModificationDates: [TypedVirtualPath: Date] | ||
| ) throws | ||
| | @@ -35,6 +36,7 @@ public protocol DriverExecutor { | |
| func execute(jobs: [Job], | ||
| delegate: JobExecutionDelegate, | ||
| numParallelJobs: Int, | ||
| continueBuildingAfterErrors: Bool, | ||
| forceResponseFiles: Bool, | ||
| recordedInputModificationDates: [TypedVirtualPath: Date] | ||
| ) throws | ||
| | @@ -92,13 +94,15 @@ extension DriverExecutor { | |
| jobs: [Job], | ||
| delegate: JobExecutionDelegate, | ||
| numParallelJobs: Int, | ||
| continueBuildingAfterErrors: Bool, | ||
| forceResponseFiles: Bool, | ||
| recordedInputModificationDates: [TypedVirtualPath: Date] | ||
| ) throws { | ||
| try execute( | ||
| workload: .all(jobs), | ||
| delegate: delegate, | ||
| numParallelJobs: numParallelJobs, | ||
| continueBuildingAfterErrors: continueBuildingAfterErrors, | ||
| forceResponseFiles: forceResponseFiles, | ||
| recordedInputModificationDates: recordedInputModificationDates) | ||
| } | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -67,6 +67,9 @@ public final class MultiJobExecutor { | |
| /// Operation queue for executing tasks in parallel. | ||
| let jobQueue: OperationQueue | ||
| | ||
| /// Whether jobs should continue being scheduled after one fails. | ||
| let continueBuildingAfterErrors: Bool | ||
| | ||
| /// The process set to use when launching new processes. | ||
| let processSet: ProcessSet? | ||
| | ||
| | @@ -87,6 +90,9 @@ public final class MultiJobExecutor { | |
| /// any newly-required job. Set only once. | ||
| private(set) var executeAllJobsTaskBuildEngine: LLTaskBuildEngine? = nil | ||
| | ||
| /// Should new jobs immediately fail. | ||
| private(set) var isCancelled: Bool = false | ||
| | ||
| | ||
| init( | ||
| argsResolver: ArgsResolver, | ||
| | @@ -95,6 +101,7 @@ public final class MultiJobExecutor { | |
| workload: DriverExecutorWorkload, | ||
| executorDelegate: JobExecutionDelegate, | ||
| jobQueue: OperationQueue, | ||
| continueBuildingAfterErrors: Bool, | ||
| processSet: ProcessSet?, | ||
| forceResponseFiles: Bool, | ||
| recordedInputModificationDates: [TypedVirtualPath: Date], | ||
| | @@ -114,6 +121,7 @@ public final class MultiJobExecutor { | |
| self.fileSystem = fileSystem | ||
| self.executorDelegate = executorDelegate | ||
| self.jobQueue = jobQueue | ||
| self.continueBuildingAfterErrors = continueBuildingAfterErrors | ||
| self.processSet = processSet | ||
| self.forceResponseFiles = forceResponseFiles | ||
| self.recordedInputModificationDates = recordedInputModificationDates | ||
| | @@ -199,6 +207,10 @@ public final class MultiJobExecutor { | |
| executeAllJobsTaskBuildEngine = engine | ||
| } | ||
| | ||
| fileprivate func cancel() { | ||
| isCancelled = true | ||
| } | ||
| | ||
| /// After a job finishes, an incremental build may discover more jobs are needed, or if all compilations | ||
| /// are done, will need to then add in the post-compilation rules. | ||
| fileprivate func addRuleBeyondMandatoryCompiles( | ||
| | @@ -239,6 +251,9 @@ public final class MultiJobExecutor { | |
| /// The number of jobs to run in parallel. | ||
| private let numParallelJobs: Int | ||
| | ||
| /// Whether jobs should continue being scheduled after one fails. | ||
| private let continueBuildingAfterErrors: Bool | ||
| | ||
| /// The process set to use when launching new processes. | ||
| private let processSet: ProcessSet? | ||
| | ||
| | @@ -260,6 +275,7 @@ public final class MultiJobExecutor { | |
| executorDelegate: JobExecutionDelegate, | ||
| diagnosticsEngine: DiagnosticsEngine, | ||
| numParallelJobs: Int? = nil, | ||
| continueBuildingAfterErrors: Bool = false, | ||
| processSet: ProcessSet? = nil, | ||
| forceResponseFiles: Bool = false, | ||
| recordedInputModificationDates: [TypedVirtualPath: Date] = [:], | ||
| | @@ -270,6 +286,7 @@ public final class MultiJobExecutor { | |
| self.executorDelegate = executorDelegate | ||
| self.diagnosticsEngine = diagnosticsEngine | ||
| self.numParallelJobs = numParallelJobs ?? 1 | ||
| self.continueBuildingAfterErrors = continueBuildingAfterErrors | ||
| self.processSet = processSet | ||
| self.forceResponseFiles = forceResponseFiles | ||
| self.recordedInputModificationDates = recordedInputModificationDates | ||
| | @@ -304,6 +321,7 @@ public final class MultiJobExecutor { | |
| workload: workload, | ||
| executorDelegate: executorDelegate, | ||
| jobQueue: jobQueue, | ||
| continueBuildingAfterErrors: continueBuildingAfterErrors, | ||
| processSet: processSet, | ||
| forceResponseFiles: forceResponseFiles, | ||
| recordedInputModificationDates: recordedInputModificationDates, | ||
| | @@ -428,11 +446,14 @@ class ExecuteJobRule: LLBuildRule { | |
| | ||
| override func inputsAvailable(_ engine: LLTaskBuildEngine) { | ||
| // Return early any of the input failed. | ||
| guard allInputsSucceeded else { | ||
| guard allInputsSucceeded, !context.isCancelled else { | ||
| return engine.taskIsComplete(DriverBuildValue.jobExecution(success: false)) | ||
| } | ||
| | ||
| context.jobQueue.addOperation { | ||
| guard !self.context.isCancelled else { | ||
| return engine.taskIsComplete(DriverBuildValue.jobExecution(success: false)) | ||
| } | ||
| self.executeJob(engine) | ||
| } | ||
| } | ||
| Comment on lines 447 to 459 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If these changes work, this part of the PR is more elegant than mine. | ||
| | @@ -532,6 +553,11 @@ class ExecuteJobRule: LLBuildRule { | |
| } | ||
| | ||
| engine.taskIsComplete(value) | ||
| | ||
| if !value.success, !context.continueBuildingAfterErrors { | ||
| // Allow currently running jobs to finish, but don't execute anymore | ||
| context.cancel() | ||
| } | ||
| } | ||
| } | ||
| | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -306,14 +306,98 @@ final class JobExecutorTests: XCTestCase { | |
| $0 <<< "let bar = 3" | ||
| } | ||
| | ||
| // FIXME: It's unfortunate we diagnose this twice, once for each job which uses the input. | ||
| verifier.expect(.error("input file '\(other.description)' was modified during the build")) | ||
| Comment on lines -309 to -310 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this still needed if -driver-continue-building... is set? Or can we 86 it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope not needed. It was getting emitted twice because the second job was running even though the first one failed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh @davidungar I missunderstood your question. Yes, if continue building after errors is set, then we would get 2 errors, but since it's not and the driver defaults to 1 job at a time, it only outputs once. Is this not causing a test failure in #387 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, @cltnschlosser . Apparently not causing a failure there. As soon as I've gotten my PRs in, I'll push forward on the legacy tests and see what pops up. | ||
| verifier.expect(.error("input file '\(other.description)' was modified during the build")) | ||
| XCTAssertThrowsError(try driver.run(jobs: jobs)) | ||
| } | ||
| } | ||
| } | ||
| | ||
| func testStopAfterError() throws { | ||
| try withTemporaryDirectory { path in | ||
| let foo = path.appending(component: "foo.swift") | ||
| try localFileSystem.writeFileContents(foo) { | ||
| $0 <<< """ | ||
| struct Foo { | ||
| let bar: Int | ||
| init() { | ||
| self.bar = self.bar | ||
| } | ||
| } | ||
| """ | ||
| } | ||
| let bar = path.appending(component: "bar.swift") | ||
| try localFileSystem.writeFileContents(bar) { | ||
| $0 <<< """ | ||
| struct Bar { | ||
| let baz: Int | ||
| init() { | ||
| self.baz = self.baz | ||
| } | ||
| } | ||
| """ | ||
| } | ||
| | ||
| // Replace the error stream with one we capture here. | ||
| let errorStream = stderrStream | ||
| let errorOutputFile = path.appending(component: "dummy_error_stream") | ||
| TSCBasic.stderrStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(errorOutputFile)) | ||
| // Restore the error stream to what it was | ||
| defer { TSCBasic.stderrStream = errorStream } | ||
| | ||
| try assertNoDriverDiagnostics(args: "swiftc", foo.pathString, bar.pathString) { driver in | ||
| let jobs = try driver.planBuild() | ||
| XCTAssertThrowsError(try driver.run(jobs: jobs)) | ||
| } | ||
| | ||
| let invocationError = try localFileSystem.readFileContents(errorOutputFile).description | ||
| XCTAssertTrue(invocationError.contains("self.bar = self.bar")) | ||
| XCTAssertFalse(invocationError.contains("self.baz = self.baz")) | ||
| } | ||
| } | ||
| | ||
| func testContinueAfterError() throws { | ||
| try withTemporaryDirectory { path in | ||
| let foo = path.appending(component: "foo.swift") | ||
| try localFileSystem.writeFileContents(foo) { | ||
| $0 <<< """ | ||
| struct Foo { | ||
| let bar: Int | ||
| init() { | ||
| self.bar = self.bar | ||
| } | ||
| } | ||
| """ | ||
| } | ||
| let bar = path.appending(component: "bar.swift") | ||
| try localFileSystem.writeFileContents(bar) { | ||
| $0 <<< """ | ||
| struct Bar { | ||
| let baz: Int | ||
| init() { | ||
| self.baz = self.baz | ||
| } | ||
| } | ||
| """ | ||
| } | ||
| | ||
| // Replace the error stream with one we capture here. | ||
| let errorStream = stderrStream | ||
| let errorOutputFile = path.appending(component: "dummy_error_stream") | ||
| TSCBasic.stderrStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(errorOutputFile)) | ||
| // Restore the error stream to what it was | ||
| defer { TSCBasic.stderrStream = errorStream } | ||
| | ||
| try assertNoDriverDiagnostics(args: "swiftc", "-continue-building-after-errors", foo.pathString, bar.pathString) { driver in | ||
| let jobs = try driver.planBuild() | ||
| XCTAssertThrowsError(try driver.run(jobs: jobs)) | ||
| } | ||
| | ||
| let invocationError = try localFileSystem.readFileContents(errorOutputFile).description | ||
| XCTAssertTrue(invocationError.contains("self.bar = self.bar")) | ||
| XCTAssertTrue(invocationError.contains("self.baz = self.baz")) | ||
| } | ||
| } | ||
| | ||
| func testTemporaryFileWriting() throws { | ||
| try withTemporaryDirectory { path in | ||
| let resolver = try ArgsResolver(fileSystem: localFileSystem, temporaryDirectory: .absolute(path)) | ||
| | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to drop te @_spi here until the related build issues get sorted out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's being used a lot in this file. Not sure what the issue is, but it's still being used in a lot of places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a set of changes I have not pushed in a PR (yet) that comments out all the spi's so we can use the older compilers. Not sure what to do about this issue.