Skip to content

Commit b31f4c2

Browse files
[6.0] Fix incremental builds for embedInCode resources (#7618)
**Explanation**: Fix a build system hole that incremental build did not detect changes in embedded resources. **Scope**: Incremental build with `embedInCode` feature **Risk**: Low. **Testing**: Added a new test to cover the incremental build case. **Original PR**: #7616 **Reviewers**: @MaxDesiatov
1 parent a140cd6 commit b31f4c2

File tree

4 files changed

+74
-29
lines changed

4 files changed

+74
-29
lines changed

Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,13 @@ public final class SwiftTargetBuildDescription {
7373
return resources.filter { $0.rule != .embedInCode }.isEmpty == false
7474
}
7575

76-
private var needsResourceEmbedding: Bool {
77-
return resources.filter { $0.rule == .embedInCode }.isEmpty == false
76+
var resourceFilesToEmbed: [AbsolutePath] {
77+
return resources.filter { $0.rule == .embedInCode }.map { $0.path }
7878
}
7979

80+
/// The path to Swift source file embedding resource contents if needed.
81+
private(set) var resourcesEmbeddingSource: AbsolutePath?
82+
8083
/// The list of all source files in the target, including the derived ones.
8184
public var sources: [AbsolutePath] {
8285
self.target.sources.paths + self.derivedSources.paths + self.pluginDerivedSources.paths
@@ -309,7 +312,10 @@ public final class SwiftTargetBuildDescription {
309312
}
310313
}
311314

312-
try self.generateResourceEmbeddingCode()
315+
if !resourceFilesToEmbed.isEmpty {
316+
resourcesEmbeddingSource = try addResourceEmbeddingSource()
317+
}
318+
313319
try self.generateTestObservation()
314320
}
315321

@@ -337,31 +343,10 @@ public final class SwiftTargetBuildDescription {
337343
try self.fileSystem.writeIfChanged(path: path, string: content)
338344
}
339345

340-
// FIXME: This will not work well for large files, as we will store the entire contents, plus its byte array representation in memory and also `writeIfChanged()` will read the entire generated file again.
341-
private func generateResourceEmbeddingCode() throws {
342-
guard needsResourceEmbedding else { return }
343-
344-
var content =
345-
"""
346-
struct PackageResources {
347-
348-
"""
349-
350-
try resources.forEach {
351-
guard $0.rule == .embedInCode else { return }
352-
353-
let variableName = $0.path.basename.spm_mangledToC99ExtendedIdentifier()
354-
let fileContent = try Data(contentsOf: URL(fileURLWithPath: $0.path.pathString)).map { String($0) }.joined(separator: ",")
355-
356-
content += "static let \(variableName): [UInt8] = [\(fileContent)]\n"
357-
}
358-
359-
content += "}"
360-
346+
private func addResourceEmbeddingSource() throws -> AbsolutePath {
361347
let subpath = try RelativePath(validating: "embedded_resources.swift")
362348
self.derivedSources.relativePaths.append(subpath)
363-
let path = self.derivedSources.root.appending(subpath)
364-
try self.fileSystem.writeIfChanged(path: path, string: content)
349+
return self.derivedSources.root.appending(subpath)
365350
}
366351

367352
/// Generate the resource bundle accessor, if appropriate.

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,11 @@ extension LLBuildManifestBuilder {
415415
inputs.append(resourcesNode)
416416
}
417417

418+
if let resourcesEmbeddingSource = target.resourcesEmbeddingSource {
419+
let resourceFilesToEmbed = target.resourceFilesToEmbed
420+
self.manifest.addWriteEmbeddedResourcesCommand(resources: resourceFilesToEmbed, outputPath: resourcesEmbeddingSource)
421+
}
422+
418423
func addStaticTargetInputs(_ target: ResolvedModule) throws {
419424
// Ignore C Modules.
420425
if target.underlying is SystemLibraryTarget { return }

Sources/LLBuildManifest/LLBuildManifest.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public enum WriteAuxiliary {
2727
LinkFileList.self,
2828
SourcesFileList.self,
2929
SwiftGetVersion.self,
30-
XCTestInfoPlist.self
30+
XCTestInfoPlist.self,
31+
EmbeddedResources.self,
3132
]
3233

3334
public struct EntitlementPlist: AuxiliaryFileType {
@@ -159,6 +160,35 @@ public enum WriteAuxiliary {
159160
case undefinedPrincipalClass
160161
}
161162
}
163+
164+
public struct EmbeddedResources: AuxiliaryFileType {
165+
public static let name = "embedded-resources"
166+
167+
public static func computeInputs(resources: [AbsolutePath]) -> [Node] {
168+
return [.virtual(Self.name)] + resources.map { Node.file($0) }
169+
}
170+
171+
// FIXME: This will not work well for large files, as we will store the entire contents, plus its byte array
172+
// representation in memory.
173+
public static func getFileContents(inputs: [Node]) throws -> String {
174+
var content =
175+
"""
176+
struct PackageResources {
177+
178+
"""
179+
180+
for input in inputs where input.kind == .file {
181+
let resourcePath = try AbsolutePath(validating: input.name)
182+
let variableName = resourcePath.basename.spm_mangledToC99ExtendedIdentifier()
183+
let fileContent = try Data(contentsOf: URL(fileURLWithPath: resourcePath.pathString)).map { String($0) }.joined(separator: ",")
184+
185+
content += "static let \(variableName): [UInt8] = [\(fileContent)]\n"
186+
}
187+
188+
content += "}"
189+
return content
190+
}
191+
}
162192
}
163193

164194
public struct LLBuildManifest {
@@ -280,6 +310,16 @@ public struct LLBuildManifest {
280310
commands[name] = Command(name: name, tool: tool)
281311
}
282312

313+
public mutating func addWriteEmbeddedResourcesCommand(
314+
resources: [AbsolutePath],
315+
outputPath: AbsolutePath
316+
) {
317+
let inputs = WriteAuxiliary.EmbeddedResources.computeInputs(resources: resources)
318+
let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath)
319+
let name = outputPath.pathString
320+
commands[name] = Command(name: name, tool: tool)
321+
}
322+
283323
public mutating func addPkgStructureCmd(
284324
name: String,
285325
inputs: [Node],

Tests/FunctionalTests/ResourcesTests.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,23 @@ class ResourcesTests: XCTestCase {
122122

123123
func testResourcesEmbeddedInCode() throws {
124124
try fixture(name: "Resources/EmbedInCodeSimple") { fixturePath in
125-
let result = try executeSwiftRun(fixturePath, "EmbedInCodeSimple")
126-
XCTAssertEqual(result.stdout, "hello world\n\n")
125+
let execPath = fixturePath.appending(components: ".build", "debug", "EmbedInCodeSimple")
126+
try executeSwiftBuild(fixturePath)
127+
let result = try Process.checkNonZeroExit(args: execPath.pathString)
128+
XCTAssertEqual(result, "hello world\n\n")
129+
let resourcePath = fixturePath.appending(
130+
components: "Sources", "EmbedInCodeSimple", "best.txt")
131+
132+
// Check incremental builds
133+
for i in 0..<2 {
134+
let content = "Hi there \(i)!"
135+
// Update the resource file.
136+
try localFileSystem.writeFileContents(resourcePath, string: content)
137+
try executeSwiftBuild(fixturePath)
138+
// Run the executable again.
139+
let result2 = try Process.checkNonZeroExit(args: execPath.pathString)
140+
XCTAssertEqual(result2, "\(content)\n")
141+
}
127142
}
128143
}
129144

0 commit comments

Comments
 (0)