Skip to content

Commit 47aefe7

Browse files
committed
Support vending products that are backed by binaryTargets
This adds support for vending an executable product that consists solely of a binary target that is backed by an artifact bundle. This allows vending binary executables as their own separate package, independently of the plugins that are using them. rdar://101096803
1 parent db9a253 commit 47aefe7

20 files changed

Lines changed: 272 additions & 83 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"schemaVersion": "1.0",
3+
"artifacts": {
4+
"mytool": {
5+
"type": "executable",
6+
"version": "1.2.3",
7+
"variants": [
8+
{
9+
"path": "mytool-macos/mytool",
10+
"supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"]
11+
},
12+
{
13+
"path": "mytool-linux/mytool",
14+
"supportedTriples": ["x86_64-unknown-linux-gnu"]
15+
}
16+
]
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
print_usage() {
4+
echo "usage: ${0##*/} [--verbose] <in> <out>"
5+
}
6+
7+
# Parse arguments until we find '--' or an argument that isn't an option.
8+
until [ $# -eq 0 ]
9+
do
10+
case "$1" in
11+
--verbose) verbose=1; shift;;
12+
--) shift; break;;
13+
-*) echo "unknown option: ${1}"; print_usage; exit 1; shift;;
14+
*) break;;
15+
esac
16+
done
17+
18+
# Print usage and leave if we don't have exactly two arguments.
19+
if [ $# -ne 2 ]; then
20+
print_usage
21+
exit 1
22+
fi
23+
24+
# For our sample tool we just copy from one to the other.
25+
if [ $verbose != 0 ]; then
26+
echo "[${0##*/}-linux] '$1' '$2'"
27+
fi
28+
29+
cp "$1" "$2"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
print_usage() {
4+
echo "usage: ${0##*/} [--verbose] <in> <out>"
5+
}
6+
7+
# Parse arguments until we find '--' or an argument that isn't an option.
8+
until [ $# -eq 0 ]
9+
do
10+
case "$1" in
11+
--verbose) verbose=1; shift;;
12+
--) shift; break;;
13+
-*) echo "unknown option: ${1}"; print_usage; exit 1; shift;;
14+
*) break;;
15+
esac
16+
done
17+
18+
# Print usage and leave if we don't have exactly two arguments.
19+
if [ $# -ne 2 ]; then
20+
print_usage
21+
exit 1
22+
fi
23+
24+
# For our sample tool we just copy from one to the other.
25+
if [ $verbose != 0 ]; then
26+
echo "[${0##*/}-macosx] '$1' '$2'"
27+
fi
28+
29+
cp "$1" "$2"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// swift-tools-version: 5.6
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "MyBinaryProduct",
6+
products: [
7+
.executable(
8+
name: "MyVendedSourceGenBuildTool",
9+
targets: ["MyVendedSourceGenBuildTool"]
10+
),
11+
],
12+
targets: [
13+
.binaryTarget(
14+
name: "MyVendedSourceGenBuildTool",
15+
path: "Binaries/MyVendedSourceGenBuildTool.artifactbundle"
16+
),
17+
]
18+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// swift-tools-version: 5.6
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "MyBinaryToolPlugin",
6+
dependencies: [
7+
.package(path: "Dependency"),
8+
],
9+
targets: [
10+
// A local tool that uses a build tool plugin.
11+
.executableTarget(
12+
name: "MyLocalTool",
13+
plugins: [
14+
"MySourceGenBuildToolPlugin",
15+
]
16+
),
17+
// The plugin that generates build tool commands to invoke MySourceGenBuildTool.
18+
.plugin(
19+
name: "MySourceGenBuildToolPlugin",
20+
capability: .buildTool(),
21+
dependencies: [
22+
.product(name: "MyVendedSourceGenBuildTool", package: "Dependency"),
23+
]
24+
),
25+
]
26+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import PackagePlugin
2+
3+
@main
4+
struct MyPlugin: BuildToolPlugin {
5+
6+
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
7+
print("Hello from the Build Tool Plugin!")
8+
guard let target = target as? SourceModuleTarget else { return [] }
9+
let inputFiles = target.sourceFiles.filter({ $0.path.extension == "dat" })
10+
return try inputFiles.map {
11+
let inputFile = $0
12+
let inputPath = inputFile.path
13+
let outputName = inputPath.stem + ".swift"
14+
let outputPath = context.pluginWorkDirectory.appending(outputName)
15+
return .buildCommand(
16+
displayName:
17+
"Generating \(outputName) from \(inputPath.lastComponent)",
18+
executable:
19+
try context.tool(named: "mytool").path,
20+
arguments: [
21+
"--verbose",
22+
"\(inputPath)",
23+
"\(outputPath)"
24+
],
25+
inputFiles: [
26+
inputPath,
27+
],
28+
outputFiles: [
29+
outputPath
30+
]
31+
)
32+
}
33+
}
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let foo = "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: '\(foo)'")

Sources/Build/BuildPlan.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,7 @@ public final class ProductBuildDescription {
14691469
// we will instead have generated a source file containing the redirect.
14701470
// Support for linking tests against executables is conditional on the tools
14711471
// version of the package that defines the executable product.
1472-
let executableTarget = try product.executableTarget()
1472+
let executableTarget = try product.executableTarget
14731473
if executableTarget.underlyingTarget is SwiftTarget, toolsVersion >= .v5_5,
14741474
buildParameters.canRenameEntrypointFunctionName {
14751475
if let flags = buildParameters.linkerFlagsForRenamingMainFunction(of: executableTarget) {
@@ -1988,9 +1988,8 @@ public class BuildPlan {
19881988
}
19891989

19901990
var productMap: [ResolvedProduct: ProductBuildDescription] = [:]
1991-
// Create product description for each product we have in the package graph except
1992-
// for automatic libraries and plugins, because they don't produce any output.
1993-
for product in graph.allProducts where product.type != .library(.automatic) && product.type != .plugin {
1991+
// Create product description for each product we have in the package graph that is eligible.
1992+
for product in graph.allProducts where product.shouldCreateProductDescription {
19941993
guard let package = graph.package(for: product) else {
19951994
throw InternalError("unknown package for \(product)")
19961995
}
@@ -2567,3 +2566,22 @@ extension ResolvedPackage {
25672566
}
25682567
}
25692568
}
2569+
2570+
extension ResolvedProduct {
2571+
private var isAutomaticLibrary: Bool {
2572+
return self.type == .library(.automatic)
2573+
}
2574+
2575+
private var isBinaryOnly: Bool {
2576+
return self.targets.filter({ !($0.underlyingTarget is BinaryTarget) }).isEmpty
2577+
}
2578+
2579+
private var isPlugin: Bool {
2580+
return self.type == .plugin
2581+
}
2582+
2583+
// We shouldn't create product descriptions for automatic libraries, plugins or products which consist solely of binary targets, because they don't produce any output.
2584+
fileprivate var shouldCreateProductDescription: Bool {
2585+
return !isAutomaticLibrary && !isBinaryOnly && !isPlugin
2586+
}
2587+
}

Sources/Build/LLBuildManifestBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ extension LLBuildManifestBuilder {
553553
if target.type == .executable {
554554
// FIXME: Optimize.
555555
let _product = try plan.graph.allProducts.first {
556-
try $0.type == .executable && $0.executableTarget() == target
556+
try $0.type == .executable && $0.executableTarget == target
557557
}
558558
if let product = _product {
559559
guard let planProduct = plan.productMap[product] else {

0 commit comments

Comments
 (0)