Skip to content

Commit 85f81f3

Browse files
authored
Use the new LLBuildManifestWriter support to pass through shell script build phase environment and working directory for build tool commands. (#3333)
Support for working directories is temporarily marked as unavailable since llbuild doesn't currently support it on platforms other than Darwin. This restriction will be removed when llbuild starts to support it. rdar://74663489
1 parent 953f368 commit 85f81f3

File tree

8 files changed

+123
-4
lines changed

8 files changed

+123
-4
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// swift-tools-version: 999.0
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "ContrivedTestPlugin",
6+
targets: [
7+
// A local tool that uses a build tool plugin.
8+
.executableTarget(
9+
name: "MyLocalTool",
10+
dependencies: [
11+
"MySourceGenBuildToolPlugin",
12+
]
13+
),
14+
// The plugin that generates build tool commands to invoke MySourceGenBuildTool.
15+
.plugin(
16+
name: "MySourceGenBuildToolPlugin",
17+
capability: .buildTool(),
18+
dependencies: [
19+
"MySourceGenBuildTool",
20+
]
21+
),
22+
// The command line tool that generates source files.
23+
.executableTarget(
24+
name: "MySourceGenBuildTool"
25+
),
26+
]
27+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I am Foo!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("Generated string Foo: '\(PREFIX_foo)'")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
// Sample source generator tool that emits a Swift variable declaration of a string containing the hex representation of the contents of a file as a quoted string. The variable name is the base name of the input file, with the value from an environment value prepended. The input file is the first argument and the output file is the second.
4+
if ProcessInfo.processInfo.arguments.count != 3 {
5+
print("usage: MySourceGenBuildTool <input> <output>")
6+
exit(1)
7+
}
8+
let inputFile = ProcessInfo.processInfo.arguments[1]
9+
let outputFile = ProcessInfo.processInfo.arguments[2]
10+
11+
let variablePrefix = ProcessInfo.processInfo.environment["VARIABLE_NAME_PREFIX"] ?? ""
12+
let variableName = URL(fileURLWithPath: inputFile).deletingPathExtension().lastPathComponent
13+
14+
let inputData = FileManager.default.contents(atPath: inputFile) ?? Data()
15+
let dataAsHex = inputData.map { String(format: "%02hhx", $0) }.joined()
16+
let outputString = "public var \(variablePrefix)\(variableName) = \(dataAsHex.quotedForSourceCode)\n"
17+
let outputData = outputString.data(using: .utf8)
18+
FileManager.default.createFile(atPath: outputFile, contents: outputData)
19+
20+
extension String {
21+
22+
public var quotedForSourceCode: String {
23+
return "\"" + self
24+
.replacingOccurrences(of: "\\", with: "\\\\")
25+
.replacingOccurrences(of: "\"", with: "\\\"")
26+
+ "\""
27+
}
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import PackagePlugin
2+
3+
print("Hello from the Build Tool Plugin!")
4+
5+
for inputPath in targetBuildContext.otherFiles {
6+
guard inputPath.suffix == ".dat" else { continue }
7+
let outputName = inputPath.basename + ".swift"
8+
let outputPath = targetBuildContext.outputDir.appending(outputName)
9+
commandConstructor.createCommand(
10+
displayName:
11+
"Generating \(outputName) from \(inputPath.filename)",
12+
executable:
13+
try targetBuildContext.lookupTool(named: "MySourceGenBuildTool"),
14+
arguments: [
15+
"\(inputPath)",
16+
"\(outputPath)"
17+
],
18+
environment: [
19+
"VARIABLE_NAME_PREFIX": "PREFIX_"
20+
],
21+
inputPaths: [
22+
inputPath,
23+
],
24+
outputPaths: [
25+
outputPath
26+
]
27+
)
28+
commandConstructor.addGeneratedOutputFile(path: outputPath)
29+
}

Sources/Build/ManifestBuilder.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,16 +584,17 @@ extension LLBuildManifestBuilder {
584584

585585
// Add any build tool commands created by plugins for the target (prebuild and postbuild commands are handled outside the build).
586586
for command in target.pluginInvocationResults.reduce([], { $0 + $1.commands }) {
587-
if case .buildToolCommand(let displayName, let executable, let arguments, _, _, let inputPaths, let outputPaths) = command {
587+
if case .buildToolCommand(let displayName, let executable, let arguments, let environment, let workingDirectory, let inputPaths, let outputPaths) = command {
588588
// Create a shell command to invoke the executable. We include the path of the executable as a dependency.
589-
// FIXME: We will need to extend the addShellCmd() function to also take working directory and environment.
590589
let execPath = AbsolutePath(executable, relativeTo: buildParameters.buildPath)
591590
manifest.addShellCmd(
592591
name: displayName,
593592
description: displayName,
594593
inputs: [.file(execPath)] + inputPaths.map{ .file($0) },
595594
outputs: outputPaths.map{ .file($0) },
596-
args: [execPath.pathString] + arguments)
595+
args: [execPath.pathString] + arguments,
596+
environ: environment,
597+
workingDir: workingDirectory?.pathString)
597598
}
598599
}
599600

Sources/PackagePlugin/PublicAPI/CommandConstructor.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ public final class CommandConstructor {
2323
/// Note that input and output dependencies are ignored for prebuild and
2424
/// postbuild actions, since they always run before and after the build
2525
/// respectively.
26+
public func createCommand(
27+
displayName: String?,
28+
executable: Path,
29+
arguments: [String],
30+
environment: [String: String]? = nil,
31+
inputPaths: [Path] = [],
32+
outputPaths: [Path] = []
33+
) {
34+
output.commands.append(Command(displayName: displayName, executable: executable, arguments: arguments, environment: environment, workingDirectory: nil, inputPaths: inputPaths, outputPaths: outputPaths))
35+
}
36+
37+
@available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported")
2638
public func createCommand(
2739
displayName: String?,
2840
executable: Path,
@@ -32,7 +44,7 @@ public final class CommandConstructor {
3244
inputPaths: [Path] = [],
3345
outputPaths: [Path] = []
3446
) {
35-
output.commands.append(Command(displayName: displayName, executable: executable, arguments: arguments, environment: environment, workingDirectory: workingDirectory, inputPaths: inputPaths, outputPaths: outputPaths))
47+
self.createCommand(displayName: displayName, executable: executable, arguments: arguments, environment: environment, inputPaths: inputPaths, outputPaths: outputPaths)
3648
}
3749

3850
/// Registers the path of an output file generated by a `buildTool` plugin

Tests/FunctionalTests/PluginTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,24 @@ class PluginTests: XCTestCase {
7070
}
7171
}
7272
}
73+
74+
func testContrivedTestCases() throws {
75+
// Check if the host compiler supports the '-entry-point-function-name' flag. It's not needed for this test but is needed to build any executable from a package that uses tools version 999.0.
76+
try XCTSkipUnless(doesHostSwiftCompilerSupportRenamingMainSymbol(), "skipping because host compiler doesn't support '-entry-point-function-name'")
77+
78+
fixture(name: "Miscellaneous/Plugins") { path in
79+
do {
80+
let (stdout, _) = try executeSwiftBuild(path.appending(component: "ContrivedTestPlugin"), configuration: .Debug, extraArgs: ["--product", "MyLocalTool"], env: ["SWIFTPM_ENABLE_PLUGINS": "1"])
81+
XCTAssert(stdout.contains("Linking MySourceGenBuildTool"), "stdout:\n\(stdout)")
82+
XCTAssert(stdout.contains("Generating foo.swift from foo.dat"), "stdout:\n\(stdout)")
83+
XCTAssert(stdout.contains("Linking MyLocalTool"), "stdout:\n\(stdout)")
84+
XCTAssert(stdout.contains("Build complete!"), "stdout:\n\(stdout)")
85+
}
86+
catch {
87+
print(error)
88+
throw error
89+
}
90+
}
91+
}
92+
7393
}

0 commit comments

Comments
 (0)