Skip to content

Commit 5fbbcb9

Browse files
committed
WIP: Support vending products that are backed by binaryTargets
Very rough poc, lots to clean up rdar://101096803
1 parent db9a253 commit 5fbbcb9

File tree

10 files changed

+80
-24
lines changed

10 files changed

+80
-24
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 1 addition & 1 deletion
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) {

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 {

Sources/Commands/SwiftPackageTool.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,7 @@ extension SwiftPackageTool {
10101010
try PluginCommand.run(
10111011
plugin: matchingPlugins[0],
10121012
package: packageGraph.rootPackages[0],
1013+
packageGraph: packageGraph,
10131014
options: pluginOptions,
10141015
arguments: arguments,
10151016
swiftTool: swiftTool)
@@ -1018,6 +1019,7 @@ extension SwiftPackageTool {
10181019
static func run(
10191020
plugin: PluginTarget,
10201021
package: ResolvedPackage,
1022+
packageGraph: PackageGraph,
10211023
options: PluginOptions,
10221024
arguments: [String],
10231025
swiftTool: SwiftTool
@@ -1084,10 +1086,21 @@ extension SwiftPackageTool {
10841086
let buildOperation = try swiftTool.createBuildOperation(cacheBuildManifest: false)
10851087
switch dep {
10861088
case .product(let productRef, _):
1087-
// Build the product referenced by the tool, and add the executable to the tool map.
1088-
try buildOperation.build(subset: .product(productRef.name))
1089-
if let builtTool = buildOperation.buildPlan?.buildProducts.first(where: { $0.product.name == productRef.name}) {
1090-
toolNamesToPaths[productRef.name] = builtTool.binary
1089+
guard let product = packageGraph.reachableProducts.first(where: { $0.name == productRef.name }) else {
1090+
// TODO: proper error handling
1091+
fatalError("no product named \(productRef.name)")
1092+
}
1093+
if let target = product.targets.first?.underlyingTarget as? BinaryTarget, target.containsExecutable {
1094+
// FIXME: consolidate with `case .target` down below
1095+
for exec in try target.parseArtifactArchives(for: pluginScriptRunner.hostTriple, fileSystem: swiftTool.fileSystem) {
1096+
toolNamesToPaths[exec.name] = exec.executablePath
1097+
}
1098+
} else {
1099+
// Build the product referenced by the tool, and add the executable to the tool map.
1100+
try buildOperation.build(subset: .product(productRef.name))
1101+
if let builtTool = buildOperation.buildPlan?.buildProducts.first(where: { $0.product.name == productRef.name}) {
1102+
toolNamesToPaths[productRef.name] = builtTool.binary
1103+
}
10911104
}
10921105
case .target(let target, _):
10931106
if let target = target as? BinaryTarget {
@@ -1536,6 +1549,7 @@ extension SwiftPackageTool {
15361549
try PluginCommand.run(
15371550
plugin: matchingPlugins[0],
15381551
package: packageGraph.rootPackages[0],
1552+
packageGraph: packageGraph,
15391553
options: pluginOptions,
15401554
arguments: Array( remaining.dropFirst()),
15411555
swiftTool: swiftTool)

Sources/PackageGraph/ResolvedProduct.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,16 @@ public final class ResolvedProduct {
4343
/// The main executable target of product.
4444
///
4545
/// Note: This property is only valid for executable products.
46-
public func executableTarget() throws -> ResolvedTarget {
47-
guard type == .executable || type == .snippet else {
48-
throw InternalError("firstExecutableModule should only be called for executable targets")
46+
public var executableTarget: ResolvedTarget {
47+
get throws {
48+
guard type == .executable || type == .snippet else {
49+
throw InternalError("`executableTarget` should only be called for executable targets")
50+
}
51+
guard let underlyingExecutableTarget = targets.map({ $0.underlyingTarget }).executables.first, let executableTarget = targets.first(where: { $0.underlyingTarget == underlyingExecutableTarget }) else {
52+
throw InternalError("could not determine executable target")
53+
}
54+
return executableTarget
4955
}
50-
return targets.first(where: { $0.type == .executable || $0.type == .snippet })!
5156
}
5257

5358
public init(product: Product, targets: [ResolvedTarget]) {

Sources/PackageLoading/ManifestLoader+Validation.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@ public struct ManifestValidator {
7878
}
7979
}
8080

81-
// Check that products that reference only binary targets don't define a type.
82-
let areTargetsBinary = product.targets.allSatisfy { self.manifest.targetMap[$0]?.type == .binary }
83-
if areTargetsBinary && product.type != .library(.automatic) {
84-
diagnostics.append(.invalidBinaryProductType(productName: product.name))
81+
// Check that products that reference only binary targets don't define an explicit library type.
82+
if product.targets.allSatisfy({ self.manifest.targetMap[$0]?.type == .binary }) {
83+
switch product.type {
84+
case .library(.automatic), .executable:
85+
break
86+
default:
87+
diagnostics.append(.invalidBinaryProductType(productName: product.name))
88+
}
8589
}
8690
}
8791

@@ -262,7 +266,7 @@ extension Basics.Diagnostic {
262266
}
263267

264268
static func invalidBinaryProductType(productName: String) -> Self {
265-
.error("invalid type for binary product '\(productName)'; products referencing only binary targets must have a type of 'library'")
269+
.error("invalid type for binary product '\(productName)'; products referencing only binary targets must be executable or automatic library products")
266270
}
267271

268272
/*static func duplicateDependency(dependencyIdentity: PackageIdentity) -> Self {

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,7 @@ public final class PackageBuilder {
12901290
}
12911291

12921292
private func validateExecutableProduct(_ product: ProductDescription, with targets: [Target]) -> Bool {
1293-
let executableTargetCount = targets.filter { $0.type == .executable }.count
1293+
let executableTargetCount = targets.executables.count
12941294
guard executableTargetCount == 1 else {
12951295
if executableTargetCount == 0 {
12961296
if let target = targets.spm_only {

Sources/PackageModel/Product.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class Product: Codable {
4343
throw InternalError("Targets cannot be empty")
4444
}
4545
if type == .executable {
46-
guard targets.filter({ $0.type == .executable }).count == 1 else {
46+
guard targets.executables.count == 1 else {
4747
throw InternalError("Executable products should have exactly one executable target.")
4848
}
4949
}

Sources/PackageModel/Target.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,11 @@ public final class BinaryTarget: Target {
638638
}
639639
}
640640

641+
// FIXME: preliminary, this should do `parseArtifactArchives` for actual verification
642+
public var containsExecutable: Bool {
643+
return self.kind == .artifactsArchive
644+
}
645+
641646
public enum Origin: Equatable, Codable {
642647

643648
/// Represents an artifact that was downloaded from a remote URL.
@@ -799,3 +804,18 @@ public enum PluginPermission: Hashable, Codable {
799804
}
800805
}
801806
}
807+
808+
public extension Sequence where Iterator.Element == Target {
809+
var executables: [Target] {
810+
return filter {
811+
switch $0.type {
812+
case .binary:
813+
return ($0 as? BinaryTarget)?.containsExecutable == true
814+
case .executable:
815+
return true
816+
default:
817+
return false
818+
}
819+
}
820+
}
821+
}

Sources/SPMBuildCore/PluginContextSerializer.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,7 @@ internal struct PluginContextSerializer {
184184
switch product.type {
185185

186186
case .executable:
187-
guard let mainExecTarget = product.targets.first(where: { $0.type == .executable }) else {
188-
throw InternalError("could not determine main executable target for product \(product)")
189-
}
187+
let mainExecTarget = try product.executableTarget
190188
guard let mainExecTargetId = try serialize(target: mainExecTarget) else {
191189
throw InternalError("unable to serialize main executable target \(mainExecTarget) for product \(product)")
192190
}

Sources/SPMBuildCore/PluginInvocation.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ extension PackageGraph {
355355
for pluginTarget in pluginTargets {
356356
// Determine the tools to which this plugin has access, and create a name-to-path mapping from tool
357357
// names to the corresponding paths. Built tools are assumed to be in the build tools directory.
358-
let accessibleTools = pluginTarget.accessibleTools(fileSystem: fileSystem, environment: buildEnvironment, for: try pluginScriptRunner.hostTriple)
358+
let accessibleTools = pluginTarget.accessibleTools(packageGraph: self, fileSystem: fileSystem, environment: buildEnvironment, for: try pluginScriptRunner.hostTriple)
359359
let toolNamesToPaths = accessibleTools.reduce(into: [String: AbsolutePath](), { dict, tool in
360360
switch tool {
361361
case .builtTool(let name, let path):
@@ -501,8 +501,10 @@ public extension PluginTarget {
501501
return self.dependencies.filter { $0.satisfies(environment) }
502502
}
503503

504+
// TODO: feels like this should be consolidated with the code in `SwiftPackageTool`
505+
504506
/// The set of tools that are accessible to this plugin.
505-
func accessibleTools(fileSystem: FileSystem, environment: BuildEnvironment, for hostTriple: Triple) -> Set<PluginAccessibleTool> {
507+
func accessibleTools(packageGraph: PackageGraph, fileSystem: FileSystem, environment: BuildEnvironment, for hostTriple: Triple) -> Set<PluginAccessibleTool> {
506508
return Set(self.dependencies(satisfying: environment).flatMap { dependency -> [PluginAccessibleTool] in
507509
switch dependency {
508510
case .target(let target, _):
@@ -523,8 +525,21 @@ public extension PluginTarget {
523525
else {
524526
return []
525527
}
526-
case .product(let product, _):
527-
return [.builtTool(name: product.name, path: RelativePath(product.name))]
528+
case .product(let productRef, _):
529+
guard let product = packageGraph.reachableProducts.first(where: { $0.name == productRef.name }) else {
530+
// TODO: proper error handling
531+
fatalError("no product named \(productRef.name)")
532+
}
533+
if let target = product.targets.first?.underlyingTarget as? BinaryTarget, target.containsExecutable {
534+
// FIXME: consolidate with `case .target` down above
535+
guard let execInfos = try? target.parseArtifactArchives(for: hostTriple, fileSystem: fileSystem) else {
536+
// TODO: Deal better with errors in parsing the artifacts
537+
return []
538+
}
539+
return execInfos.map{ .vendedTool(name: $0.name, path: $0.executablePath) }
540+
} else {
541+
return [.builtTool(name: productRef.name, path: RelativePath(productRef.name))]
542+
}
528543
}
529544
})
530545
}

0 commit comments

Comments
 (0)