diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1f1e70d1..106b1ece 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -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 diff --git a/Package.swift b/Package.swift index f3d71a0f..99730240 100644 --- a/Package.swift +++ b/Package.swift @@ -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 @@ -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 += [ diff --git a/Sources/SWBUtil/Library.swift b/Sources/SWBUtil/Library.swift index a0174ad3..3412bbb1 100644 --- a/Sources/SWBUtil/Library.swift +++ b/Sources/SWBUtil/Library.swift @@ -63,28 +63,24 @@ public enum Library: Sendable { return unsafeBitCast(ptr, to: T.self) } - public static func locate(_ pointer: T.Type) -> Path { + public static func locate(_ 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 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 } } @@ -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) diff --git a/Sources/SwiftBuild/SWBBuildServiceConnection.swift b/Sources/SwiftBuild/SWBBuildServiceConnection.swift index 67265583..19efb51c 100644 --- a/Sources/SwiftBuild/SWBBuildServiceConnection.swift +++ b/Sources/SwiftBuild/SWBBuildServiceConnection.swift @@ -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 { @@ -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//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 @@ -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: @@ -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) } @@ -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. diff --git a/Tests/SwiftBuildTests/ServiceTests.swift b/Tests/SwiftBuildTests/ServiceTests.swift index aa928a09..f3e67770 100644 --- a/Tests/SwiftBuildTests/ServiceTests.swift +++ b/Tests/SwiftBuildTests/ServiceTests.swift @@ -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")