1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import struct Basics.AbsolutePath
import struct Basics.Triple
import struct Basics.InternalError
import struct PackageGraph.ResolvedProduct
import struct PackageGraph.ResolvedModule
import class PackageModel.BinaryModule
import class PackageModel.ClangModule
@_spi(SwiftPMInternal)
import class PackageModel.Module
import class PackageModel.SwiftModule
import class PackageModel.SystemLibraryModule
import struct SPMBuildCore.BuildParameters
import struct SPMBuildCore.ExecutableInfo
import func TSCBasic.topologicalSort
extension BuildPlan {
/// Plan a product.
func plan(buildProduct: ProductBuildDescription) throws {
// Compute the product's dependency.
let dependencies = try computeDependencies(
of: buildProduct.product,
buildParameters: buildProduct.buildParameters
)
// Add flags for system targets.
for systemModule in dependencies.systemModules {
guard case let target as SystemLibraryModule = systemModule.underlying else {
throw InternalError("This should not be possible.")
}
// Add pkgConfig libs arguments.
buildProduct.additionalFlags += try pkgConfig(for: target).libs
}
// Add flags for binary dependencies.
for binaryPath in dependencies.libraryBinaryPaths {
if binaryPath.extension == "framework" {
buildProduct.additionalFlags += ["-framework", binaryPath.basenameWithoutExt]
} else if binaryPath.basename.starts(with: "lib") {
buildProduct.additionalFlags += ["-l\(binaryPath.basenameWithoutExt.dropFirst(3))"]
} else {
self.observabilityScope.emit(error: "unexpected binary framework")
}
}
// Don't link libc++ or libstd++ when building for Embedded Swift.
// Users can still link it manually for embedded platforms when needed,
// by providing `-Xlinker -lc++` options via CLI or `Package.swift`.
if !buildProduct.product.modules.contains(where: \.underlying.isEmbeddedSwiftTarget) {
// Link C++ if needed.
// Note: This will come from build settings in future.
for target in dependencies.staticTargets {
if case let target as ClangModule = target.underlying, target.isCXX {
let triple = buildProduct.buildParameters.triple
if triple.isDarwin() {
buildProduct.additionalFlags += ["-lc++"]
} else if triple.isWindows() {
// Don't link any C++ library.
} else {
buildProduct.additionalFlags += ["-lstdc++"]
}
break
}
}
}
for target in dependencies.staticTargets {
switch target.underlying {
case is SwiftModule:
// Swift targets are guaranteed to have a corresponding Swift description.
guard case .swift(let description) = self.targetMap[target.id] else {
throw InternalError("unknown target \(target)")
}
// Based on the debugging strategy, we either need to pass swiftmodule paths to the
// product or link in the wrapped module object. This is required for properly debugging
// Swift products. Debugging strategy is computed based on the current platform we're
// building for and is nil for the release configuration.
switch buildProduct.buildParameters.debuggingStrategy {
case .swiftAST:
buildProduct.swiftASTs.insert(description.moduleOutputPath)
case .modulewrap:
buildProduct.objects += [description.wrappedModuleOutputPath]
case nil:
break
}
default: break
}
}
buildProduct.staticTargets = dependencies.staticTargets
buildProduct.dylibs = try dependencies.dylibs.map {
guard let product = self.productMap[$0.id] else {
throw InternalError("unknown product \($0)")
}
return product
}
buildProduct.objects += try dependencies.staticTargets.flatMap { targetName -> [AbsolutePath] in
guard let target = self.targetMap[targetName.id] else {
throw InternalError("unknown target \(targetName)")
}
return try target.objects
}
buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths
buildProduct.availableTools = dependencies.availableTools
}
/// Computes the dependencies of a product.
private func computeDependencies(
of product: ResolvedProduct,
buildParameters: BuildParameters
) throws -> (
dylibs: [ResolvedProduct],
staticTargets: [ResolvedModule],
systemModules: [ResolvedModule],
libraryBinaryPaths: Set<AbsolutePath>,
availableTools: [String: AbsolutePath]
) {
/* Prior to tools-version 5.9, we used to erroneously recursively traverse executable/plugin dependencies and statically include their
targets. For compatibility reasons, we preserve that behavior for older tools-versions. */
let shouldExcludePlugins: Bool
if let toolsVersion = self.graph.package(for: product)?.manifest.toolsVersion {
shouldExcludePlugins = toolsVersion >= .v5_9
} else {
shouldExcludePlugins = false
}
// For test targets, we need to consider the first level of transitive dependencies since the first level is always test targets.
let topLevelDependencies: [PackageModel.Module]
if product.type == .test {
topLevelDependencies = product.modules.flatMap { $0.underlying.dependencies }.compactMap {
switch $0 {
case .product:
return nil
case .module(let target, _):
return target
}
}
} else {
topLevelDependencies = []
}
// get the dynamic libraries for explicitly linking rdar://108561857
func recursiveDynamicLibraries(for product: ResolvedProduct) throws -> [ResolvedProduct] {
let dylibs = try computeDependencies(of: product, buildParameters: buildParameters).dylibs
return try dylibs + dylibs.flatMap { try recursiveDynamicLibraries(for: $0) }
}
// Sort the product targets in topological order.
let nodes: [ResolvedModule.Dependency] = product.modules.map { .module($0, conditions: []) }
let allTargets = try topologicalSort(nodes, successors: { dependency in
switch dependency {
// Include all the dependencies of a target.
case .module(let target, _):
let isTopLevel = topLevelDependencies.contains(target.underlying) || product.modules.contains(id: target.id)
let topLevelIsMacro = isTopLevel && product.type == .macro
let topLevelIsPlugin = isTopLevel && product.type == .plugin
let topLevelIsTest = isTopLevel && product.type == .test
if !topLevelIsMacro && !topLevelIsTest && target.type == .macro {
return []
}
if shouldExcludePlugins, !topLevelIsPlugin && !topLevelIsTest && target.type == .plugin {
return []
}
return target.dependencies.filter { $0.satisfies(buildParameters.buildEnvironment) }
// For a product dependency, we only include its content only if we
// need to statically link it.
case .product(let product, _):
guard dependency.satisfies(buildParameters.buildEnvironment) else {
return []
}
let productDependencies: [ResolvedModule.Dependency] = product.modules.map { .module($0, conditions: []) }
switch product.type {
case .library(.automatic), .library(.static):
return productDependencies
case .plugin:
return shouldExcludePlugins ? [] : productDependencies
case .library(.dynamic):
return try recursiveDynamicLibraries(for: product).map { .product($0, conditions: []) }
case .test, .executable, .snippet, .macro:
return []
}
}
})
// Create empty arrays to collect our results.
var linkLibraries = [ResolvedProduct]()
var staticTargets = [ResolvedModule]()
var systemModules = [ResolvedModule]()
var libraryBinaryPaths: Set<AbsolutePath> = []
var availableTools = [String: AbsolutePath]()
for dependency in allTargets {
switch dependency {
case .module(let target, _):
switch target.type {
// Executable target have historically only been included if they are directly in the product's
// target list. Otherwise they have always been just build-time dependencies.
// In tool version .v5_5 or greater, we also include executable modules implemented in Swift in
// any test products... this is to allow testing of executables. Note that they are also still
// built as separate products that the test can invoke as subprocesses.
case .executable, .snippet, .macro:
if product.modules.contains(id: target.id) {
staticTargets.append(target)
} else if product.type == .test && (target.underlying as? SwiftModule)?.supportsTestableExecutablesFeature == true {
// Only "top-level" targets should really be considered here, not transitive ones.
let isTopLevel = topLevelDependencies.contains(target.underlying) || product.modules.contains(id: target.id)
if let toolsVersion = graph.package(for: product)?.manifest.toolsVersion, toolsVersion >= .v5_5, isTopLevel {
staticTargets.append(target)
}
}
// Test targets should be included only if they are directly in the product's target list.
case .test:
if product.modules.contains(id: target.id) {
staticTargets.append(target)
}
// Library targets should always be included for the same build triple.
case .library:
if target.buildTriple == product.buildTriple {
staticTargets.append(target)
}
// Add system target to system targets array.
case .systemModule:
systemModules.append(target)
// Add binary to binary paths set.
case .binary:
guard let binaryTarget = target.underlying as? BinaryModule else {
throw InternalError("invalid binary target '\(target.name)'")
}
switch binaryTarget.kind {
case .xcframework:
let libraries = try self.parseXCFramework(for: binaryTarget, triple: buildParameters.triple)
for library in libraries {
libraryBinaryPaths.insert(library.libraryPath)
}
case .artifactsArchive:
let tools = try self.parseArtifactsArchive(for: binaryTarget, triple: buildParameters.triple)
tools.forEach { availableTools[$0.name] = $0.executablePath }
case.unknown:
throw InternalError("unknown binary target '\(target.name)' type")
}
case .plugin:
continue
}
case .product(let product, _):
// Add the dynamic products to array of libraries to link.
if product.type == .library(.dynamic) {
linkLibraries.append(product)
}
}
}
// Add derived test targets, if necessary
if product.type == .test, let derivedTestTargets = derivedTestTargetsMap[product.id] {
staticTargets.append(contentsOf: derivedTestTargets)
}
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools)
}
/// Extracts the artifacts from an artifactsArchive
private func parseArtifactsArchive(for binaryTarget: BinaryModule, triple: Triple) throws -> [ExecutableInfo] {
try self.externalExecutablesCache.memoize(key: binaryTarget) {
let execInfos = try binaryTarget.parseArtifactArchives(for: triple, fileSystem: self.fileSystem)
return execInfos.filter{!$0.supportedTriples.isEmpty}
}
}
}
|