Skip to content

Commit ee7f064

Browse files
authored
Write link file list as a build command (#6606)
This moves the generation of link file lists into the build system instead of doing it ad-hoc outside of the build. For this, we have a new `WriteAuxiliaryFile` tool and associated command that should be usable for any kind of writing of auxiliary files during the build. Note that this change opted to not touch the existing infrastructure for in-process tools, so any inputs that are needed for the file generation will need to be flattened into a generic array of input nodes. The different types of file generation are keyed off a virtual node at the start of that array.
1 parent a4a5aa7 commit ee7f064

8 files changed

+230
-96
lines changed

Sources/Build/BuildDescription/ProductBuildDescription.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -336,20 +336,6 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
336336
return self.stripInvalidArguments(args)
337337
}
338338

339-
/// Writes link filelist to the filesystem.
340-
func writeLinkFilelist(_ fs: FileSystem) throws {
341-
var content = self.objects
342-
.map { $0.pathString.spm_shellEscaped() }
343-
.joined(separator: "\n")
344-
345-
// not sure this is needed, added here for backward compatibility
346-
if !content.isEmpty {
347-
content.append("\n")
348-
}
349-
350-
try fs.writeFileContents(self.linkFileListPath, string: content)
351-
}
352-
353339
/// Returns the build flags from the declared build settings.
354340
private func buildSettingsFlags() -> [String] {
355341
var flags: [String] = []

Sources/Build/BuildOperation.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ extension BuildDescription {
699699
let testDiscoveryCommands = llbuild.manifest.getCmdToolMap(kind: TestDiscoveryTool.self)
700700
let testEntryPointCommands = llbuild.manifest.getCmdToolMap(kind: TestEntryPointTool.self)
701701
let copyCommands = llbuild.manifest.getCmdToolMap(kind: CopyTool.self)
702+
let writeCommands = llbuild.manifest.getCmdToolMap(kind: WriteAuxiliaryFile.self)
702703

703704
// Create the build description.
704705
let buildDescription = try BuildDescription(
@@ -708,6 +709,7 @@ extension BuildDescription {
708709
testDiscoveryCommands: testDiscoveryCommands,
709710
testEntryPointCommands: testEntryPointCommands,
710711
copyCommands: copyCommands,
712+
writeCommands: writeCommands,
711713
pluginDescriptions: plan.pluginDescriptions
712714
)
713715
try fileSystem.createDirectory(

Sources/Build/BuildOperationBuildSystemDelegateHandler.swift

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ public struct BuildDescription: Codable {
303303
/// The map of copy commands.
304304
let copyCommands: [BuildManifest.CmdName: LLBuildManifest.CopyTool]
305305

306+
/// The map of write commands.
307+
let writeCommands: [BuildManifest.CmdName: LLBuildManifest.WriteAuxiliaryFile]
308+
306309
/// A flag that indicates this build should perform a check for whether targets only import
307310
/// their explicitly-declared dependencies
308311
let explicitTargetDependencyImportCheckingMode: BuildParameters.TargetDependencyImportCheckingMode
@@ -329,13 +332,15 @@ public struct BuildDescription: Codable {
329332
testDiscoveryCommands: [BuildManifest.CmdName: LLBuildManifest.TestDiscoveryTool],
330333
testEntryPointCommands: [BuildManifest.CmdName: LLBuildManifest.TestEntryPointTool],
331334
copyCommands: [BuildManifest.CmdName: LLBuildManifest.CopyTool],
335+
writeCommands: [BuildManifest.CmdName: LLBuildManifest.WriteAuxiliaryFile],
332336
pluginDescriptions: [PluginDescription]
333337
) throws {
334338
self.swiftCommands = swiftCommands
335339
self.swiftFrontendCommands = swiftFrontendCommands
336340
self.testDiscoveryCommands = testDiscoveryCommands
337341
self.testEntryPointCommands = testEntryPointCommands
338342
self.copyCommands = copyCommands
343+
self.writeCommands = writeCommands
339344
self.explicitTargetDependencyImportCheckingMode = plan.buildParameters
340345
.explicitTargetDependencyImportCheckingMode
341346
self.targetDependencyMap = try plan.targets.reduce(into: [TargetName: [TargetName]]()) {
@@ -465,6 +470,76 @@ public final class BuildExecutionContext {
465470
}
466471
}
467472

473+
final class WriteAuxiliaryFileCommand: CustomLLBuildCommand {
474+
override func getSignature(_ command: SPMLLBuild.Command) -> [UInt8] {
475+
guard let buildDescription = self.context.buildDescription else {
476+
return []
477+
}
478+
guard let tool = buildDescription.copyCommands[command.name] else {
479+
return []
480+
}
481+
482+
do {
483+
let encoder = JSONEncoder.makeWithDefaults()
484+
var hash = Data()
485+
hash += try encoder.encode(tool.inputs)
486+
hash += try encoder.encode(tool.outputs)
487+
return [UInt8](hash)
488+
} catch {
489+
self.context.observabilityScope.emit(error: "getSignature() failed: \(error.interpolationDescription)")
490+
return []
491+
}
492+
}
493+
494+
override func execute(
495+
_ command: SPMLLBuild.Command,
496+
_: SPMLLBuild.BuildSystemCommandInterface
497+
) -> Bool {
498+
let outputFilePath: AbsolutePath
499+
let tool: WriteAuxiliaryFile!
500+
501+
do {
502+
guard let buildDescription = self.context.buildDescription else {
503+
throw InternalError("unknown build description")
504+
}
505+
guard let _tool = buildDescription.writeCommands[command.name] else {
506+
throw StringError("command \(command.name) not registered")
507+
}
508+
tool = _tool
509+
510+
guard let output = tool.outputs.first, output.kind == .file else {
511+
throw StringError("invalid output path")
512+
}
513+
outputFilePath = try AbsolutePath(validating: output.name)
514+
} catch {
515+
self.context.observabilityScope.emit(error: "failed to write auxiliary file: \(error.interpolationDescription)")
516+
return false
517+
}
518+
519+
do {
520+
try self.context.fileSystem.writeFileContents(outputFilePath, string: getFileContents(tool: tool))
521+
return true
522+
} catch {
523+
self.context.observabilityScope.emit(error: "failed to write auxiliary file '\(outputFilePath.pathString)': \(error.interpolationDescription)")
524+
return false
525+
}
526+
}
527+
528+
func getFileContents(tool: WriteAuxiliaryFile) throws -> String {
529+
guard tool.inputs.first?.kind == .virtual, let generatedFileType = tool.inputs.first?.name.dropFirst().dropLast() else {
530+
throw StringError("invalid inputs")
531+
}
532+
533+
for fileType in WriteAuxiliary.fileTypes {
534+
if generatedFileType == fileType.name {
535+
return try fileType.getFileContents(inputs: Array(tool.inputs.dropFirst()))
536+
}
537+
}
538+
539+
throw InternalError("unhandled generated file type '\(generatedFileType)'")
540+
}
541+
}
542+
468543
public protocol PackageStructureDelegate {
469544
func packageStructureChanged() -> Bool
470545
}
@@ -584,6 +659,8 @@ final class BuildOperationBuildSystemDelegateHandler: LLBuildBuildSystemDelegate
584659
return InProcessTool(buildExecutionContext, type: PackageStructureCommand.self)
585660
case CopyTool.name:
586661
return InProcessTool(buildExecutionContext, type: CopyCommand.self)
662+
case WriteAuxiliaryFile.name:
663+
return InProcessTool(buildExecutionContext, type: WriteAuxiliaryFileCommand.self)
587664
default:
588665
return nil
589666
}

Sources/Build/BuildPlan.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -682,12 +682,6 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
682682
}
683683
buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths
684684

685-
// Write the link filelist file.
686-
//
687-
// FIXME: We should write this as a custom llbuild task once we adopt it
688-
// as a library.
689-
try buildProduct.writeLinkFilelist(fileSystem)
690-
691685
buildProduct.availableTools = dependencies.availableTools
692686
}
693687

Sources/Build/LLBuildManifestBuilder.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -989,13 +989,13 @@ extension LLBuildManifestBuilder {
989989
try self.manifest.addShellCmd(
990990
name: cmdName,
991991
description: "Archiving \(buildProduct.binaryPath.prettyPath())",
992-
inputs: buildProduct.objects.map(Node.file),
992+
inputs: (buildProduct.objects + [buildProduct.linkFileListPath]).map(Node.file),
993993
outputs: [.file(buildProduct.binaryPath)],
994994
arguments: try buildProduct.archiveArguments()
995995
)
996996

997997
default:
998-
let inputs = try buildProduct.objects + buildProduct.dylibs.map{ try $0.binaryPath }
998+
let inputs = try buildProduct.objects + buildProduct.dylibs.map{ try $0.binaryPath } + [buildProduct.linkFileListPath]
999999

10001000
try self.manifest.addShellCmd(
10011001
name: cmdName,
@@ -1023,6 +1023,8 @@ extension LLBuildManifestBuilder {
10231023
}
10241024
self.addNode(output, toTarget: .test)
10251025
}
1026+
1027+
self.manifest.addWriteLinkFileListCommand(objects: Array(buildProduct.objects), linkFileListPath: buildProduct.linkFileListPath)
10261028
}
10271029
}
10281030

Sources/LLBuildManifest/BuildManifest.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,46 @@
1212

1313
import Basics
1414

15+
public protocol AuxiliaryFileType {
16+
static var name: String { get }
17+
18+
static func getFileContents(inputs: [Node]) throws -> String
19+
}
20+
21+
public enum WriteAuxiliary {
22+
public static let fileTypes: [AuxiliaryFileType.Type] = [LinkFileList.self]
23+
24+
public struct LinkFileList: AuxiliaryFileType {
25+
public static let name = "link-file-list"
26+
27+
// FIXME: We should extend the `InProcessTool` support to allow us to specify these in a typed way, but today we have to flatten all the inputs into a generic `Node` array (rdar://109844243).
28+
public static func computeInputs(objects: [AbsolutePath]) -> [Node] {
29+
return [.virtual(Self.name)] + objects.map { Node.file($0) }
30+
}
31+
32+
public static func getFileContents(inputs: [Node]) throws -> String {
33+
let objects = inputs.compactMap {
34+
if $0.kind == .file {
35+
return $0.name
36+
} else {
37+
return nil
38+
}
39+
}
40+
41+
var content = objects
42+
.map { $0.spm_shellEscaped() }
43+
.joined(separator: "\n")
44+
45+
// not sure this is needed, added here for backward compatibility
46+
if !content.isEmpty {
47+
content.append("\n")
48+
}
49+
50+
return content
51+
}
52+
}
53+
}
54+
1555
public struct BuildManifest {
1656
public typealias TargetName = String
1757
public typealias CmdName = String
@@ -87,6 +127,16 @@ public struct BuildManifest {
87127
commands[name] = Command(name: name, tool: tool)
88128
}
89129

130+
public mutating func addWriteLinkFileListCommand(
131+
objects: [AbsolutePath],
132+
linkFileListPath: AbsolutePath
133+
) {
134+
let inputs = WriteAuxiliary.LinkFileList.computeInputs(objects: objects)
135+
let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: linkFileListPath)
136+
let name = linkFileListPath.pathString
137+
commands[name] = Command(name: name, tool: tool)
138+
}
139+
90140
public mutating func addPkgStructureCmd(
91141
name: String,
92142
inputs: [Node],

Sources/LLBuildManifest/Tools.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,26 @@ public struct ShellTool: ToolProtocol {
150150
}
151151
}
152152

153+
public struct WriteAuxiliaryFile: ToolProtocol {
154+
public static let name: String = "write-auxiliary-file"
155+
156+
public let inputs: [Node]
157+
private let outputFilePath: AbsolutePath
158+
159+
public init(inputs: [Node], outputFilePath: AbsolutePath) {
160+
self.inputs = inputs
161+
self.outputFilePath = outputFilePath
162+
}
163+
164+
public var outputs: [Node] {
165+
return [.file(outputFilePath)]
166+
}
167+
168+
public func write(to stream: ManifestToolStream) {
169+
stream["description"] = "Write auxiliary file \(outputFilePath.pathString)"
170+
}
171+
}
172+
153173
public struct ClangTool: ToolProtocol {
154174
public static let name: String = "clang"
155175

0 commit comments

Comments
 (0)