Skip to content

Fix incremental builds for embedInCode resources #7616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 11 additions & 27 deletions Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ public final class SwiftTargetBuildDescription {
return resources.filter { $0.rule != .embedInCode }.isEmpty == false
}

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

/// The path to Swift source file embedding resource contents if needed.
private(set) var resourcesEmbeddingSource: AbsolutePath?

/// The list of all source files in the target, including the derived ones.
public var sources: [AbsolutePath] {
self.target.sources.paths + self.derivedSources.paths + self.pluginDerivedSources.paths
Expand Down Expand Up @@ -317,7 +320,10 @@ public final class SwiftTargetBuildDescription {
}
}

try self.generateResourceEmbeddingCode()
if !resourceFilesToEmbed.isEmpty {
resourcesEmbeddingSource = try addResourceEmbeddingSource()
}

try self.generateTestObservation()
}

Expand Down Expand Up @@ -348,32 +354,10 @@ public final class SwiftTargetBuildDescription {
try self.fileSystem.writeIfChanged(path: path, string: content)
}

// 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.
private func generateResourceEmbeddingCode() throws {
guard needsResourceEmbedding else { return }

var content =
"""
struct PackageResources {

"""

try resources.forEach {
guard $0.rule == .embedInCode else { return }

let variableName = $0.path.basename.spm_mangledToC99ExtendedIdentifier()
let fileContent = try Data(contentsOf: URL(fileURLWithPath: $0.path.pathString)).map { String($0) }.joined(separator: ",")

content += "static let \(variableName): [UInt8] = [\(fileContent)]\n"
}

content += "}"

private func addResourceEmbeddingSource() throws -> AbsolutePath {
let subpath = try RelativePath(validating: "embedded_resources.swift")
self.derivedSources.relativePaths.append(subpath)
let path = self.derivedSources.root.appending(subpath)
try self.fileSystem.writeIfChanged(path: path, string: content)
return self.derivedSources.root.appending(subpath)
}

/// Generate the resource bundle accessor, if appropriate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,11 @@ extension LLBuildManifestBuilder {
inputs.append(resourcesNode)
}

if let resourcesEmbeddingSource = target.resourcesEmbeddingSource {
let resourceFilesToEmbed = target.resourceFilesToEmbed
self.manifest.addWriteEmbeddedResourcesCommand(resources: resourceFilesToEmbed, outputPath: resourcesEmbeddingSource)
}

func addStaticTargetInputs(_ target: ResolvedModule) throws {
// Ignore C Modules.
if target.underlying is SystemLibraryTarget { return }
Expand Down
42 changes: 41 additions & 1 deletion Sources/LLBuildManifest/LLBuildManifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public enum WriteAuxiliary {
LinkFileList.self,
SourcesFileList.self,
SwiftGetVersion.self,
XCTestInfoPlist.self
XCTestInfoPlist.self,
EmbeddedResources.self,
]

public struct EntitlementPlist: AuxiliaryFileType {
Expand Down Expand Up @@ -159,6 +160,35 @@ public enum WriteAuxiliary {
case undefinedPrincipalClass
}
}

public struct EmbeddedResources: AuxiliaryFileType {
public static let name = "embedded-resources"

public static func computeInputs(resources: [AbsolutePath]) -> [Node] {
return [.virtual(Self.name)] + resources.map { Node.file($0) }
}

// FIXME: This will not work well for large files, as we will store the entire contents, plus its byte array
// representation in memory.
public static func getFileContents(inputs: [Node]) throws -> String {
var content =
"""
struct PackageResources {

"""

for input in inputs where input.kind == .file {
let resourcePath = try AbsolutePath(validating: input.name)
let variableName = resourcePath.basename.spm_mangledToC99ExtendedIdentifier()
let fileContent = try Data(contentsOf: URL(fileURLWithPath: resourcePath.pathString)).map { String($0) }.joined(separator: ",")

content += "static let \(variableName): [UInt8] = [\(fileContent)]\n"
}

content += "}"
return content
}
}
}

public struct LLBuildManifest {
Expand Down Expand Up @@ -280,6 +310,16 @@ public struct LLBuildManifest {
commands[name] = Command(name: name, tool: tool)
}

public mutating func addWriteEmbeddedResourcesCommand(
resources: [AbsolutePath],
outputPath: AbsolutePath
) {
let inputs = WriteAuxiliary.EmbeddedResources.computeInputs(resources: resources)
let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath)
let name = outputPath.pathString
commands[name] = Command(name: name, tool: tool)
}

public mutating func addPkgStructureCmd(
name: String,
inputs: [Node],
Expand Down
19 changes: 17 additions & 2 deletions Tests/FunctionalTests/ResourcesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,23 @@ class ResourcesTests: XCTestCase {

func testResourcesEmbeddedInCode() throws {
try fixture(name: "Resources/EmbedInCodeSimple") { fixturePath in
let result = try executeSwiftRun(fixturePath, "EmbedInCodeSimple")
XCTAssertEqual(result.stdout, "hello world\n\n")
let execPath = fixturePath.appending(components: ".build", "debug", "EmbedInCodeSimple")
try executeSwiftBuild(fixturePath)
let result = try Process.checkNonZeroExit(args: execPath.pathString)
XCTAssertEqual(result, "hello world\n\n")
let resourcePath = fixturePath.appending(
components: "Sources", "EmbedInCodeSimple", "best.txt")

// Check incremental builds
for i in 0..<2 {
let content = "Hi there \(i)!"
// Update the resource file.
try localFileSystem.writeFileContents(resourcePath, string: content)
try executeSwiftBuild(fixturePath)
// Run the executable again.
let result2 = try Process.checkNonZeroExit(args: execPath.pathString)
XCTAssertEqual(result2, "\(content)\n")
}
}
}

Expand Down