diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 37276b3538c..6e304910499 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -67,7 +67,12 @@ package struct PIFBuilderParameters { /// Additional rules for including a source or resource file in a target let additionalFileRules: [FileRuleDescription] - package init(isPackageAccessModifierSupported: Bool, enableTestability: Bool, shouldCreateDylibForDynamicProducts: Bool, toolchainLibDir: AbsolutePath, pkgConfigDirectories: [AbsolutePath], supportedSwiftVersions: [SwiftLanguageVersion], pluginScriptRunner: PluginScriptRunner, disableSandbox: Bool, pluginWorkingDirectory: AbsolutePath, additionalFileRules: [FileRuleDescription]) { + /// Add rpaths which allow loading libraries adjacent to the current image at runtime. This is desirable + /// when launching build products from the build directory, but should often be disabled when deploying + /// the build products to a different location. + let addLocalRpaths: Bool + + package init(isPackageAccessModifierSupported: Bool, enableTestability: Bool, shouldCreateDylibForDynamicProducts: Bool, toolchainLibDir: AbsolutePath, pkgConfigDirectories: [AbsolutePath], supportedSwiftVersions: [SwiftLanguageVersion], pluginScriptRunner: PluginScriptRunner, disableSandbox: Bool, pluginWorkingDirectory: AbsolutePath, additionalFileRules: [FileRuleDescription], addLocalRPaths: Bool) { self.isPackageAccessModifierSupported = isPackageAccessModifierSupported self.enableTestability = enableTestability self.shouldCreateDylibForDynamicProducts = shouldCreateDylibForDynamicProducts @@ -78,6 +83,7 @@ package struct PIFBuilderParameters { self.disableSandbox = disableSandbox self.pluginWorkingDirectory = pluginWorkingDirectory self.additionalFileRules = additionalFileRules + self.addLocalRpaths = addLocalRPaths } } @@ -406,6 +412,7 @@ public final class PIFBuilder { delegate: packagePIFBuilderDelegate, buildToolPluginResultsByTargetName: buildToolPluginResultsByTargetName, createDylibForDynamicProducts: self.parameters.shouldCreateDylibForDynamicProducts, + addLocalRpaths: self.parameters.addLocalRpaths, packageDisplayVersion: package.manifest.displayName, fileSystem: self.fileSystem, observabilityScope: self.observabilityScope @@ -503,7 +510,8 @@ public final class PIFBuilder { disableSandbox: Bool, pluginWorkingDirectory: AbsolutePath, pkgConfigDirectories: [Basics.AbsolutePath], - additionalFileRules: [FileRuleDescription] + additionalFileRules: [FileRuleDescription], + addLocalRpaths: Bool ) async throws -> String { let parameters = PIFBuilderParameters( buildParameters, @@ -512,6 +520,7 @@ public final class PIFBuilder { disableSandbox: disableSandbox, pluginWorkingDirectory: pluginWorkingDirectory, additionalFileRules: additionalFileRules, + addLocalRpaths: addLocalRpaths ) let builder = Self( graph: packageGraph, @@ -773,7 +782,8 @@ extension PIFBuilderParameters { pluginScriptRunner: PluginScriptRunner, disableSandbox: Bool, pluginWorkingDirectory: AbsolutePath, - additionalFileRules: [FileRuleDescription] + additionalFileRules: [FileRuleDescription], + addLocalRpaths: Bool ) { self.init( isPackageAccessModifierSupported: buildParameters.driverParameters.isPackageAccessModifierSupported, @@ -786,6 +796,7 @@ extension PIFBuilderParameters { disableSandbox: disableSandbox, pluginWorkingDirectory: pluginWorkingDirectory, additionalFileRules: additionalFileRules, + addLocalRPaths: addLocalRpaths, ) } } diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index b6dd140d8aa..75e813edf31 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -168,6 +168,11 @@ public final class PackagePIFBuilder { /// * Remove IDEPackageSupportCreateDylibsForDynamicProducts. let createDylibForDynamicProducts: Bool + /// Add rpaths which allow loading libraries adjacent to the current image at runtime. This is desirable + /// when launching build products from the build directory, but should often be disabled when deploying + /// the build products to a different location. + let addLocalRpaths: Bool + /// Package display version, if any (i.e., it can be a version, branch or a git ref). let packageDisplayVersion: String? @@ -195,6 +200,7 @@ public final class PackagePIFBuilder { delegate: PackagePIFBuilder.BuildDelegate, buildToolPluginResultsByTargetName: [String: [BuildToolPluginInvocationResult]], createDylibForDynamicProducts: Bool = false, + addLocalRpaths: Bool = true, packageDisplayVersion: String?, fileSystem: FileSystem, observabilityScope: ObservabilityScope @@ -208,6 +214,7 @@ public final class PackagePIFBuilder { self.packageDisplayVersion = packageDisplayVersion self.fileSystem = fileSystem self.observabilityScope = observabilityScope + self.addLocalRpaths = addLocalRpaths } public init( @@ -217,6 +224,7 @@ public final class PackagePIFBuilder { delegate: PackagePIFBuilder.BuildDelegate, buildToolPluginResultsByTargetName: [String: BuildToolPluginInvocationResult], createDylibForDynamicProducts: Bool = false, + addLocalRpaths: Bool = true, packageDisplayVersion: String?, fileSystem: FileSystem, observabilityScope: ObservabilityScope @@ -227,6 +235,7 @@ public final class PackagePIFBuilder { self.delegate = delegate self.buildToolPluginResultsByTargetName = buildToolPluginResultsByTargetName.mapValues { [$0] } self.createDylibForDynamicProducts = createDylibForDynamicProducts + self.addLocalRpaths = addLocalRpaths self.packageDisplayVersion = packageDisplayVersion self.fileSystem = fileSystem self.observabilityScope = observabilityScope diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index bb124d30607..af81b346520 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -750,12 +750,21 @@ extension PackagePIFProjectBuilder { // // An imparted build setting on C will propagate back to both B and A. // FIXME: -rpath should not be given if -static is - impartedSettings[.LD_RUNPATH_SEARCH_PATHS] = - ["$(RPATH_ORIGIN)"] + - (impartedSettings[.LD_RUNPATH_SEARCH_PATHS] ?? ["$(inherited)"]) + var rpaths: [String] = [] + if let existingRpaths = impartedSettings[.LD_RUNPATH_SEARCH_PATHS] { + rpaths.append(contentsOf: existingRpaths) + } + if pifBuilder.addLocalRpaths { + rpaths.append("$(RPATH_ORIGIN)") + impartedSettings[.LD_RUNPATH_SEARCH_PATHS] = rpaths + ["$(inherited)"] + } var impartedDebugSettings = impartedSettings - impartedDebugSettings[.LD_RUNPATH_SEARCH_PATHS]! += ["$(BUILT_PRODUCTS_DIR)/PackageFrameworks"] + if pifBuilder.addLocalRpaths { + // FIXME: Why is this rpath only added to the debug config? We should investigate reworking this. + rpaths.append("$(BUILT_PRODUCTS_DIR)/PackageFrameworks") + impartedDebugSettings[.LD_RUNPATH_SEARCH_PATHS] = rpaths + ["$(inherited)"] + } self.project[keyPath: sourceModuleTargetKeyPath].common.addBuildConfig { id in BuildConfig( diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index e9f1234e784..e7c75b928d6 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -119,11 +119,13 @@ extension PackagePIFProjectBuilder { if mainModule.type == .test { // FIXME: we shouldn't always include both the deep and shallow bundle paths here, but for that we'll need rdar://31867023 - settings[.LD_RUNPATH_SEARCH_PATHS] = [ - "$(RPATH_ORIGIN)/Frameworks", - "$(RPATH_ORIGIN)/../Frameworks", - "$(inherited)" - ] + if pifBuilder.addLocalRpaths { + settings[.LD_RUNPATH_SEARCH_PATHS] = [ + "$(RPATH_ORIGIN)/Frameworks", + "$(RPATH_ORIGIN)/../Frameworks", + "$(inherited)" + ] + } settings[.GENERATE_INFOPLIST_FILE] = "YES" settings[.SKIP_INSTALL] = "NO" settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS].lazilyInitialize { ["$(inherited)"] } @@ -1019,10 +1021,12 @@ extension PackagePIFProjectBuilder { // A test-runner should always be adjacent to the dynamic library containing the tests, // so add the appropriate rpaths. - settings[.LD_RUNPATH_SEARCH_PATHS] = [ - "$(inherited)", - "$(RPATH_ORIGIN)" - ] + if pifBuilder.addLocalRpaths { + settings[.LD_RUNPATH_SEARCH_PATHS] = [ + "$(inherited)", + "$(RPATH_ORIGIN)" + ] + } let deploymentTargets = unitTestProduct.deploymentTargets settings[.MACOSX_DEPLOYMENT_TARGET] = deploymentTargets?[.macOS] ?? nil diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index a0d710c29bc..b0f68bc7dd3 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -1080,7 +1080,6 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { break } - // TODO: shouldDisableLocalRpath // TODO: shouldLinkStaticSwiftStdlib return settings @@ -1146,7 +1145,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { pluginScriptRunner: self.pluginConfiguration.scriptRunner, disableSandbox: self.pluginConfiguration.disableSandbox, pluginWorkingDirectory: self.pluginConfiguration.workDirectory, - additionalFileRules: additionalFileRules + additionalFileRules: additionalFileRules, + addLocalRpaths: !self.buildParameters.linkingParameters.shouldDisableLocalRpath ), fileSystem: self.fileSystem, observabilityScope: self.observabilityScope, diff --git a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift index 3bac4ebadfc..9d4ebab1e26 100644 --- a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift @@ -22,7 +22,7 @@ import _InternalTestSupport import Workspace extension PIFBuilderParameters { - fileprivate static func constructDefaultParametersForTesting(temporaryDirectory: Basics.AbsolutePath) throws -> Self { + fileprivate static func constructDefaultParametersForTesting(temporaryDirectory: Basics.AbsolutePath, addLocalRpaths: Bool) throws -> Self { self.init( isPackageAccessModifierSupported: true, enableTestability: false, @@ -37,12 +37,13 @@ extension PIFBuilderParameters { ), disableSandbox: false, pluginWorkingDirectory: temporaryDirectory.appending(component: "plugin-working-dir"), - additionalFileRules: [] + additionalFileRules: [], + addLocalRPaths: addLocalRpaths ) } } -fileprivate func withGeneratedPIF(fromFixture fixtureName: String, do doIt: (SwiftBuildSupport.PIF.TopLevelObject, TestingObservability) async throws -> ()) async throws { +fileprivate func withGeneratedPIF(fromFixture fixtureName: String, addLocalRpaths: Bool = true, do doIt: (SwiftBuildSupport.PIF.TopLevelObject, TestingObservability) async throws -> ()) async throws { try await fixture(name: fixtureName) { fixturePath in let observabilitySystem = ObservabilitySystem.makeForTesting() let workspace = try Workspace( @@ -58,7 +59,7 @@ fileprivate func withGeneratedPIF(fromFixture fixtureName: String, do doIt: (Swi ) let builder = PIFBuilder( graph: graph, - parameters: try PIFBuilderParameters.constructDefaultParametersForTesting(temporaryDirectory: fixturePath), + parameters: try PIFBuilderParameters.constructDefaultParametersForTesting(temporaryDirectory: fixturePath, addLocalRpaths: addLocalRpaths), fileSystem: localFileSystem, observabilityScope: observabilitySystem.topScope ) @@ -238,4 +239,36 @@ struct PIFBuilderTests { } } } + + @Test func disablingLocalRpaths() async throws { + try await withGeneratedPIF(fromFixture: "Miscellaneous/Simple") { pif, observabilitySystem in + #expect(observabilitySystem.diagnostics.filter { + $0.severity == .error + }.isEmpty) + + do { + let releaseConfig = try pif.workspace + .project(named: "Foo") + .target(named: "Foo") + .buildConfig(named: "Release") + + #expect(releaseConfig.impartedBuildProperties.settings[.LD_RUNPATH_SEARCH_PATHS] == ["$(RPATH_ORIGIN)", "$(inherited)"]) + } + } + + try await withGeneratedPIF(fromFixture: "Miscellaneous/Simple", addLocalRpaths: false) { pif, observabilitySystem in + #expect(observabilitySystem.diagnostics.filter { + $0.severity == .error + }.isEmpty) + + do { + let releaseConfig = try pif.workspace + .project(named: "Foo") + .target(named: "Foo") + .buildConfig(named: "Release") + + #expect(releaseConfig.impartedBuildProperties.settings[.LD_RUNPATH_SEARCH_PATHS] == nil) + } + } + } }