diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift index 5dc3f792..fa1b5686 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift @@ -34,28 +34,43 @@ final class CustomTaskProducer: PhasedTaskProducer, TaskProducer { let workingDirectory = customTask.workingDirectory.map { Path(context.settings.globalScope.evaluate($0)).normalize() } ?? context.defaultWorkingDirectory let inputPaths = customTask.inputFilePaths.map { Path(context.settings.globalScope.evaluate($0)).normalize() } let inputs = inputPaths.map { delegate.createNode($0) } - var outputs: [any PlannedNode] = customTask.outputFilePaths.map { Path(context.settings.globalScope.evaluate($0)).normalize() }.map { delegate.createNode($0) } - + let outputPaths = customTask.outputFilePaths.map { Path(context.settings.globalScope.evaluate($0)).normalize() } + var outputs: [any PlannedNode] = outputPaths.map { delegate.createNode($0) } + + let md5Context = InsecureHashContext() + for arg in commandLine { + md5Context.add(string: arg) + md5Context.add(number: 0) + } + md5Context.add(number: 1) + for (key, value) in environment.bindingsDictionary { + md5Context.add(string: key) + md5Context.add(number: 0) + md5Context.add(string: value) + md5Context.add(number: 0) + } + md5Context.add(number: 1) + md5Context.add(string: workingDirectory.str) + md5Context.add(number: 1) + for input in inputPaths { + md5Context.add(string: input.str) + md5Context.add(number: 0) + } + md5Context.add(number: 1) + for output in outputPaths { + md5Context.add(string: output.str) + md5Context.add(number: 0) + } + let taskSignature = md5Context.signature.asString + if outputs.isEmpty { // If there are no outputs, create a virtual output that can be wired up to gates - let md5Context = InsecureHashContext() - for arg in commandLine { - md5Context.add(string: arg) - } - for (key, value) in environment.bindingsDictionary { - md5Context.add(string: key) - md5Context.add(string: value) - } - md5Context.add(string: workingDirectory.str) - for input in inputPaths { - md5Context.add(string: input.str) - } - outputs.append(delegate.createVirtualNode("CustomTask-\(md5Context.signature.asString)")) + outputs.append(delegate.createVirtualNode("CustomTask-\(taskSignature)")) } delegate.createTask( type: CustomTaskTypeDescription.only, - ruleInfo: ["CustomTask", context.settings.globalScope.evaluate(customTask.executionDescription)], + ruleInfo: ["CustomTask", context.settings.globalScope.evaluate(customTask.executionDescription), taskSignature], commandLine: commandLine, environment: environment, workingDirectory: workingDirectory, diff --git a/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift index c8cd71e9..7dfea111 100644 --- a/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift @@ -59,7 +59,7 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkCommandLine(["tool", "-foo", "-bar"]) task.checkEnvironment(["ENVVAR": "VALUE"]) #expect(task.workingDirectory == Path.root.join("working/directory")) @@ -119,14 +119,14 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkCommandLine(["tool", "-foo", "-bar"]) - results.checkTaskDoesNotFollow(task, .matchRule(["CustomTask", "My Custom Task 2"])) + results.checkTaskDoesNotFollow(task, .matchRulePattern(["CustomTask", "My Custom Task 2", .any])) } - results.checkTask(.matchRule(["CustomTask", "My Custom Task 2"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task 2", .any])) { task in task.checkCommandLine(["tool2", "-bar", "-foo"]) - results.checkTaskDoesNotFollow(task, .matchRule(["CustomTask", "My Custom Task"])) + results.checkTaskDoesNotFollow(task, .matchRulePattern(["CustomTask", "My Custom Task", .any])) } } } @@ -170,7 +170,7 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkCommandLine(["tool", "-foo", "-bar"]) task.checkOutputs([ // Virtual output @@ -223,9 +223,64 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkEnvironment(["ENVVAR": "VALUE", "MY_SETTING": "FOO"]) } } } + + @Test(.requireSDKs(.host)) + func customTasksWithDuplicateDescriptions() async throws { + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", path: "Sources", + children: [ + TestFile("input.txt"), + TestFile("input2.txt"), + TestFile("main.c"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SDKROOT": "auto", + ]), + ], + targets: [ + TestStandardTarget( + "CoreFoo", type: .framework, + buildPhases: [ + TestSourcesBuildPhase(["main.c"]) + ], + customTasks: [ + TestCustomTask( + commandLine: ["tool", "-foo", "-bar"], + environment: ["ENVVAR": "VALUE"], + workingDirectory: Path.root.join("working/directory").str, + executionDescription: "My Custom Task", + inputs: ["$(SRCROOT)/Sources/input.txt"], + outputs: [Path.root.join("output").str], + enableSandboxing: false, + preparesForIndexing: false), + TestCustomTask( + commandLine: ["tool", "-foo", "-bar"], + environment: ["ENVVAR": "VALUE"], + workingDirectory: Path.root.join("working/directory").str, + executionDescription: "My Custom Task", + inputs: ["$(SRCROOT)/Sources/input2.txt"], + outputs: [Path.root.join("output2").str], + enableSandboxing: false, + preparesForIndexing: false) + ] + ), + ]) + let tester = try await TaskConstructionTester(getCore(), testProject) + await tester.checkBuild(runDestination: .host) { results in + // Ensure we don't incorrectly diagnose duplicate custom tasks + results.checkNoDiagnostics() + } + } }