diff --git a/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift b/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift index 55a889b9..262cd90e 100644 --- a/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift +++ b/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift @@ -80,8 +80,6 @@ public struct SwiftDriverJob: Serializable, CustomDebugStringConvertible { public let outputs: [Path] /// The command line to execute for this job public let commandLine: [SWBUtil.ByteString] - /// A signature which uniquely identifies the job. - public let signature: SWBUtil.ByteString /// Cache keys for the swift-frontend invocation (one key per output producing input) public let cacheKeys: [String] @@ -103,15 +101,10 @@ public struct SwiftDriverJob: Serializable, CustomDebugStringConvertible { self.cacheKeys = job.outputCacheKeys.reduce(into: [String]()) { result, key in result.append(key.value) }.sorted() - let md5 = InsecureHashContext() - for arg in commandLine { - md5.add(bytes: arg) - } - self.signature = md5.signature } public func serialize(to serializer: T) where T : Serializer { - serializer.serializeAggregate(10) { + serializer.serializeAggregate(9) { serializer.serialize(kind) serializer.serialize(ruleInfoType) serializer.serialize(moduleName) @@ -120,13 +113,12 @@ public struct SwiftDriverJob: Serializable, CustomDebugStringConvertible { serializer.serialize(outputs) serializer.serialize(commandLine) serializer.serialize(descriptionForLifecycle) - serializer.serialize(signature) serializer.serialize(cacheKeys) } } public init(from deserializer: any Deserializer) throws { - try deserializer.beginAggregate(10) + try deserializer.beginAggregate(9) try self.kind = deserializer.deserialize() try self.ruleInfoType = deserializer.deserialize() try self.moduleName = deserializer.deserialize() @@ -135,7 +127,6 @@ public struct SwiftDriverJob: Serializable, CustomDebugStringConvertible { try self.outputs = deserializer.deserialize() try self.commandLine = deserializer.deserialize() try self.descriptionForLifecycle = deserializer.deserialize() - try self.signature = deserializer.deserialize() try self.cacheKeys = deserializer.deserialize() } @@ -173,20 +164,30 @@ extension LibSwiftDriver { public let dependencies: [JobKey] /// Working directory for running this job public let workingDirectory: Path + /// A signature which uniquely identifies this planned job. + public let signature: SWBUtil.ByteString internal init(key: JobKey, driverJob: SwiftDriverJob, dependencies: [JobKey], workingDirectory: Path) { self.key = key self.driverJob = driverJob self.dependencies = dependencies self.workingDirectory = workingDirectory + let md5 = InsecureHashContext() + for arg in driverJob.commandLine { + md5.add(bytes: arg) + } + md5.add(string: workingDirectory.str) + md5.add(number: dependencies.hashValue) + self.signature = md5.signature } public func serialize(to serializer: T) where T : Serializer { - serializer.serializeAggregate(4) { + serializer.serializeAggregate(5) { serializer.serialize(key) serializer.serialize(driverJob) serializer.serialize(dependencies) serializer.serialize(workingDirectory) + serializer.serialize(signature) } } @@ -196,6 +197,7 @@ extension LibSwiftDriver { try driverJob = deserializer.deserialize() try dependencies = deserializer.deserialize() try workingDirectory = deserializer.deserialize() + try signature = deserializer.deserialize() } public func addingDependencies(_ newDependencies: [JobKey]) -> PlannedSwiftDriverJob { diff --git a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobSchedulingTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobSchedulingTaskAction.swift index d75cbca8..def85286 100644 --- a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobSchedulingTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobSchedulingTaskAction.swift @@ -291,7 +291,7 @@ open class SwiftDriverJobSchedulingTaskAction: TaskAction { outputDelegate.previouslyBatchedSubtaskUpToDate(signature: SwiftCompilerSpec.computeRuleInfoAndSignatureForPerFileVirtualBatchSubtask(variant: driverPayload.variant, arch: driverPayload.architecture, path: singleInput).1, target: target) } else { // Other jobs are reported as skipped/up-to-date in the usual way. - let taskKey = SwiftDriverJobTaskKey(identifier: driverPayload.uniqueID, variant: driverPayload.variant, arch: driverPayload.architecture, driverJobKey: job.key, driverJobSignature: job.driverJob.signature, isUsingWholeModuleOptimization: driverPayload.isUsingWholeModuleOptimization, compilerLocation: driverPayload.compilerLocation, casOptions: driverPayload.casOptions) + let taskKey = SwiftDriverJobTaskKey(identifier: driverPayload.uniqueID, variant: driverPayload.variant, arch: driverPayload.architecture, driverJobKey: job.key, driverJobSignature: job.signature, isUsingWholeModuleOptimization: driverPayload.isUsingWholeModuleOptimization, compilerLocation: driverPayload.compilerLocation, casOptions: driverPayload.casOptions) let dynamicTask = DynamicTask(toolIdentifier: SwiftDriverJobTaskAction.toolIdentifier, taskKey: .swiftDriverJob(taskKey), workingDirectory: task.workingDirectory, environment: task.environment, target: task.forTarget, showEnvironment: task.showEnvironment) let subtask = try spec.buildExecutableTask(dynamicTask: dynamicTask, context: dynamicExecutionDelegate.operationContext) outputDelegate.subtaskUpToDate(subtask) @@ -306,7 +306,7 @@ open class SwiftDriverJobSchedulingTaskAction: TaskAction { key = .swiftDriverExplicitDependencyJob(SwiftDriverExplicitDependencyJobTaskKey( arch: driverPayload.architecture, driverJobKey: plannedJob.key, - driverJobSignature: plannedJob.driverJob.signature, + driverJobSignature: plannedJob.signature, compilerLocation: driverPayload.compilerLocation, casOptions: driverPayload.casOptions)) } else { @@ -315,7 +315,7 @@ open class SwiftDriverJobSchedulingTaskAction: TaskAction { variant: driverPayload.variant, arch: driverPayload.architecture, driverJobKey: plannedJob.key, - driverJobSignature: plannedJob.driverJob.signature, + driverJobSignature: plannedJob.signature, isUsingWholeModuleOptimization: driverPayload.isUsingWholeModuleOptimization, compilerLocation: driverPayload.compilerLocation, casOptions: driverPayload.casOptions)) diff --git a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift index db56f501..08d57daa 100644 --- a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift @@ -167,7 +167,7 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas public override func getSignature(_ task: any ExecutableTask, executionDelegate: any TaskExecutionDelegate) -> ByteString { let md5 = InsecureHashContext() // We intentionally do not integrate the superclass signature here, because the driver job's signature captures the same information without requiring expensive serialization. - md5.add(bytes: driverJob.driverJob.signature) + md5.add(bytes: driverJob.signature) task.environment.computeSignature(into: md5) return md5.signature } @@ -211,7 +211,7 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas key = .swiftDriverExplicitDependencyJob(SwiftDriverExplicitDependencyJobTaskKey( arch: arch, driverJobKey: plannedJob.key, - driverJobSignature: plannedJob.driverJob.signature, + driverJobSignature: plannedJob.signature, compilerLocation: compilerLocation, casOptions: casOptions)) } else { @@ -226,7 +226,7 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas variant: variant, arch: arch, driverJobKey: plannedJob.key, - driverJobSignature: plannedJob.driverJob.signature, + driverJobSignature: plannedJob.signature, isUsingWholeModuleOptimization: isUsingWholeModuleOptimization, compilerLocation: compilerLocation, casOptions: casOptions)) diff --git a/Tests/SWBBuildSystemTests/SwiftDriverTests.swift b/Tests/SWBBuildSystemTests/SwiftDriverTests.swift index 6be300a1..7d7df89f 100644 --- a/Tests/SWBBuildSystemTests/SwiftDriverTests.swift +++ b/Tests/SWBBuildSystemTests/SwiftDriverTests.swift @@ -5143,4 +5143,147 @@ fileprivate struct SwiftDriverTests: CoreBasedTests { #expect(cleanContents == incrementalContents) } } + + @Test(.requireSDKs(.macOS)) + func ensureIdenticalCommandLinesWithDifferentDependenciesAreNotDeduplicated() async throws { + try await withTemporaryDirectory { tmpDir in + let testWorkspace = try await TestWorkspace( + "Test", + sourceRoot: tmpDir.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + children: [ + TestFile("Framework1.h"), + TestFile("file_1.c"), + TestFile("Framework2.h"), + TestFile("file_2.c"), + TestFile("file_3.swift"), + ]), + buildConfigurations: [TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "CLANG_ENABLE_MODULES": "YES", + "SWIFT_ENABLE_EXPLICIT_MODULES": "YES", + "SWIFT_VERSION": swiftVersion, + "DEFINES_MODULE": "YES", + "VALID_ARCHS": "arm64", + "DSTROOT": tmpDir.join("dstroot").str, + "SWIFT_ENABLE_COMPILE_CACHE": "YES", + ])], + targets: [ + TestStandardTarget( + "Framework1", + type: .framework, + buildPhases: [ + TestHeadersBuildPhase([TestBuildFile("Framework1.h", headerVisibility: .public)]), + TestSourcesBuildPhase(["file_1.c"]), + ]), + TestStandardTarget( + "Framework2", + type: .framework, + buildPhases: [ + TestHeadersBuildPhase([TestBuildFile("Framework2.h", headerVisibility: .public)]), + TestSourcesBuildPhase(["file_2.c"]), + ]), + TestStandardTarget( + "Framework3", + type: .framework, + buildPhases: [ + TestSourcesBuildPhase(["file_3.swift"]), + ], + dependencies: [ + "Framework1", + "Framework2" + ]), + ])]) + + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/Framework1.h")) { stream in + stream <<< + """ + void foo(void); + """ + } + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/file_1.c")) { stream in + stream <<< + """ + void foo(void) {} + """ + } + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/Framework2.h")) { stream in + stream <<< + """ + void qux(void); + """ + } + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/file_2.c")) { stream in + stream <<< + """ + void qux(void) {} + """ + } + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/file_3.swift")) { stream in + stream <<< + """ + import Framework1 + import Framework2 + public func bar() { + foo() + qux() + } + """ + } + + let parameters = BuildParameters(configuration: "Debug", overrides: [:]) + let buildRequest = BuildRequest(parameters: parameters, buildTargets: tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }), continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: false, useDryRun: false) + + try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in + results.checkTasks(.matchRuleType("SwiftExplicitDependencyGeneratePcm")) { tasks in + #expect(tasks.count == 4) + } + results.checkNoDiagnostics() + } + + try await tester.checkNullBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/Framework1.h")) { stream in + stream <<< + """ + void foo(void); introduce an error + """ + } + + try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in + results.checkTaskExists(.matchRuleType("SwiftDriver")) + results.checkTasks(.matchRuleType("SwiftExplicitDependencyGeneratePcm")) { tasks in + #expect(tasks.count == 1) + } + results.checkedErrors = true + } + + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/Framework1.h")) { stream in + stream <<< + """ + void foo(void); + """ + } + + try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in + results.checkTaskExists(.matchRuleType("SwiftDriver")) + results.checkTasks(.matchRuleType("SwiftExplicitDependencyGeneratePcm")) { tasks in + #expect(tasks.count == 1) + } + results.checkNoDiagnostics() + } + } + } }