diff --git a/Sources/SWBBuildSystem/BuildOperation.swift b/Sources/SWBBuildSystem/BuildOperation.swift index 3f2afc02..228e3cf4 100644 --- a/Sources/SWBBuildSystem/BuildOperation.swift +++ b/Sources/SWBBuildSystem/BuildOperation.swift @@ -1002,6 +1002,10 @@ extension BuildOperation: TaskExecutionDelegate { package var emitFrontendCommandLines: Bool { buildDescription.emitFrontendCommandLines } + + package var continueBuildingAfterErrors: Bool { + request.continueBuildingAfterErrors + } } // BuildOperation uses reference semantics. diff --git a/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift index fcf16b8f..ddbc7332 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift @@ -1368,7 +1368,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible extraInputs = [] } - if let moduleDependenciesContext { + // If validation is set to `YES_ERROR`, individual Clang tasks emit diagnostics, so they need this data in their signature. Otherwise, diagnostics are handled by the per-target validation action so individual compiles are not affected by changes to validation-related build settings. + if let moduleDependenciesContext, moduleDependenciesContext.validate == .yesError { do { let jsonData = try JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(moduleDependenciesContext) guard let signature = String(data: jsonData, encoding: .utf8) else { diff --git a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift index 4b3d7e34..825277e5 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift @@ -2405,25 +2405,8 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi dependencyData = nil } - let compilationRequirementAdditionalSignatureData: String - if let moduleDependenciesContext = cbc.producer.moduleDependenciesContext { - do { - let jsonData = try JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(moduleDependenciesContext) - guard let signature = String(data: jsonData, encoding: .utf8) else { - throw StubError.error("non-UTF-8 data") - } - compilationRequirementAdditionalSignatureData = additionalSignatureData + "|\(signature)" - } catch { - delegate.error("failed to serialize 'MODULE_DEPENDENCIES' context information: \(error)") - return - } - } - else { - compilationRequirementAdditionalSignatureData = additionalSignatureData - } - // Compilation Requirements - delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo("SwiftDriver Compilation Requirements", targetName), additionalSignatureData: compilationRequirementAdditionalSignatureData, commandLine: ["builtin-Swift-Compilation-Requirements", "--"] + args, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: allInputsNodes, outputs: compilationRequirementOutputs, action: delegate.taskActionCreationDelegate.createSwiftCompilationRequirementTaskAction(), execDescription: archSpecificExecutionDescription(cbc.scope.namespace.parseString("Unblock downstream dependents of $PRODUCT_NAME"), cbc, delegate), preparesForIndexing: true, enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilation, .compilationRequirement, .linkingRequirement, .blockedByTargetHeaders, .compilationForIndexableSourceFile], usesExecutionInputs: true, showInLog: true) + delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo("SwiftDriver Compilation Requirements", targetName), additionalSignatureData: additionalSignatureData, commandLine: ["builtin-Swift-Compilation-Requirements", "--"] + args, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: allInputsNodes, outputs: compilationRequirementOutputs, action: delegate.taskActionCreationDelegate.createSwiftCompilationRequirementTaskAction(), execDescription: archSpecificExecutionDescription(cbc.scope.namespace.parseString("Unblock downstream dependents of $PRODUCT_NAME"), cbc, delegate), preparesForIndexing: true, enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilation, .compilationRequirement, .linkingRequirement, .blockedByTargetHeaders, .compilationForIndexableSourceFile], usesExecutionInputs: true, showInLog: true) if case .compile = compilationMode { // Unblocking compilation diff --git a/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift b/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift index 0fb14969..a138e1c6 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +private import Foundation public import SWBUtil import SWBMacro @@ -39,8 +40,21 @@ public final class ValidateDependenciesSpec: CommandLineToolSpec, SpecImplementa guard let configuredTarget = cbc.producer.configuredTarget else { return } + + let jsonData: Data + do { + jsonData = try JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(payload) + } catch { + delegate.error("Error serializing \(payload): \(error)") + return + } + guard let signature = String(data: jsonData, encoding: .utf8) else { + delegate.error("non-UTF-8 data in signature for ValidateDependencies") + return + } + let output = delegate.createVirtualNode("ValidateDependencies \(configuredTarget.guid)") - delegate.createTask(type: self, payload: payload, ruleInfo: ["ValidateDependencies"], commandLine: ["builtin-validate-dependencies"] + dependencyInfos.map { $0.path.str }, environment: EnvironmentBindings(), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: dependencyInfos + cbc.commandOrderingInputs, outputs: [output], action: delegate.taskActionCreationDelegate.createValidateDependenciesTaskAction(), preparesForIndexing: false, enableSandboxing: false) + delegate.createTask(type: self, payload: payload, ruleInfo: ["ValidateDependencies"], additionalSignatureData: signature, commandLine: ["builtin-validate-dependencies"] + dependencyInfos.map { $0.path.str }, environment: EnvironmentBindings(), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: dependencyInfos + cbc.commandOrderingInputs, outputs: [output], action: delegate.taskActionCreationDelegate.createValidateDependenciesTaskAction(), preparesForIndexing: false, enableSandboxing: false) } } diff --git a/Sources/SWBTaskExecution/Task.swift b/Sources/SWBTaskExecution/Task.swift index 745dc5ea..acbd8b60 100644 --- a/Sources/SWBTaskExecution/Task.swift +++ b/Sources/SWBTaskExecution/Task.swift @@ -583,6 +583,8 @@ public protocol TaskExecutionDelegate var namespace: MacroNamespace { get } + var continueBuildingAfterErrors: Bool { get } + /// Notifies the delegate that this task has discovered a target dependency which must exist to ensure deterministic builds. func taskDiscoveredRequiredTargetDependency(target: ConfiguredTarget, antecedent: ConfiguredTarget, reason: RequiredTargetDependencyReason, warningLevel: BooleanWarningLevel) } diff --git a/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift index 88062d09..6b341083 100644 --- a/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift @@ -261,6 +261,20 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA moduleDependenciesContext = nil dependencyValidationOutputPath = nil } + + // Emit per-task diagnostics if validation is set to `YES_ERROR` and we're not continuing to build after errors. + let moduleDependenciesEmitPerTaskDiagnostics = moduleDependenciesContext?.validate == .yesError && !executionDelegate.continueBuildingAfterErrors + if moduleDependenciesEmitPerTaskDiagnostics, let dependencyValidationOutputPath { + // Since we're emitting per task diagnostics, communicate empty diagnostics to the validation task. + let validationInfo = DependencyValidationInfo(payload: .clangDependencies(files: [])) + _ = try executionDelegate.fs.writeIfChanged( + dependencyValidationOutputPath, + contents: ByteString( + JSONEncoder(outputFormatting: .sortedKeys).encode(validationInfo) + ) + ) + } + if let traceFilePath { // Remove the trace output file if it already exists. if executionDelegate.fs.exists(traceFilePath) { @@ -325,28 +339,41 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA } } - if lastResult == .succeeded { + if lastResult == .succeeded, let moduleDependenciesContext { // Verify the dependencies from the trace data. let payload: DependencyValidationInfo.Payload + var allFiles = Set() if let traceFilePath { let fs = executionDelegate.fs let traceData = try JSONDecoder().decode(Array.self, from: Data(fs.read(traceFilePath))) - var allFiles = Set() traceData.forEach { allFiles.formUnion(Set($0.includes)) } payload = .clangDependencies(files: allFiles.map { $0.str }) } else { payload = .unsupported } - if let dependencyValidationOutputPath { - let validationInfo = DependencyValidationInfo(payload: payload) - _ = try executionDelegate.fs.writeIfChanged( - dependencyValidationOutputPath, - contents: ByteString( - JSONEncoder(outputFormatting: .sortedKeys).encode(validationInfo) + if moduleDependenciesEmitPerTaskDiagnostics { + let missingDeps = moduleDependenciesContext.computeMissingDependencies(files: Array(allFiles)) + let diagnostics = moduleDependenciesContext.makeDiagnostics(missingDependencies: missingDeps) + + for diagnostic in diagnostics { + outputDelegate.emit(diagnostic) + } + + if diagnostics.contains(where: { $0.behavior == .error }) { + return .failed + } + } else { + if let dependencyValidationOutputPath { + let validationInfo = DependencyValidationInfo(payload: payload) + _ = try executionDelegate.fs.writeIfChanged( + dependencyValidationOutputPath, + contents: ByteString( + JSONEncoder(outputFormatting: .sortedKeys).encode(validationInfo) + ) ) - ) + } } } diff --git a/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift b/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift index 82e321c8..7eed9d7a 100644 --- a/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift +++ b/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift @@ -42,6 +42,7 @@ struct MockExecutionDelegate: TaskExecutionDelegate { var namespace: MacroNamespace var requestContext: SWBCore.BuildRequestContext { fatalError() } var emitFrontendCommandLines: Bool { false } + var continueBuildingAfterErrors: Bool { false } private var core: Core? func taskDiscoveredRequiredTargetDependency(target: ConfiguredTarget, antecedent: ConfiguredTarget, reason: RequiredTargetDependencyReason, warningLevel: BooleanWarningLevel) {}