Skip to content

Build: pass through Embedded flag to link jobs #7304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions Sources/Build/BuildDescription/ProductBuildDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

import Basics
import PackageGraph

@_spi(SwiftPMInternal)
import PackageModel

import OrderedCollections
import SPMBuildCore

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

// Embed the swift stdlib library path inside tests and executables on Darwin.
if containsSwiftTargets {
// Pass experimental features to link jobs in addition to compile jobs. Preserve ordering while eliminating
// duplicates with `OrderedSet`.
var experimentalFeatures = OrderedSet<String>()
for target in self.product.targets {
Copy link
Member

@kateinoigakukun kateinoigakukun Feb 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what is the semantically correct way, but should we collect features from transitive dependencies too? Or it's it fine to collect from only top-level targets as is now?

Either way, it would be better to cover transitive dependencies in test cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky, in my testing the embedded mode doesn't even support multiple modules in the same product right now, or at least SwiftPM is not ready for that yet. When we unblock that in a future PR, we can look into transitive dependencies support too.

let swiftSettings = target.underlying.buildSettingsDescription.filter { $0.tool == .swift }
for case let .enableExperimentalFeature(feature) in swiftSettings.map(\.kind) {
experimentalFeatures.append(feature)
}
}
for feature in experimentalFeatures {
args += ["-enable-experimental-feature", feature]
}

// Embed the swift stdlib library path inside tests and executables on Darwin.
let useStdlibRpath: Bool
switch self.product.type {
case .library(let type):
Expand All @@ -297,11 +313,9 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString]
}
}
}

// Don't link runtime compatibility patch libraries if there are no
// Swift sources in the target.
if !containsSwiftTargets {
} else {
// Don't link runtime compatibility patch libraries if there are no
// Swift sources in the target.
args += ["-runtime-compatibility-version", "none"]
}

Expand Down
48 changes: 28 additions & 20 deletions Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,11 @@ public final class PackageBuilder {
}

// Create the build setting assignment table for this target.
let buildSettings = try self.buildSettings(for: manifestTarget, targetRoot: potentialModule.path, cxxLanguageStandard: self.manifest.cxxLanguageStandard)
let buildSettings = try self.buildSettings(
for: manifestTarget,
targetRoot: potentialModule.path,
cxxLanguageStandard: self.manifest.cxxLanguageStandard
)

// Compute the path to public headers directory.
let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent
Expand Down Expand Up @@ -969,6 +973,7 @@ public final class PackageBuilder {
packageAccess: potentialModule.packageAccess,
swiftVersion: try self.swiftVersion(),
buildSettings: buildSettings,
buildSettingsDescription: manifestTarget.settings,
usesUnsafeFlags: manifestTarget.usesUnsafeFlags
)
} else {
Expand Down Expand Up @@ -1011,14 +1016,18 @@ public final class PackageBuilder {
ignored: ignored,
dependencies: dependencies,
buildSettings: buildSettings,
buildSettingsDescription: manifestTarget.settings,
usesUnsafeFlags: manifestTarget.usesUnsafeFlags
)
}
}

/// Creates build setting assignment table for the given target.
func buildSettings(for target: TargetDescription?, targetRoot: AbsolutePath, cxxLanguageStandard: String? = nil) throws -> BuildSettings
.AssignmentTable
func buildSettings(
for target: TargetDescription?,
targetRoot: AbsolutePath,
cxxLanguageStandard: String? = nil
) throws -> BuildSettings.AssignmentTable
{
var table = BuildSettings.AssignmentTable()
guard let target else { return table }
Expand Down Expand Up @@ -1653,23 +1662,21 @@ extension PackageBuilder {
let sources = Sources(paths: [sourceFile], root: sourceFile.parentDirectory)
let buildSettings: BuildSettings.AssignmentTable

do {
let targetDescription = try TargetDescription(
name: name,
dependencies: dependencies
.map {
TargetDescription.Dependency.target(name: $0.name)
},
path: sourceFile.parentDirectory.pathString,
sources: [sourceFile.pathString],
type: .executable,
packageAccess: false
)
buildSettings = try self.buildSettings(
for: targetDescription,
targetRoot: sourceFile.parentDirectory
)
}
let targetDescription = try TargetDescription(
name: name,
dependencies: dependencies
.map {
TargetDescription.Dependency.target(name: $0.name)
},
path: sourceFile.parentDirectory.pathString,
sources: [sourceFile.pathString],
type: .executable,
packageAccess: false
)
buildSettings = try self.buildSettings(
for: targetDescription,
targetRoot: sourceFile.parentDirectory
)

return SwiftTarget(
name: name,
Expand All @@ -1680,6 +1687,7 @@ extension PackageBuilder {
packageAccess: false,
swiftVersion: try swiftVersion(),
buildSettings: buildSettings,
buildSettingsDescription: targetDescription.settings,
usesUnsafeFlags: false
)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/Target/BinaryTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public final class BinaryTarget: Target {
dependencies: [],
packageAccess: false,
buildSettings: .init(),
buildSettingsDescription: [],
pluginUsages: [],
usesUnsafeFlags: false
)
Expand Down
2 changes: 2 additions & 0 deletions Sources/PackageModel/Target/ClangTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public final class ClangTarget: Target {
others: [AbsolutePath] = [],
dependencies: [Target.Dependency] = [],
buildSettings: BuildSettings.AssignmentTable = .init(),
buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [],
usesUnsafeFlags: Bool
) throws {
guard includeDir.isDescendantOfOrEqual(to: sources.root) else {
Expand All @@ -76,6 +77,7 @@ public final class ClangTarget: Target {
dependencies: dependencies,
packageAccess: false,
buildSettings: buildSettings,
buildSettingsDescription: buildSettingsDescription,
pluginUsages: [],
usesUnsafeFlags: usesUnsafeFlags
)
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/Target/PluginTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class PluginTarget: Target {
dependencies: dependencies,
packageAccess: packageAccess,
buildSettings: .init(),
buildSettingsDescription: [],
pluginUsages: [],
usesUnsafeFlags: false
)
Expand Down
4 changes: 4 additions & 0 deletions Sources/PackageModel/Target/SwiftTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public final class SwiftTarget: Target {
dependencies: dependencies,
packageAccess: packageAccess,
buildSettings: .init(),
buildSettingsDescription: [],
pluginUsages: [],
usesUnsafeFlags: false
)
Expand All @@ -54,6 +55,7 @@ public final class SwiftTarget: Target {
packageAccess: Bool,
swiftVersion: SwiftLanguageVersion,
buildSettings: BuildSettings.AssignmentTable = .init(),
buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [],
pluginUsages: [PluginUsage] = [],
usesUnsafeFlags: Bool
) {
Expand All @@ -70,6 +72,7 @@ public final class SwiftTarget: Target {
dependencies: dependencies,
packageAccess: packageAccess,
buildSettings: buildSettings,
buildSettingsDescription: buildSettingsDescription,
pluginUsages: pluginUsages,
usesUnsafeFlags: usesUnsafeFlags
)
Expand Down Expand Up @@ -100,6 +103,7 @@ public final class SwiftTarget: Target {
dependencies: dependencies,
packageAccess: packageAccess,
buildSettings: .init(),
buildSettingsDescription: [],
pluginUsages: [],
usesUnsafeFlags: false
)
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/Target/SystemLibraryTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class SystemLibraryTarget: Target {
dependencies: [],
packageAccess: false,
buildSettings: .init(),
buildSettingsDescription: [],
pluginUsages: [],
usesUnsafeFlags: false
)
Expand Down
26 changes: 25 additions & 1 deletion Sources/PackageModel/Target/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ public class Target: PolymorphicCodableProtocol {
/// The build settings assignments of this target.
public let buildSettings: BuildSettings.AssignmentTable

@_spi(SwiftPMInternal)
public let buildSettingsDescription: [TargetBuildSettingDescription.Setting]

/// The usages of package plugins by this target.
public let pluginUsages: [PluginUsage]

Expand All @@ -251,6 +254,7 @@ public class Target: PolymorphicCodableProtocol {
dependencies: [Target.Dependency],
packageAccess: Bool,
buildSettings: BuildSettings.AssignmentTable,
buildSettingsDescription: [TargetBuildSettingDescription.Setting],
pluginUsages: [PluginUsage],
usesUnsafeFlags: Bool
) {
Expand All @@ -266,12 +270,27 @@ public class Target: PolymorphicCodableProtocol {
self.c99name = self.name.spm_mangledToC99ExtendedIdentifier()
self.packageAccess = packageAccess
self.buildSettings = buildSettings
self.buildSettingsDescription = buildSettingsDescription
self.pluginUsages = pluginUsages
self.usesUnsafeFlags = usesUnsafeFlags
}

private enum CodingKeys: String, CodingKey {
case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, packageAccess, buildSettings, pluginUsages, usesUnsafeFlags
case name
case potentialBundleName
case defaultLocalization
case platforms
case type
case path
case sources
case resources
case ignored
case others
case packageAccess
case buildSettings
case buildSettingsDescription
case pluginUsages
case usesUnsafeFlags
}

public func encode(to encoder: Encoder) throws {
Expand All @@ -289,6 +308,7 @@ public class Target: PolymorphicCodableProtocol {
try container.encode(others, forKey: .others)
try container.encode(packageAccess, forKey: .packageAccess)
try container.encode(buildSettings, forKey: .buildSettings)
try container.encode(buildSettingsDescription, forKey: .buildSettingsDescription)
// FIXME: pluginUsages property is skipped on purpose as it points to
// the actual target dependency object.
try container.encode(usesUnsafeFlags, forKey: .usesUnsafeFlags)
Expand All @@ -310,6 +330,10 @@ public class Target: PolymorphicCodableProtocol {
self.c99name = self.name.spm_mangledToC99ExtendedIdentifier()
self.packageAccess = try container.decode(Bool.self, forKey: .packageAccess)
self.buildSettings = try container.decode(BuildSettings.AssignmentTable.self, forKey: .buildSettings)
self.buildSettingsDescription = try container.decode(
[TargetBuildSettingDescription.Setting].self,
forKey: .buildSettingsDescription
)
// FIXME: pluginUsages property is skipped on purpose as it points to
// the actual target dependency object.
self.pluginUsages = []
Expand Down
75 changes: 75 additions & 0 deletions Tests/BuildTests/ProductBuildDescriptionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2021 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
//
//===----------------------------------------------------------------------===//

@testable
import Build

import class Basics.ObservabilitySystem
import class TSCBasic.InMemoryFileSystem

import class PackageModel.Manifest
import struct PackageModel.TargetDescription

@testable
import struct PackageGraph.ResolvedProduct

import func SPMTestSupport.loadPackageGraph
import func SPMTestSupport.mockBuildParameters
import func SPMTestSupport.XCTAssertNoDiagnostics
import XCTest

final class ProductBuildDescriptionTests: XCTestCase {
func testEmbeddedProducts() throws {
let fs = InMemoryFileSystem(
emptyFiles:
"/Pkg/Sources/exe/main.swift"
)

let observability = ObservabilitySystem.makeForTesting()
let graph = try loadPackageGraph(
fileSystem: fs,
manifests: [
Manifest.createRootManifest(
displayName: "Pkg",
path: "/Pkg",
targets: [
TargetDescription(
name: "exe",
settings: [.init(tool: .swift, kind: .enableExperimentalFeature("Embedded"))]
),
]
),
],
observabilityScope: observability.topScope
)
XCTAssertNoDiagnostics(observability.diagnostics)

let id = ResolvedProduct.ID(productName: "exe", packageIdentity: .plain("pkg"), buildTriple: .destination)
let package = try XCTUnwrap(graph.rootPackages.first)
let product = try XCTUnwrap(graph.allProducts[id])

let buildDescription = try ProductBuildDescription(
package: package,
product: product,
toolsVersion: .v5_9,
buildParameters: mockBuildParameters(environment: .init(platform: .macOS)),
fileSystem: fs,
observabilityScope: observability.topScope
)

XCTAssertTrue(
try buildDescription.linkArguments()
.joined(separator: " ")
.contains("-enable-experimental-feature Embedded")
)
}
}