Skip to content

Enable the static Linux SDK build #704

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 1 commit into from
Aug 11, 2025
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
2 changes: 2 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
linux_swift_versions: '["nightly-main", "nightly-6.2"]'
windows_swift_versions: '["nightly-main"]'
windows_build_command: 'swift test --no-parallel'
enable_linux_static_sdk_build: true
linux_static_sdk_build_command: SWIFTBUILD_STATIC_LINK=1 LLBUILD_STATIC_LINK=1 swift build
cmake-smoke-test:
name: cmake-smoke-test
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
Expand Down
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ let appleOS = true
let appleOS = false
#endif

let isStaticBuild = Context.environment["SWIFTBUILD_STATIC_LINK"] != nil
let useLocalDependencies = Context.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil
let useLLBuildFramework = Context.environment["SWIFTBUILD_LLBUILD_FWK"] != nil

Expand Down Expand Up @@ -447,6 +448,12 @@ for target in package.targets {
}
}

if isStaticBuild {
package.targets = package.targets.filter { target in
target.type != .test && !target.name.hasSuffix("TestSupport")
}
}

// `SWIFTCI_USE_LOCAL_DEPS` configures if dependencies are locally available to build
if useLocalDependencies {
package.dependencies += [
Expand Down
35 changes: 24 additions & 11 deletions Sources/SWBUtil/Library.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,24 @@ public enum Library: Sendable {
return unsafeBitCast(ptr, to: T.self)
}

public static func locate<T>(_ pointer: T.Type) -> Path {
public static func locate<T>(_ pointer: T.Type) throws -> Path {
#if os(Windows)
var handle: HMODULE?
guard GetModuleHandleExW(DWORD(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT), unsafeBitCast(pointer, to: LPCWSTR?.self), &handle) else {
return Path("")
}
do {
return try Path(SWB_GetModuleFileNameW(handle))
} catch {
return Path("")
throw SymbolLookupError(underlyingError: Win32Error(GetLastError()))
}
return try Path(SWB_GetModuleFileNameW(handle))
#else
let outPointer: UnsafeMutablePointer<CInterop.PlatformChar>
var info = Dl_info()
#if os(Android)
dladdr(unsafeBitCast(pointer, to: UnsafeMutableRawPointer.self), &info)
outPointer = UnsafeMutablePointer(mutating: info.dli_fname!)
#else
dladdr(unsafeBitCast(pointer, to: UnsafeMutableRawPointer?.self), &info)
outPointer = UnsafeMutablePointer(mutating: info.dli_fname)
#endif
return Path(platformString: outPointer)
guard let dli_fname = info.dli_fname else {
throw SymbolLookupError(underlyingError: nil)
}
return Path(platformString: UnsafeMutablePointer(mutating: dli_fname))
#endif
}
}
Expand All @@ -102,6 +98,23 @@ public struct LibraryOpenError: Error, CustomStringConvertible, Sendable {
}
}

public struct SymbolLookupError: Error, CustomStringConvertible, Sendable {
private let underlyingError: (any Error)?

public var description: String {
let message = "Could not locate shared object for pointer"
if let underlyingError {
return "\(message): \(underlyingError)"
}
return message
}

@usableFromInline
internal init(underlyingError: (any Error)?) {
self.underlyingError = underlyingError
}
}

// Library handles just store an opaque reference to the dlopen/LoadLibrary-returned pointer, and so are Sendable in practice based on how they are used.
public struct LibraryHandle: @unchecked Sendable {
#if os(Windows)
Expand Down
12 changes: 7 additions & 5 deletions Sources/SwiftBuild/SWBBuildServiceConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ typealias swb_build_service_connection_message_handler_t = @Sendable (UInt64, SW

/// Absolute path to the dyld-loaded dylib binary that contains this class.
fileprivate class var swiftbuildDylibPath: Path {
return Library.locate(SWBBuildServiceConnection.self)
get throws {
return try Library.locate(SWBBuildServiceConnection.self)
}
}

fileprivate enum State {
Expand Down Expand Up @@ -168,7 +170,7 @@ typealias swb_build_service_connection_message_handler_t = @Sendable (UInt64, SW
// Compute the path to the clang ASan dylib to use when launching the ASan variant of SWBBuildService.
// The linker adds a non-portable rpath to the directory containing the ASan dylib based on the path to the Xcode used to link the binary. We look in Bundle.main.bundlePath (SwiftBuild_asan) for .../Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/<vers>/lib/darwin so we can relaunch with the ASan library in the default toolchain of the Xcode we're part of.
// There are some examples of this rpath breaking which we've had to fix, e.g.: rdar://57759442&75222176
let asanDylib = SWBBuildServiceConnection.swiftbuildDylibPath.str.hasSuffix("_asan") ? SWBBuildServiceConnection.swiftbuildDylibPath.str : SWBBuildServiceConnection.swiftbuildDylibPath.str + "_asan"
let asanDylib = try SWBBuildServiceConnection.swiftbuildDylibPath.str.hasSuffix("_asan") ? SWBBuildServiceConnection.swiftbuildDylibPath.str : SWBBuildServiceConnection.swiftbuildDylibPath.str + "_asan"
if !FileManager.default.isExecutableFile(atPath: asanDylib) {
// We always look for the _asan variant of the build service executable since only it will have the rpaths we need to subsequently look up. However, if it's missing we should then just fall back to the normal variant.
break asan
Expand Down Expand Up @@ -605,7 +607,7 @@ extension SWBBuildServiceVariant {
case .default:
// Check if the binary containing this class ends with _asan, in which case, we interpret this as a signal that we're running in asan mode, and that we should
// load the service bundle in asan mode as well.
return SWBBuildServiceConnection.swiftbuildDylibPath.str.hasSuffix("_asan")
return (try? SWBBuildServiceConnection.swiftbuildDylibPath.str.hasSuffix("_asan")) ?? false
case .normal:
return false
case .asan:
Expand Down Expand Up @@ -705,7 +707,7 @@ fileprivate final class InProcessStaticConnection: ConnectionTransport {
buildServicePlugInsDirectory = URL(fileURLWithPath: path.dirname.str, isDirectory: true)
} else {
// If the build service executable is unbundled, then try to find the build service entry point in this executable.
let path = Library.locate(SWBBuildServiceConnection.self)
let path = try Library.locate(SWBBuildServiceConnection.self)
// If the build service executable is unbundled, assume that any plugins that may exist are next to our executable.
buildServicePlugInsDirectory = URL(fileURLWithPath: path.dirname.str, isDirectory: true)
}
Expand Down Expand Up @@ -802,7 +804,7 @@ fileprivate final class InProcessConnection: ConnectionTransport {
buildServicePlugInsDirectory = URL(fileURLWithPath: path.dirname.str, isDirectory: true)
} else {
// If the build service executable is unbundled, then try to find the build service entry point in this executable.
let path = Library.locate(SWBBuildServiceConnection.self)
let path = try Library.locate(SWBBuildServiceConnection.self)
handle = try Library.open(path)

// If the build service executable is unbundled, assume that any plugins that may exist are next to our executable.
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftBuildTests/ServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ fileprivate struct ServiceTests {

@Test(.requireSDKs(.macOS), .skipSwiftPackage, .skipIfEnvironment(key: "DYLD_IMAGE_SUFFIX", value: "_asan"), .disabled(if: getEnvironmentVariable("CI")?.isEmpty == false))
func ASanBuildService() async throws {
let testBundleExecutableFilePath = Library.locate(Self.self)
let testBundleExecutableFilePath = try Library.locate(Self.self)

// Whether the current XCTest executable is running in ASan mode. Most likely never true.
let runningWithASanSupport = testBundleExecutableFilePath.str.hasSuffix("_asan")
Expand Down