Skip to content

Commit c82304c

Browse files
authored
Build: pass through Embedded flag to link jobs (#7304)
### Motivation: Without passing this flag, Swift Driver links such products as if they're not built for embedded platforms at all, passing incorrect flags to the linker. This is not reproducible when using plain `swiftc`, as that combines both compile and link jobs by default, but SwiftPM prefers to run those separately, which requires this special handling. ### Modifications: Directly checking in `ProductBuildDescription/linkArguments` whether all of the product's targets are built in the embedded mode. If so, we're passing the embedded mode flag to Swift Driver. Unfortunately, `BuildSettings.AssignmentTable` is formed too early in the build process right in `PackageModel`, while we still need to run checks specific build settings quite late in the build stage. Because of that, there's no clean way to check if `Embedded` flag is passed other than directly rendering `BuildSettings.Scope` to a string and running a substring check on that. In the future we should move `BuildSettings` out of `PackageModel`, ensuring that SwiftPM clients don't rely on this behavior anymore. ### Result: Products that have targets using Embedded Swift can be built with SwiftPM, assuming that Swift Driver handles other linker flags correctly.
1 parent adfbd9a commit c82304c

File tree

9 files changed

+158
-28
lines changed

9 files changed

+158
-28
lines changed

Sources/Build/BuildDescription/ProductBuildDescription.swift

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212

1313
import Basics
1414
import PackageGraph
15+
16+
@_spi(SwiftPMInternal)
1517
import PackageModel
18+
1619
import OrderedCollections
1720
import SPMBuildCore
1821

@@ -198,7 +201,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
198201
// No arguments for static libraries.
199202
return []
200203
case .test:
201-
// Test products are bundle when using objectiveC, executable when using test entry point.
204+
// Test products are bundle when using Objective-C, executable when using test entry point.
202205
switch self.buildParameters.testingParameters.testProductStyle {
203206
case .loadableBundle:
204207
args += ["-Xlinker", "-bundle"]
@@ -271,8 +274,21 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
271274
}
272275
args += ["@\(self.linkFileListPath.pathString)"]
273276

274-
// Embed the swift stdlib library path inside tests and executables on Darwin.
275277
if containsSwiftTargets {
278+
// Pass experimental features to link jobs in addition to compile jobs. Preserve ordering while eliminating
279+
// duplicates with `OrderedSet`.
280+
var experimentalFeatures = OrderedSet<String>()
281+
for target in self.product.targets {
282+
let swiftSettings = target.underlying.buildSettingsDescription.filter { $0.tool == .swift }
283+
for case let .enableExperimentalFeature(feature) in swiftSettings.map(\.kind) {
284+
experimentalFeatures.append(feature)
285+
}
286+
}
287+
for feature in experimentalFeatures {
288+
args += ["-enable-experimental-feature", feature]
289+
}
290+
291+
// Embed the swift stdlib library path inside tests and executables on Darwin.
276292
let useStdlibRpath: Bool
277293
switch self.product.type {
278294
case .library(let type):
@@ -297,11 +313,9 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
297313
args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString]
298314
}
299315
}
300-
}
301-
302-
// Don't link runtime compatibility patch libraries if there are no
303-
// Swift sources in the target.
304-
if !containsSwiftTargets {
316+
} else {
317+
// Don't link runtime compatibility patch libraries if there are no
318+
// Swift sources in the target.
305319
args += ["-runtime-compatibility-version", "none"]
306320
}
307321

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,11 @@ public final class PackageBuilder {
877877
}
878878

879879
// Create the build setting assignment table for this target.
880-
let buildSettings = try self.buildSettings(for: manifestTarget, targetRoot: potentialModule.path, cxxLanguageStandard: self.manifest.cxxLanguageStandard)
880+
let buildSettings = try self.buildSettings(
881+
for: manifestTarget,
882+
targetRoot: potentialModule.path,
883+
cxxLanguageStandard: self.manifest.cxxLanguageStandard
884+
)
881885

882886
// Compute the path to public headers directory.
883887
let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent
@@ -969,6 +973,7 @@ public final class PackageBuilder {
969973
packageAccess: potentialModule.packageAccess,
970974
swiftVersion: try self.swiftVersion(),
971975
buildSettings: buildSettings,
976+
buildSettingsDescription: manifestTarget.settings,
972977
usesUnsafeFlags: manifestTarget.usesUnsafeFlags
973978
)
974979
} else {
@@ -1011,14 +1016,18 @@ public final class PackageBuilder {
10111016
ignored: ignored,
10121017
dependencies: dependencies,
10131018
buildSettings: buildSettings,
1019+
buildSettingsDescription: manifestTarget.settings,
10141020
usesUnsafeFlags: manifestTarget.usesUnsafeFlags
10151021
)
10161022
}
10171023
}
10181024

10191025
/// Creates build setting assignment table for the given target.
1020-
func buildSettings(for target: TargetDescription?, targetRoot: AbsolutePath, cxxLanguageStandard: String? = nil) throws -> BuildSettings
1021-
.AssignmentTable
1026+
func buildSettings(
1027+
for target: TargetDescription?,
1028+
targetRoot: AbsolutePath,
1029+
cxxLanguageStandard: String? = nil
1030+
) throws -> BuildSettings.AssignmentTable
10221031
{
10231032
var table = BuildSettings.AssignmentTable()
10241033
guard let target else { return table }
@@ -1653,23 +1662,21 @@ extension PackageBuilder {
16531662
let sources = Sources(paths: [sourceFile], root: sourceFile.parentDirectory)
16541663
let buildSettings: BuildSettings.AssignmentTable
16551664

1656-
do {
1657-
let targetDescription = try TargetDescription(
1658-
name: name,
1659-
dependencies: dependencies
1660-
.map {
1661-
TargetDescription.Dependency.target(name: $0.name)
1662-
},
1663-
path: sourceFile.parentDirectory.pathString,
1664-
sources: [sourceFile.pathString],
1665-
type: .executable,
1666-
packageAccess: false
1667-
)
1668-
buildSettings = try self.buildSettings(
1669-
for: targetDescription,
1670-
targetRoot: sourceFile.parentDirectory
1671-
)
1672-
}
1665+
let targetDescription = try TargetDescription(
1666+
name: name,
1667+
dependencies: dependencies
1668+
.map {
1669+
TargetDescription.Dependency.target(name: $0.name)
1670+
},
1671+
path: sourceFile.parentDirectory.pathString,
1672+
sources: [sourceFile.pathString],
1673+
type: .executable,
1674+
packageAccess: false
1675+
)
1676+
buildSettings = try self.buildSettings(
1677+
for: targetDescription,
1678+
targetRoot: sourceFile.parentDirectory
1679+
)
16731680

16741681
return SwiftTarget(
16751682
name: name,
@@ -1680,6 +1687,7 @@ extension PackageBuilder {
16801687
packageAccess: false,
16811688
swiftVersion: try swiftVersion(),
16821689
buildSettings: buildSettings,
1690+
buildSettingsDescription: targetDescription.settings,
16831691
usesUnsafeFlags: false
16841692
)
16851693
}

Sources/PackageModel/Target/BinaryTarget.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public final class BinaryTarget: Target {
4141
dependencies: [],
4242
packageAccess: false,
4343
buildSettings: .init(),
44+
buildSettingsDescription: [],
4445
pluginUsages: [],
4546
usesUnsafeFlags: false
4647
)

Sources/PackageModel/Target/ClangTarget.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public final class ClangTarget: Target {
5353
others: [AbsolutePath] = [],
5454
dependencies: [Target.Dependency] = [],
5555
buildSettings: BuildSettings.AssignmentTable = .init(),
56+
buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [],
5657
usesUnsafeFlags: Bool
5758
) throws {
5859
guard includeDir.isDescendantOfOrEqual(to: sources.root) else {
@@ -76,6 +77,7 @@ public final class ClangTarget: Target {
7677
dependencies: dependencies,
7778
packageAccess: false,
7879
buildSettings: buildSettings,
80+
buildSettingsDescription: buildSettingsDescription,
7981
pluginUsages: [],
8082
usesUnsafeFlags: usesUnsafeFlags
8183
)

Sources/PackageModel/Target/PluginTarget.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public final class PluginTarget: Target {
3636
dependencies: dependencies,
3737
packageAccess: packageAccess,
3838
buildSettings: .init(),
39+
buildSettingsDescription: [],
3940
pluginUsages: [],
4041
usesUnsafeFlags: false
4142
)

Sources/PackageModel/Target/SwiftTarget.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public final class SwiftTarget: Target {
3333
dependencies: dependencies,
3434
packageAccess: packageAccess,
3535
buildSettings: .init(),
36+
buildSettingsDescription: [],
3637
pluginUsages: [],
3738
usesUnsafeFlags: false
3839
)
@@ -54,6 +55,7 @@ public final class SwiftTarget: Target {
5455
packageAccess: Bool,
5556
swiftVersion: SwiftLanguageVersion,
5657
buildSettings: BuildSettings.AssignmentTable = .init(),
58+
buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [],
5759
pluginUsages: [PluginUsage] = [],
5860
usesUnsafeFlags: Bool
5961
) {
@@ -70,6 +72,7 @@ public final class SwiftTarget: Target {
7072
dependencies: dependencies,
7173
packageAccess: packageAccess,
7274
buildSettings: buildSettings,
75+
buildSettingsDescription: buildSettingsDescription,
7376
pluginUsages: pluginUsages,
7477
usesUnsafeFlags: usesUnsafeFlags
7578
)
@@ -100,6 +103,7 @@ public final class SwiftTarget: Target {
100103
dependencies: dependencies,
101104
packageAccess: packageAccess,
102105
buildSettings: .init(),
106+
buildSettingsDescription: [],
103107
pluginUsages: [],
104108
usesUnsafeFlags: false
105109
)

Sources/PackageModel/Target/SystemLibraryTarget.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public final class SystemLibraryTarget: Target {
4343
dependencies: [],
4444
packageAccess: false,
4545
buildSettings: .init(),
46+
buildSettingsDescription: [],
4647
pluginUsages: [],
4748
usesUnsafeFlags: false
4849
)

Sources/PackageModel/Target/Target.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ public class Target: PolymorphicCodableProtocol {
233233
/// The build settings assignments of this target.
234234
public let buildSettings: BuildSettings.AssignmentTable
235235

236+
@_spi(SwiftPMInternal)
237+
public let buildSettingsDescription: [TargetBuildSettingDescription.Setting]
238+
236239
/// The usages of package plugins by this target.
237240
public let pluginUsages: [PluginUsage]
238241

@@ -251,6 +254,7 @@ public class Target: PolymorphicCodableProtocol {
251254
dependencies: [Target.Dependency],
252255
packageAccess: Bool,
253256
buildSettings: BuildSettings.AssignmentTable,
257+
buildSettingsDescription: [TargetBuildSettingDescription.Setting],
254258
pluginUsages: [PluginUsage],
255259
usesUnsafeFlags: Bool
256260
) {
@@ -266,12 +270,27 @@ public class Target: PolymorphicCodableProtocol {
266270
self.c99name = self.name.spm_mangledToC99ExtendedIdentifier()
267271
self.packageAccess = packageAccess
268272
self.buildSettings = buildSettings
273+
self.buildSettingsDescription = buildSettingsDescription
269274
self.pluginUsages = pluginUsages
270275
self.usesUnsafeFlags = usesUnsafeFlags
271276
}
272277

273278
private enum CodingKeys: String, CodingKey {
274-
case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, packageAccess, buildSettings, pluginUsages, usesUnsafeFlags
279+
case name
280+
case potentialBundleName
281+
case defaultLocalization
282+
case platforms
283+
case type
284+
case path
285+
case sources
286+
case resources
287+
case ignored
288+
case others
289+
case packageAccess
290+
case buildSettings
291+
case buildSettingsDescription
292+
case pluginUsages
293+
case usesUnsafeFlags
275294
}
276295

277296
public func encode(to encoder: Encoder) throws {
@@ -289,6 +308,7 @@ public class Target: PolymorphicCodableProtocol {
289308
try container.encode(others, forKey: .others)
290309
try container.encode(packageAccess, forKey: .packageAccess)
291310
try container.encode(buildSettings, forKey: .buildSettings)
311+
try container.encode(buildSettingsDescription, forKey: .buildSettingsDescription)
292312
// FIXME: pluginUsages property is skipped on purpose as it points to
293313
// the actual target dependency object.
294314
try container.encode(usesUnsafeFlags, forKey: .usesUnsafeFlags)
@@ -310,6 +330,10 @@ public class Target: PolymorphicCodableProtocol {
310330
self.c99name = self.name.spm_mangledToC99ExtendedIdentifier()
311331
self.packageAccess = try container.decode(Bool.self, forKey: .packageAccess)
312332
self.buildSettings = try container.decode(BuildSettings.AssignmentTable.self, forKey: .buildSettings)
333+
self.buildSettingsDescription = try container.decode(
334+
[TargetBuildSettingDescription.Setting].self,
335+
forKey: .buildSettingsDescription
336+
)
313337
// FIXME: pluginUsages property is skipped on purpose as it points to
314338
// the actual target dependency object.
315339
self.pluginUsages = []
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@testable
14+
import Build
15+
16+
import class Basics.ObservabilitySystem
17+
import class TSCBasic.InMemoryFileSystem
18+
19+
import class PackageModel.Manifest
20+
import struct PackageModel.TargetDescription
21+
22+
@testable
23+
import struct PackageGraph.ResolvedProduct
24+
25+
import func SPMTestSupport.loadPackageGraph
26+
import func SPMTestSupport.mockBuildParameters
27+
import func SPMTestSupport.XCTAssertNoDiagnostics
28+
import XCTest
29+
30+
final class ProductBuildDescriptionTests: XCTestCase {
31+
func testEmbeddedProducts() throws {
32+
let fs = InMemoryFileSystem(
33+
emptyFiles:
34+
"/Pkg/Sources/exe/main.swift"
35+
)
36+
37+
let observability = ObservabilitySystem.makeForTesting()
38+
let graph = try loadPackageGraph(
39+
fileSystem: fs,
40+
manifests: [
41+
Manifest.createRootManifest(
42+
displayName: "Pkg",
43+
path: "/Pkg",
44+
targets: [
45+
TargetDescription(
46+
name: "exe",
47+
settings: [.init(tool: .swift, kind: .enableExperimentalFeature("Embedded"))]
48+
),
49+
]
50+
),
51+
],
52+
observabilityScope: observability.topScope
53+
)
54+
XCTAssertNoDiagnostics(observability.diagnostics)
55+
56+
let id = ResolvedProduct.ID(productName: "exe", packageIdentity: .plain("pkg"), buildTriple: .destination)
57+
let package = try XCTUnwrap(graph.rootPackages.first)
58+
let product = try XCTUnwrap(graph.allProducts[id])
59+
60+
let buildDescription = try ProductBuildDescription(
61+
package: package,
62+
product: product,
63+
toolsVersion: .v5_9,
64+
buildParameters: mockBuildParameters(environment: .init(platform: .macOS)),
65+
fileSystem: fs,
66+
observabilityScope: observability.topScope
67+
)
68+
69+
XCTAssertTrue(
70+
try buildDescription.linkArguments()
71+
.joined(separator: " ")
72+
.contains("-enable-experimental-feature Embedded")
73+
)
74+
}
75+
}

0 commit comments

Comments
 (0)