File: BuildPlan%2BProduct.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (286 lines) | stat: -rw-r--r-- 13,794 bytes parent folder | download
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}
        }
    }
}