Skip to content

Commit 734e28d

Browse files
committed
WIP: Write link file list as a build command
1 parent 2d072e4 commit 734e28d

File tree

8 files changed

+120
-38
lines changed

8 files changed

+120
-38
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: 66 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,65 @@ public final class BuildExecutionContext {
465470
}
466471
}
467472

473+
final class WriteLinkFilelistCommand: 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+
let encoder = JSONEncoder.makeWithDefaults()
483+
var hash = Data()
484+
hash += try! encoder.encode(tool.inputs)
485+
hash += try! encoder.encode(tool.outputs)
486+
return [UInt8](hash)
487+
}
488+
489+
override func execute(
490+
_ command: SPMLLBuild.Command,
491+
_: SPMLLBuild.BuildSystemCommandInterface
492+
) -> Bool {
493+
do {
494+
guard let buildDescription = self.context.buildDescription else {
495+
throw InternalError("unknown build description")
496+
}
497+
guard let tool = buildDescription.writeCommands[command.name] else {
498+
throw StringError("command \(command.name) not registered")
499+
}
500+
501+
guard let output = tool.outputs.first, output.kind == .file else {
502+
throw StringError("invalid output path")
503+
}
504+
let linkFileListPath = try AbsolutePath(validating: output.name)
505+
506+
let objects = tool.inputs.compactMap {
507+
if $0.kind == .file {
508+
return $0.name
509+
} else {
510+
return nil
511+
}
512+
}
513+
514+
var content = objects
515+
.map { $0.spm_shellEscaped() }
516+
.joined(separator: "\n")
517+
518+
// not sure this is needed, added here for backward compatibility
519+
if !content.isEmpty {
520+
content.append("\n")
521+
}
522+
523+
try self.context.fileSystem.writeFileContents(linkFileListPath, string: content)
524+
return true
525+
} catch {
526+
self.context.observabilityScope.emit(error: "failed to write link file list: \(error.interpolationDescription)")
527+
return false
528+
}
529+
}
530+
}
531+
468532
public protocol PackageStructureDelegate {
469533
func packageStructureChanged() -> Bool
470534
}
@@ -584,6 +648,8 @@ final class BuildOperationBuildSystemDelegateHandler: LLBuildBuildSystemDelegate
584648
return InProcessTool(buildExecutionContext, type: PackageStructureCommand.self)
585649
case CopyTool.name:
586650
return InProcessTool(buildExecutionContext, type: CopyCommand.self)
651+
case WriteAuxiliaryFile.name:
652+
return InProcessTool(buildExecutionContext, type: WriteLinkFilelistCommand.self)
587653
default:
588654
return nil
589655
}

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.addWriteAuxiliaryFileCommand(objects: Array(buildProduct.objects), linkFileListPath: buildProduct.linkFileListPath)
10261028
}
10271029
}
10281030

Sources/LLBuildManifest/BuildManifest.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ public struct BuildManifest {
8787
commands[name] = Command(name: name, tool: tool)
8888
}
8989

90+
public mutating func addWriteAuxiliaryFileCommand(
91+
objects: [AbsolutePath],
92+
linkFileListPath: AbsolutePath
93+
) {
94+
let tool = WriteAuxiliaryFile(objects: objects, linkFileListPath: linkFileListPath)
95+
let name = linkFileListPath.pathString
96+
commands[name] = Command(name: name, tool: tool)
97+
}
98+
9099
public mutating func addPkgStructureCmd(
91100
name: String,
92101
inputs: [Node],

Sources/LLBuildManifest/Tools.swift

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

153+
public struct WriteAuxiliaryFile: ToolProtocol {
154+
public static let name: String = "write-link-file-list"
155+
156+
private let objects: [AbsolutePath]
157+
private let linkFileListPath: AbsolutePath
158+
159+
public init(objects: [AbsolutePath], linkFileListPath: AbsolutePath) {
160+
self.objects = objects
161+
self.linkFileListPath = linkFileListPath
162+
}
163+
164+
public var inputs: [Node] {
165+
return self.objects.map { Node.file($0) }
166+
}
167+
168+
public var outputs: [Node] {
169+
return [Node.file(linkFileListPath)]
170+
}
171+
172+
public func write(to stream: ManifestToolStream) {
173+
stream["description"] = "yolo"
174+
}
175+
}
176+
153177
public struct ClangTool: ToolProtocol {
154178
public static let name: String = "clang"
155179

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -865,9 +865,11 @@ final class BuildPlanTests: XCTestCase {
865865

866866
let buildPath: AbsolutePath = plan.buildParameters.dataPath.appending(components: "release")
867867

868-
let linkedFileList: String = try fs.readFileContents("/path/to/build/release/exe.product/Objects.LinkFileList")
869-
XCTAssertMatch(linkedFileList, .contains("PkgLib"))
870-
XCTAssertNoMatch(linkedFileList, .contains("ExtLib"))
868+
let result = try BuildPlanResult(plan: plan)
869+
let buildProduct = try result.buildProduct(for: "exe")
870+
let objectDirectoryNames = buildProduct.objects.map { $0.parentDirectory.basename }
871+
XCTAssertTrue(objectDirectoryNames.contains("PkgLib.build"))
872+
XCTAssertFalse(objectDirectoryNames.contains("ExtLib.build"))
871873

872874
let yaml = try fs.tempDirectory.appending(components: UUID().uuidString, "release.yaml")
873875
try fs.createDirectory(yaml.parentDirectory, recursive: true)
@@ -891,9 +893,11 @@ final class BuildPlanTests: XCTestCase {
891893
observabilityScope: observability.topScope
892894
)
893895

894-
let linkedFileList: String = try fs.readFileContents("/path/to/build/debug/exe.product/Objects.LinkFileList")
895-
XCTAssertNoMatch(linkedFileList, .contains("PkgLib"))
896-
XCTAssertNoMatch(linkedFileList, .contains("ExtLib"))
896+
let result = try BuildPlanResult(plan: plan)
897+
let buildProduct = try result.buildProduct(for: "exe")
898+
let objectDirectoryNames = buildProduct.objects.map { $0.parentDirectory.basename }
899+
XCTAssertFalse(objectDirectoryNames.contains("PkgLib.build"))
900+
XCTAssertFalse(objectDirectoryNames.contains("ExtLib.build"))
897901

898902
let yaml = try fs.tempDirectory.appending(components: UUID().uuidString, "debug.yaml")
899903
try fs.createDirectory(yaml.parentDirectory, recursive: true)
@@ -1260,13 +1264,8 @@ final class BuildPlanTests: XCTestCase {
12601264
])
12611265
#endif
12621266

1263-
let linkedFileList: String = try fs.readFileContents(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))
1264-
XCTAssertEqual(linkedFileList, """
1265-
\(buildPath.appending(components: "exe.build", "main.c.o"))
1266-
\(buildPath.appending(components: "extlib.build", "extlib.c.o"))
1267-
\(buildPath.appending(components: "lib.build", "lib.c.o"))
1268-
1269-
""")
1267+
let buildProduct = try XCTUnwrap(result.productMap["exe"])
1268+
XCTAssertEqual(Array(buildProduct.objects), [buildPath.appending(components: "exe.build", "main.c.o"), buildPath.appending(components: "extlib.build", "extlib.c.o"), buildPath.appending(components: "lib.build", "lib.c.o")])
12701269
}
12711270

12721271
func testClangConditionalDependency() throws {
@@ -4053,7 +4052,7 @@ final class BuildPlanTests: XCTestCase {
40534052
XCTAssertMatch(contents, .contains("""
40544053
"C.rary-debug.a":
40554054
tool: shell
4056-
inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString())"]
4055+
inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString())",,"\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"]
40574056
outputs: ["\(buildPath.appending(components: "library.a").escapedPathString())"]
40584057
description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString())"
40594058
args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString())","/LIB","/OUT:\(buildPath.appending(components: "library.a").escapedPathString())","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"]
@@ -4062,7 +4061,7 @@ final class BuildPlanTests: XCTestCase {
40624061
XCTAssertMatch(contents, .contains("""
40634062
"C.rary-debug.a":
40644063
tool: shell
4065-
inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())"]
4064+
inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"]
40664065
outputs: ["\(buildPath.appending(components: "library.a").escapedPathString())"]
40674066
description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString())"
40684067
args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString())","-static","-o","\(buildPath.appending(components: "library.a").escapedPathString())","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"]
@@ -4071,7 +4070,7 @@ final class BuildPlanTests: XCTestCase {
40714070
XCTAssertMatch(contents, .contains("""
40724071
"C.rary-debug.a":
40734072
tool: shell
4074-
inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString())"]
4073+
inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString())",,"\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"]
40754074
outputs: ["\(buildPath.appending(components: "library.a").escapedPathString())"]
40764075
description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString())"
40774076
args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString())","crs","\(buildPath.appending(components: "library.a").escapedPathString())","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"]

0 commit comments

Comments
 (0)