From 43880e981a7853f53ec452490aa25db3bb06b77a Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Sat, 4 Oct 2025 11:48:56 -0700 Subject: [PATCH] Avoid duplicate OS rpaths when linking with swiftc and targeting older macOS --- .../Tools/LinkerTools.swift | 23 +++++++-- .../SpecImplementations/Tools/TAPITools.swift | 2 +- .../LinkerTaskConstructionTests.swift | 47 +++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift index 8d2580d0..5d5544df 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -334,10 +334,18 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec return usedTools.keys.map({ type(of: $0) }).contains(where: { $0 == SwiftCompilerSpec.self }) } - static public func computeRPaths(_ cbc: CommandBuildContext,_ delegate: any TaskGenerationDelegate, inputRunpathSearchPaths: [String], isUsingSwift: Bool ) async -> [String] { + public struct RuntimeSearchPaths { + // The rpaths themselves + let paths: [String] + // Whether we should suppress rpaths the linker driver might otherwise insert + let suppressDriverStdlibPaths: Bool + } + + static public func computeRPaths(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, inputRunpathSearchPaths: [String], isUsingSwift: Bool ) async -> RuntimeSearchPaths { // Product types can provide their own set of rpath values, we need to ensure that our rpath flags for Swift in the OS appear before those. Also, due to the fact that we are staging this rollout, we need to specifically override any Swift libraries that may be in the bundle _when_ the Swift ABI version matches on the system with that in which the tool was built with. var runpathSearchPaths = inputRunpathSearchPaths + var suppressDriverStdlibPaths = false // NOTE: For swift.org toolchains, we always add the search paths to the Swift SDK location as the overlays do not have the install name set. This also works when `SWIFT_USE_DEVELOPMENT_TOOLCHAIN_RUNTIME=YES` as `DYLD_LIBRARY_PATH` is used to override these settings during debug time. If users wish to use the development runtime while not debugging, they need to manually set their rpaths as this is not a supported configuration. // Also, if the deployment target does not support Swift in the OS, the rpath entries need to be added as well. // And, if the deployment target does not support Swift Concurrency natively, then the rpath needs to be added as well so that the shim library can find the real implementation. Note that we assume `true` in the case where `supportsSwiftInTheOS` is `nil` as we don't have the platform data to make the correct choice; so fallback to existing behavior. @@ -357,9 +365,11 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // NOTE: For swift.org toolchains, this is fine as `DYLD_LIBRARY_PATH` is used to override these settings. let swiftABIVersion = await (cbc.producer.swiftCompilerSpec.discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate) as? DiscoveredSwiftCompilerToolSpecInfo)?.swiftABIVersion runpathSearchPaths.insert( swiftABIVersion.flatMap { "/usr/lib/swift-\($0)" } ?? "/usr/lib/swift", at: 0) + // Ensure the linker driver does not insert a duplicate rpath (if linking using swiftc) + suppressDriverStdlibPaths = true } - return runpathSearchPaths + return .init(paths: runpathSearchPaths, suppressDriverStdlibPaths: suppressDriverStdlibPaths) } private static func swiftcSupportsLinkingMachOType(_ type: String) -> Bool { @@ -545,8 +555,13 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // See related: var runpathSearchPaths: [String] = [] + var stdlibRPathArgs: [String] = [] if machOTypeString != "mh_object" { - runpathSearchPaths = await LdLinkerSpec.computeRPaths(cbc, delegate, inputRunpathSearchPaths: cbc.scope.evaluate(BuiltinMacros.LD_RUNPATH_SEARCH_PATHS), isUsingSwift: isLinkUsingSwift) + let rpaths = await LdLinkerSpec.computeRPaths(cbc, delegate, inputRunpathSearchPaths: cbc.scope.evaluate(BuiltinMacros.LD_RUNPATH_SEARCH_PATHS), isUsingSwift: isLinkUsingSwift) + runpathSearchPaths = rpaths.paths + if rpaths.suppressDriverStdlibPaths && cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: linkerDriverLookup) == .swiftc { + stdlibRPathArgs.append("-no-stdlib-rpath") + } // If we're merging libraries and we're reexporting any libraries, then add an rpath to the ReexportedBinaries directory. // This coordinates with the logic in SourcesTaskProducer which copies content to that directory. @@ -658,6 +673,8 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // Generate the command line. var commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: optionContext, specialArgs: specialArgs, lookup: lookup).map(\.asString) + commandLine.append(contentsOf: stdlibRPathArgs) + // Add flags to emit SDK imports info. let sdkImportsInfoFile = cbc.scope.evaluate(BuiltinMacros.LD_SDK_IMPORTS_FILE) let supportsSDKImportsFeature = (try? optionContext?.toolVersion >= .init("1164")) == true diff --git a/Sources/SWBCore/SpecImplementations/Tools/TAPITools.swift b/Sources/SWBCore/SpecImplementations/Tools/TAPITools.swift index b58dbb03..bf34a1ff 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/TAPITools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/TAPITools.swift @@ -75,7 +75,7 @@ public final class TAPIToolSpec : GenericCommandLineToolSpec, GCCCompatibleCompi let scope = cbc.scope let useOnlyFilelist = scope.evaluate(BuiltinMacros.TAPI_ENABLE_PROJECT_HEADERS) || scope.evaluate(BuiltinMacros.TAPI_USE_SRCROOT) - let runpathSearchPaths = await LdLinkerSpec.computeRPaths(cbc, delegate, inputRunpathSearchPaths: scope.evaluate(BuiltinMacros.TAPI_RUNPATH_SEARCH_PATHS), isUsingSwift: !generatedTBDFiles.isEmpty) + let runpathSearchPaths = await LdLinkerSpec.computeRPaths(cbc, delegate, inputRunpathSearchPaths: scope.evaluate(BuiltinMacros.TAPI_RUNPATH_SEARCH_PATHS), isUsingSwift: !generatedTBDFiles.isEmpty).paths let runpathSearchPathsExpr = scope.namespace.parseStringList(runpathSearchPaths) // Create a lookup closure for build setting overrides. diff --git a/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift index 48b02778..f19e8e39 100644 --- a/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift @@ -94,4 +94,51 @@ fileprivate struct LinkerTaskConstructionTests: CoreBasedTests { } } } + + @Test(.requireSDKs(.macOS)) + func stdlibRpathSuppression() async throws { + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("s.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_EXEC": try await swiftCompilerPath.str, + "SWIFT_VERSION": try await swiftVersion, + "MACOSX_DEPLOYMENT_TARGET": "10.13" + ]), + ], + targets: [ + TestStandardTarget( + "Library", + type: .dynamicLibrary, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]), + ], + buildPhases: [ + TestSourcesBuildPhase(["s.swift"]), + ] + ), + ]) + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["LINKER_DRIVER": "swiftc"]), runDestination: .macOS) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineContains(["-no-stdlib-rpath"]) + } + } + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["LINKER_DRIVER": "clang"]), runDestination: .macOS) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineDoesNotContain("-no-stdlib-rpath") + } + } + } }