Skip to content

Commit 06b1368

Browse files
authored
Force write updates to preparation nodes (#103)
Make sure we always bump the mod time of a preparation node when it gets run since clients rely on the timestamp to know when the preparation has changed. rdar://143757173
1 parent e4a3d34 commit 06b1368

File tree

8 files changed

+55
-23
lines changed

8 files changed

+55
-23
lines changed

Sources/SWBCore/PlannedTaskAction.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ public struct AuxiliaryFileTaskActionContext {
4242
public let output: Path
4343
public let input: Path
4444
public let permissions: Int?
45+
public let forceWrite: Bool
4546
public let diagnostics: [Diagnostic]
4647
public let logContents: Bool
4748

48-
public init(output: Path, input: Path, permissions: Int?, diagnostics: [Diagnostic], logContents: Bool) {
49+
public init(output: Path, input: Path, permissions: Int?, forceWrite: Bool, diagnostics: [Diagnostic], logContents: Bool) {
4950
self.output = output
5051
self.input = input
5152
self.permissions = permissions
53+
self.forceWrite = forceWrite
5254
self.diagnostics = diagnostics
5355
self.logContents = logContents
5456
}

Sources/SWBCore/Specs/Tools/WriteFile.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ public final class WriteFileSpec: CommandLineToolSpec, SpecImplementationType {
2525
fatalError("unexpected direct invocation")
2626
}
2727

28-
@discardableResult public func constructFileTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, ruleName: String? = nil, contents: ByteString, permissions: Int?, diagnostics: [AuxiliaryFileTaskActionContext.Diagnostic] = [], logContents: Bool = false, preparesForIndexing: Bool, additionalTaskOrderingOptions: TaskOrderingOptions) -> Path {
28+
@discardableResult public func constructFileTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, ruleName: String? = nil, contents: ByteString, permissions: Int?, forceWrite: Bool = false, diagnostics: [AuxiliaryFileTaskActionContext.Diagnostic] = [], logContents: Bool = false, preparesForIndexing: Bool, additionalTaskOrderingOptions: TaskOrderingOptions) -> Path {
2929
let fileContentsPath = delegate.recordAttachment(contents: contents)
3030
let outputNode = delegate.createNode(cbc.output)
3131
let execDescription = resolveExecutionDescription(cbc, delegate)
32-
let action = delegate.taskActionCreationDelegate.createAuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputNode.path, input: fileContentsPath, permissions: permissions, diagnostics: diagnostics, logContents: logContents))
32+
let action = delegate.taskActionCreationDelegate.createAuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputNode.path, input: fileContentsPath, permissions: permissions, forceWrite: forceWrite, diagnostics: diagnostics, logContents: logContents))
3333
let ruleName = ruleName ?? "WriteAuxiliaryFile"
3434
delegate.createTask(type: self, ruleInfo: [ruleName, outputNode.path.str], commandLine: ["write-file", outputNode.path.str], environment: EnvironmentBindings(), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: cbc.commandOrderingInputs, outputs: [ outputNode ], mustPrecede: [], action: action, execDescription: execDescription, preparesForIndexing: preparesForIndexing, enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: additionalTaskOrderingOptions, priority: .unblocksDownstreamTasks)
3535
return fileContentsPath

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1265,7 +1265,7 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase
12651265
}
12661266
await appendGeneratedTasks(&tasks, usePhasedOrdering: false) { delegate in
12671267
let cbc = CommandBuildContext(producer: context, scope: scope, inputs: [], outputs: [preparedForIndexNode.path], commandOrderingInputs: prepareTargetForIndexInputs + moduleInputs)
1268-
context.writeFileSpec.constructFileTasks(cbc, delegate, ruleName: ProductPlan.preparedForIndexPreCompilationRuleName, contents: [], permissions: nil, preparesForIndexing: true, additionalTaskOrderingOptions: [])
1268+
context.writeFileSpec.constructFileTasks(cbc, delegate, ruleName: ProductPlan.preparedForIndexPreCompilationRuleName, contents: [], permissions: nil, forceWrite: true, preparesForIndexing: true, additionalTaskOrderingOptions: [])
12691269
}
12701270
}
12711271

Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TargetOrderTaskProducer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer {
115115
await appendGeneratedTasks(&context.preparedForIndexModuleContentTasks) { delegate in
116116
let outputPath = preparedForIndexModuleNode.path
117117
let cbc = CommandBuildContext(producer: context, scope: context.settings.globalScope, inputs: [], outputs: [outputPath])
118-
context.writeFileSpec.constructFileTasks(cbc, delegate, ruleName: ProductPlan.preparedForIndexModuleContentRuleName, contents: [], permissions: nil, preparesForIndexing: true, additionalTaskOrderingOptions: [])
118+
context.writeFileSpec.constructFileTasks(cbc, delegate, ruleName: ProductPlan.preparedForIndexModuleContentRuleName, contents: [], permissions: nil, forceWrite: true, preparesForIndexing: true, additionalTaskOrderingOptions: [])
119119
}
120120
}
121121
}

Sources/SWBTaskExecution/TaskActions/AuxiliaryFileTaskAction.swift

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,23 @@ public final class AuxiliaryFileTaskAction: TaskAction {
4545
}
4646

4747
do {
48-
let contents = try executionDelegate.fs.read(context.input)
49-
if context.logContents {
50-
outputDelegate.emitOutput(contents)
48+
if context.forceWrite {
49+
// If we're forcing a write, remove and then write regardless of content.
50+
if context.logContents {
51+
let contents = try executionDelegate.fs.read(context.input)
52+
outputDelegate.emitOutput(contents)
53+
}
54+
try? executionDelegate.fs.remove(context.output)
55+
try executionDelegate.fs.copy(context.input, to: context.output)
56+
try executionDelegate.fs.touch(context.output)
57+
} else {
58+
// Otherwise read the content and check to see if it has changed.
59+
let contents = try executionDelegate.fs.read(context.input)
60+
if context.logContents {
61+
outputDelegate.emitOutput(contents)
62+
}
63+
_ = try executionDelegate.fs.writeIfChanged(context.output, contents: contents)
5164
}
52-
_ = try executionDelegate.fs.writeIfChanged(context.output, contents: contents)
5365
} catch {
5466
outputDelegate.emitError("unable to write file '\(context.output.str)': \(error.localizedDescription)")
5567
return .failed
@@ -70,34 +82,37 @@ public final class AuxiliaryFileTaskAction: TaskAction {
7082
// MARK: Serialization
7183

7284
public override func serialize<T: Serializer>(to serializer: T) {
73-
serializer.serializeAggregate(6) {
85+
serializer.serializeAggregate(7) {
7486
serializer.serialize(context.output)
7587
serializer.serialize(context.input)
7688
serializer.serialize(context.permissions)
89+
serializer.serialize(context.forceWrite)
7790
serializer.serialize(context.diagnostics)
7891
serializer.serialize(context.logContents)
7992
super.serialize(to: serializer)
8093
}
8194
}
8295

8396
public required init(from deserializer: any Deserializer) throws {
84-
try deserializer.beginAggregate(6)
97+
try deserializer.beginAggregate(7)
8598
let output: Path = try deserializer.deserialize()
8699
let input: Path = try deserializer.deserialize()
87100
let permissions: Int? = try deserializer.deserialize()
101+
let forceWrite: Bool = try deserializer.deserialize()
88102
let diagnostics: [AuxiliaryFileTaskActionContext.Diagnostic] = try deserializer.deserialize()
89103
let logContents: Bool = try deserializer.deserialize()
90-
self.context = AuxiliaryFileTaskActionContext(output: output, input: input, permissions: permissions, diagnostics: diagnostics, logContents: logContents)
104+
self.context = AuxiliaryFileTaskActionContext(output: output, input: input, permissions: permissions, forceWrite: forceWrite, diagnostics: diagnostics, logContents: logContents)
91105
try super.init(from: deserializer)
92106
}
93107

94108
public override func computeInitialSignature() -> ByteString {
95109
let serializer = MsgPackSerializer()
96-
serializer.serializeAggregate(6) {
110+
serializer.serializeAggregate(7) {
97111
serializer.serialize(context.output)
98112
// We must not serialize the entire path of the 'input', because it will include the build description ID, which may change in cases when this task should not be invalidated.
99113
serializer.serialize(context.input.basename)
100114
serializer.serialize(context.permissions)
115+
serializer.serialize(context.forceWrite)
101116
serializer.serialize(context.diagnostics)
102117
serializer.serialize(context.logContents)
103118
super.serialize(to: serializer)

Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests {
759759
try fs.write(tmpDir.path.join("foo"), contents: "Hello, world!")
760760

761761
do {
762-
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("foo"), permissions: nil, diagnostics: [], logContents: false))
762+
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("foo"), permissions: nil, forceWrite: false, diagnostics: [], logContents: false))
763763
let (task, execTask) = createTask(ruleInfo: ["MOCK"], commandLine: ["builtin-mock"], inputs: [], outputs: [outputNode], mustPrecede: [], action: action)
764764

765765
// Execute a test build against the task set.
@@ -773,7 +773,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests {
773773

774774
// Perform a build with a new, identical task set, and check for a null build.
775775
do {
776-
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("foo"), permissions: nil, diagnostics: [], logContents: false))
776+
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("foo"), permissions: nil, forceWrite: false, diagnostics: [], logContents: false))
777777
let (task, _) = createTask(ruleInfo: ["MOCK"], commandLine: ["builtin-mock"], inputs: [], outputs: [outputNode], mustPrecede: [], action: action)
778778

779779
// Execute a test build against the task set.
@@ -784,7 +784,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests {
784784
// Perform a build with a changed task.
785785
do {
786786
try fs.write(tmpDir.path.join("bar"), contents: "Hello, alternate world!")
787-
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("bar"), permissions: nil, diagnostics: [], logContents: false))
787+
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("bar"), permissions: nil, forceWrite: false, diagnostics: [], logContents: false))
788788
let (task, execTask) = createTask(ruleInfo: ["MOCK"], commandLine: ["builtin-mock"], inputs: [], outputs: [outputNode], mustPrecede: [], action: action)
789789

790790
// Execute a test build against the task set.
@@ -808,7 +808,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests {
808808
try fs.write(tmpDir.path.join("foo"), contents: "Hello, world!")
809809

810810
do {
811-
let context = AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("foo"), permissions: nil, diagnostics: [.init(kind: .error, message: "Couldn't deal with this for some reason")], logContents: false)
811+
let context = AuxiliaryFileTaskActionContext(output: outputPath, input: tmpDir.path.join("foo"), permissions: nil, forceWrite: false, diagnostics: [.init(kind: .error, message: "Couldn't deal with this for some reason")], logContents: false)
812812
let action = AuxiliaryFileTaskAction(context)
813813
let (task, execTask) = createTask(ruleInfo: ["MOCK"], commandLine: ["builtin-mock"], inputs: [], outputs: [outputNode], mustPrecede: [], action: action)
814814

Tests/SWBBuildSystemTests/IndexBuildOperationTests.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,12 @@ fileprivate struct IndexBuildOperationTests: CoreBasedTests {
11001100
results.checkError(.contains("PhaseScriptExecution failed"))
11011101
results.checkTask(.matchRuleType("SwiftDriver GenerateModule"), .matchTargetName(frameTarget.name)) { _ in }
11021102
let (_, resultInfo) = try #require(results.getPreparedForIndexResultInfo().only)
1103+
if tester.fs.fileSystemMode != .checksumOnly {
1104+
// Make sure the timestamp has been updated. This is important
1105+
// since clients rely on the timestamp changing when
1106+
// preparation has changed.
1107+
#expect(currPrepareResult.timestamp < resultInfo.timestamp)
1108+
}
11031109
currPrepareResult = resultInfo
11041110
}
11051111

@@ -1113,6 +1119,9 @@ fileprivate struct IndexBuildOperationTests: CoreBasedTests {
11131119
results.checkTask(.matchRuleType("SwiftDriver GenerateModule"), .matchTargetName(frameTarget.name)) { _ in }
11141120

11151121
let (_, resultInfo) = try #require(results.getPreparedForIndexResultInfo().only)
1122+
if tester.fs.fileSystemMode != .checksumOnly {
1123+
#expect(currPrepareResult.timestamp < resultInfo.timestamp)
1124+
}
11161125
currPrepareResult = resultInfo
11171126
}
11181127

@@ -1125,6 +1134,9 @@ fileprivate struct IndexBuildOperationTests: CoreBasedTests {
11251134
results.checkError(.contains("PhaseScriptExecution failed"))
11261135
results.checkTask(.matchRuleType("SwiftDriver GenerateModule"), .matchTargetName(superframeTarget.name)) { _ in }
11271136
let (_, resultInfo) = try #require(results.getPreparedForIndexResultInfo().only)
1137+
if tester.fs.fileSystemMode != .checksumOnly {
1138+
#expect(currPrepareResult.timestamp == resultInfo.timestamp)
1139+
}
11281140
#expect(currPrepareResult == resultInfo)
11291141
}
11301142

@@ -1137,6 +1149,9 @@ fileprivate struct IndexBuildOperationTests: CoreBasedTests {
11371149
results.checkError(.contains("PhaseScriptExecution failed"))
11381150

11391151
let (_, resultInfo) = try #require(results.getPreparedForIndexResultInfo().only)
1152+
if tester.fs.fileSystemMode != .checksumOnly {
1153+
#expect(currPrepareResult.timestamp < resultInfo.timestamp)
1154+
}
11401155
currPrepareResult = resultInfo
11411156
}
11421157
}

Tests/SWBTaskExecutionTests/AuxiliaryFileTaskTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fileprivate struct AuxiliaryFileTaskTests {
2626
let output = Path.root.join("output.txt")
2727
let input = Path.root.join("input")
2828
try executionDelegate.fs.write(input, contents: ByteString(encodingAsUTF8: "Hello, world!"))
29-
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: input, permissions: 0o755, diagnostics: [], logContents: false))
29+
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: input, permissions: 0o755, forceWrite: false, diagnostics: [], logContents: false))
3030
let task = Task(forTarget: nil, ruleInfo: [], commandLine: ["WriteAuxiliaryFile", output.basename], workingDirectory: .root, outputs: [MakePlannedPathNode(output)], action: action, execDescription: "")
3131

3232
let result = await action.performTaskAction(
@@ -47,7 +47,7 @@ fileprivate struct AuxiliaryFileTaskTests {
4747
let output = Path.root.join("output.txt")
4848
let input = Path.root.join("input")
4949
try executionDelegate.fs.write(input, contents: ByteString(encodingAsUTF8: "Hello, world!"))
50-
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: input, permissions: 0o755, diagnostics: [], logContents: true))
50+
let action = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: input, permissions: 0o755, forceWrite: false, diagnostics: [], logContents: true))
5151
let task = Task(forTarget: nil, ruleInfo: [], commandLine: ["WriteAuxiliaryFile", output.basename], workingDirectory: .root, outputs: [MakePlannedPathNode(output)], action: action, execDescription: "")
5252

5353
let outputDelegate = MockTaskOutputDelegate()
@@ -65,17 +65,17 @@ fileprivate struct AuxiliaryFileTaskTests {
6565
@Test
6666
func signature() {
6767
let output = Path.root.join("output.txt")
68-
let taskA = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: Path("ContentsA"), permissions: nil, diagnostics: [], logContents: false))
68+
let taskA = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: Path("ContentsA"), permissions: nil, forceWrite: false, diagnostics: [], logContents: false))
6969
do {
70-
let taskB = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: Path("ContentsB"), permissions: nil, diagnostics: [], logContents: false))
70+
let taskB = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: Path("ContentsB"), permissions: nil, forceWrite: false, diagnostics: [], logContents: false))
7171
#expect(taskA.computeInitialSignature() != taskB.computeInitialSignature())
7272
}
7373
do {
74-
let taskB = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: Path("/output2.txt"), input: Path("ContentsA"), permissions: nil, diagnostics: [], logContents: false))
74+
let taskB = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: Path("/output2.txt"), input: Path("ContentsA"), permissions: nil, forceWrite: false, diagnostics: [], logContents: false))
7575
#expect(taskA.computeInitialSignature() != taskB.computeInitialSignature())
7676
}
7777
do {
78-
let taskB = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: Path("ContentsA"), permissions: 0o755, diagnostics: [], logContents: false))
78+
let taskB = AuxiliaryFileTaskAction(AuxiliaryFileTaskActionContext(output: output, input: Path("ContentsA"), permissions: 0o755, forceWrite: false, diagnostics: [], logContents: false))
7979
#expect(taskA.computeInitialSignature() != taskB.computeInitialSignature())
8080
}
8181
}

0 commit comments

Comments
 (0)