From 1b0f09f52fa5baa39fd5efe6b7d150901d6a037f Mon Sep 17 00:00:00 2001 From: Bassam Khouri Date: Tue, 25 Nov 2025 19:48:08 +0000 Subject: [PATCH] Set linker settings for non-Apple platform when use static swift stdlib Update the linker settings on non-Apple platforms when use static Swift stdlib is set. --- Sources/SWBCore/Settings/BuiltinMacros.swift | 2 +- .../Tools/SwiftCompiler.swift | 19 ++- Tests/SWBCoreTests/SwiftCompilerTests.swift | 133 ++++++++++++++++-- 3 files changed, 141 insertions(+), 13 deletions(-) diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index a150cc57..d45aded6 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -2776,7 +2776,7 @@ public enum ModuleVerifierKind: String, Equatable, Hashable, EnumerationMacroTyp case both } -public enum LinkerDriverChoice: String, Equatable, Hashable, EnumerationMacroType { +public enum LinkerDriverChoice: String, Equatable, Hashable, EnumerationMacroType, CaseIterable { public static let defaultValue: LinkerDriverChoice = .clang case clang diff --git a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift index 9cf58190..bf1839d6 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift @@ -3061,9 +3061,22 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi if !forTAPI { if shouldStaticLinkStdlib { - args += [["-Xlinker", "-force_load_swift_libs"]] - // The Swift runtime requires libc++ & Foundation. - args += [["-lc++", "-framework", "Foundation"]] + // Platform-specific static linking flags + if producer.isApplePlatform { + // Darwin/Apple platforms use force_load_swift_libs and Framework linking + args += [["-Xlinker", "-force_load_swift_libs"]] + args += [["-lc++", "-framework", "Foundation"]] + } else { + switch scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) { + case .swiftc: + // Non-Apple platforms (Linux, etc.) use static library linking + // Note: Foundation is not available as a framework on non-Apple platforms + args += [["-static-stdlib"]] + case .clang, .qcc, .auto: + // TODO: functionality must be implemented + break + } + } } // Add the AST, if debugging. diff --git a/Tests/SWBCoreTests/SwiftCompilerTests.swift b/Tests/SWBCoreTests/SwiftCompilerTests.swift index f37311db..e71ea7d7 100644 --- a/Tests/SWBCoreTests/SwiftCompilerTests.swift +++ b/Tests/SWBCoreTests/SwiftCompilerTests.swift @@ -117,8 +117,9 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda } /// Check the standard library linking options. - @Test(.requireHostOS(.macOS)) - func standardLibraryLinking() async throws { + @Test( + .requireHostOS(.macOS) + ) func standardLibraryLinkingMacOS() async throws { let core = try await getCore() // Computes the expected standard swift linker arguments. @@ -130,7 +131,6 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda let defaultToolchain = try #require(core.toolchainRegistry.defaultToolchain) let swiftcPath = defaultToolchain.path.join("usr/bin/swiftc") - // Check basics. do { let producer = try MockCommandProducer(core: core, productTypeIdentifier: "com.apple.product-type.framework", platform: "macosx", toolchain: core.toolchainRegistry.defaultToolchain) @@ -142,10 +142,22 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda let scope = MacroEvaluationScope(table: table) let delegate = TestTaskPlanningDelegate(clientDelegate: MockTestTaskPlanningClientDelegate(), fs: localFS) let optionContext = await spec.discoveredCommandLineToolSpecInfo(producer, scope, delegate) - try await #expect(spec.computeAdditionalLinkerArgs(producer, scope: scope, lookup: { _ in nil }, inputFileTypes: [], optionContext: optionContext, delegate: CapturingTaskGenerationDelegate(producer: producer, userPreferences: .defaultForTesting)).args == additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath)) + let actual = try await spec.computeAdditionalLinkerArgs( + producer, + scope: scope, + lookup: { _ in nil }, + inputFileTypes: [], + optionContext: optionContext, + delegate: CapturingTaskGenerationDelegate( + producer: producer, + userPreferences: .defaultForTesting, + ), + ).args + let expected = additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath) + #expect(actual == expected) } - // Check force static stdlib. + // Check force static stdlib on Apple platforms (macOS). do { let producer = try MockCommandProducer(core: core, productTypeIdentifier: "com.apple.product-type.framework", platform: "macosx") let stdlibPath = swiftcPath.dirname.dirname.join("lib/swift_static/fakeos") @@ -157,7 +169,20 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda let scope = MacroEvaluationScope(table: table) let delegate = TestTaskPlanningDelegate(clientDelegate: MockTestTaskPlanningClientDelegate(), fs: localFS) let optionContext = await spec.discoveredCommandLineToolSpecInfo(producer, scope, delegate) - try await #expect(spec.computeAdditionalLinkerArgs(producer, scope: scope, lookup: { _ in nil }, inputFileTypes: [], optionContext: optionContext, delegate: CapturingTaskGenerationDelegate(producer: producer, userPreferences: .defaultForTesting)).args == (additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath)) + [["-Xlinker", "-force_load_swift_libs"], ["-lc++", "-framework", "Foundation"]]) + // On Apple platforms, should use Apple-specific static linking flags + let actual = try await spec.computeAdditionalLinkerArgs( + producer, + scope: scope, + lookup: { _ in nil }, + inputFileTypes: [], + optionContext: optionContext, + delegate: CapturingTaskGenerationDelegate( + producer: producer, + userPreferences: .defaultForTesting, + ), + ).args + let expected = (additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath)) + [["-Xlinker", "-force_load_swift_libs"], ["-lc++", "-framework", "Foundation"]] + #expect(actual == expected) } // Check tool product type. @@ -171,7 +196,18 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda let scope = MacroEvaluationScope(table: table) let delegate = TestTaskPlanningDelegate(clientDelegate: MockTestTaskPlanningClientDelegate(), fs: localFS) let optionContext = await spec.discoveredCommandLineToolSpecInfo(producer, scope, delegate) - try await #expect(spec.computeAdditionalLinkerArgs(producer, scope: scope, lookup: { _ in nil }, inputFileTypes: [], optionContext: optionContext, delegate: CapturingTaskGenerationDelegate(producer: producer, userPreferences: .defaultForTesting)).args == additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath)) + let actual = try await spec.computeAdditionalLinkerArgs( + producer, + scope: scope, lookup: { _ in nil }, + inputFileTypes: [], + optionContext: optionContext, + delegate: CapturingTaskGenerationDelegate( + producer: producer, + userPreferences: .defaultForTesting, + ), + ).args + let expected = additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath) + #expect(actual == expected) } // Check tool product type forced to dynamic link. @@ -186,7 +222,19 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda let scope = MacroEvaluationScope(table: table) let delegate = TestTaskPlanningDelegate(clientDelegate: MockTestTaskPlanningClientDelegate(), fs: localFS) let optionContext = await spec.discoveredCommandLineToolSpecInfo(producer, scope, delegate) - try await #expect(spec.computeAdditionalLinkerArgs(producer, scope: scope, lookup: { _ in nil }, inputFileTypes: [], optionContext: optionContext, delegate: CapturingTaskGenerationDelegate(producer: producer, userPreferences: .defaultForTesting)).args == additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath)) + let actual = try await spec.computeAdditionalLinkerArgs( + producer, + scope: scope, + lookup: { _ in nil }, + inputFileTypes: [], + optionContext: optionContext, + delegate: CapturingTaskGenerationDelegate( + producer: producer, + userPreferences: .defaultForTesting, + ), + ).args + let expected = additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath) + #expect(actual == expected) } // Check system stdlib option. @@ -201,7 +249,74 @@ fileprivate final class TestSwiftParserDelegate: TaskOutputParserDelegate, Senda let scope = MacroEvaluationScope(table: table) let delegate = TestTaskPlanningDelegate(clientDelegate: MockTestTaskPlanningClientDelegate(), fs: localFS) let optionContext = await spec.discoveredCommandLineToolSpecInfo(producer, scope, delegate) - try await #expect(spec.computeAdditionalLinkerArgs(producer, scope: scope, lookup: { _ in nil }, inputFileTypes: [], optionContext: optionContext, delegate: CapturingTaskGenerationDelegate(producer: producer, userPreferences: .defaultForTesting)).args == ([["-L/usr/lib/swift"]] + additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath))) + let actual = try await spec.computeAdditionalLinkerArgs( + producer, + scope: scope, + lookup: { _ in nil }, + inputFileTypes: [], + optionContext: optionContext, + delegate: CapturingTaskGenerationDelegate( + producer: producer, + userPreferences: .defaultForTesting, + ), + ).args + let expected = ([["-L/usr/lib/swift"]] + additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath)) + #expect(actual == expected) + } + } + + @Test( + arguments: LinkerDriverChoice.allCases, + ) + func standardLibraryLinkingAllPlatforms( + linkerDriverUT: LinkerDriverChoice + ) async throws { + try await withKnownIssue(isIntermittent: true) { + let core = try await getCore() + + // Computes the expected standard swift linker arguments. + func additionalSwiftLinkerArgs(_ spec: CompilerSpec, _ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ stdlibPath: Path) -> [[String]] { + return localFS.exists(stdlibPath) ? [["-L\(stdlibPath.str)"], ["-L/usr/lib/swift"]] : [["-L/usr/lib/swift"]] + } + + let spec = try core.specRegistry.getSpec() as SwiftCompilerSpec + + let defaultToolchain = try #require(core.toolchainRegistry.defaultToolchain) + let swiftcPath = defaultToolchain.path.join("usr/bin/swiftc") + // Check force static stdlib on non-Apple platforms (Linux). + do { + let producer = try MockCommandProducer(core: core, productTypeIdentifier: "com.apple.product-type.framework", platform: "linux") + let stdlibPath = swiftcPath.dirname.dirname.join("lib/swift_static/linux") + var table = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace) + table.push(BuiltinMacros.SWIFT_EXEC, literal: swiftcPath.str) + table.push(BuiltinMacros.SWIFT_STDLIB, literal: "swiftCore") + table.push(BuiltinMacros.PLATFORM_NAME, literal: "linux") + table.push(BuiltinMacros.SWIFT_FORCE_STATIC_LINK_STDLIB, literal: true) + table.push(BuiltinMacros.LINKER_DRIVER, literal: linkerDriverUT) + let scope = MacroEvaluationScope(table: table) + let delegate = TestTaskPlanningDelegate(clientDelegate: MockTestTaskPlanningClientDelegate(), fs: localFS) + let optionContext = await spec.discoveredCommandLineToolSpecInfo(producer, scope, delegate) + // On non-Apple platforms, should use standard static linking flags (no Darwin-specific flags) + let actual = try await spec.computeAdditionalLinkerArgs( + producer, + scope: scope, + lookup: { _ in nil }, + inputFileTypes: [], + optionContext: optionContext, + delegate: CapturingTaskGenerationDelegate( + producer: producer, + userPreferences: .defaultForTesting, + ), + ).args + let expectedAdditionalArgs: [[String]] = switch linkerDriverUT { + case .swiftc: [["-static-stdlib"]] + case .auto, .clang, .qcc: [] + } + let expected = additionalSwiftLinkerArgs(spec, producer, scope, stdlibPath) + expectedAdditionalArgs + #expect(actual == expected) + } + } when: { + linkerDriverUT == .qcc // qcc support is very rough } } }