From ac858e75dfebe711bca92862a487026882e87a76 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Mon, 4 Aug 2025 10:58:45 -0700 Subject: [PATCH] Handle the OS version in the triple for FreeBSD We're going to keep the versioned triple, so it needs to be passed through after all. --- Sources/SWBAndroidPlatform/AndroidSDK.swift | 24 --- Sources/SWBAndroidPlatform/Plugin.swift | 13 +- Sources/SWBGenericUnixPlatform/Plugin.swift | 138 ++++++++++++------ Sources/SWBTestSupport/CoreTestSupport.swift | 2 +- Sources/SWBUtil/CMakeLists.txt | 1 + Sources/SWBUtil/LLVMTriple.swift | 50 +++++++ .../SWBWindowsPlatform/VSInstallation.swift | 4 +- 7 files changed, 153 insertions(+), 79 deletions(-) create mode 100644 Sources/SWBUtil/LLVMTriple.swift diff --git a/Sources/SWBAndroidPlatform/AndroidSDK.swift b/Sources/SWBAndroidPlatform/AndroidSDK.swift index 3b934838..9a2c00a0 100644 --- a/Sources/SWBAndroidPlatform/AndroidSDK.swift +++ b/Sources/SWBAndroidPlatform/AndroidSDK.swift @@ -134,30 +134,6 @@ public import Foundation case bits64 = 64 } - @_spi(Testing) public struct LLVMTriple: Codable, Equatable, Sendable { - public var arch: String - public var vendor: String - public var system: String - public var environment: String - - var description: String { - "\(arch)-\(vendor)-\(system)-\(environment)" - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - let triple = try container.decode(String.self) - if let match = try #/(?.+)-(?.+)-(?.+)-(?.+)/#.wholeMatch(in: triple) { - self.arch = String(match.output.arch) - self.vendor = String(match.output.vendor) - self.system = String(match.output.system) - self.environment = String(match.output.environment) - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid triple string: \(triple)") - } - } - } - public let bitness: Bitness public let `default`: Bool public let deprecated: Bool diff --git a/Sources/SWBAndroidPlatform/Plugin.swift b/Sources/SWBAndroidPlatform/Plugin.swift index 3eb0ca1d..117e7c6e 100644 --- a/Sources/SWBAndroidPlatform/Plugin.swift +++ b/Sources/SWBAndroidPlatform/Plugin.swift @@ -131,11 +131,14 @@ struct AndroidPlatformExtension: PlatformInfoExtension { let abis = androidNdk.abis let deploymentTargetRange = androidNdk.deploymentTargetRange - let allPossibleTriples = abis.values.flatMap { abi in - (max(deploymentTargetRange.min, abi.min_os_version)...deploymentTargetRange.max).map { deploymentTarget in + let allPossibleTriples = try abis.values.flatMap { abi in + try (max(deploymentTargetRange.min, abi.min_os_version)...deploymentTargetRange.max).map { deploymentTarget in var triple = abi.llvm_triple triple.vendor = "unknown" // Android NDK uses "none", Swift SDKs use "unknown" - triple.environment += "\(deploymentTarget)" + guard let env = triple.environment else { + throw StubError.error("Android triples must have an environment") + } + triple.environment = "\(env)\(deploymentTarget)" return triple } }.map(\.description) @@ -173,8 +176,8 @@ struct AndroidPlatformExtension: PlatformInfoExtension { "CustomProperties": .plDict([ // Unlike most platforms, the Android version goes on the environment field rather than the system field // FIXME: Make this configurable in a better way so we don't need to push build settings at the SDK definition level - "LLVM_TARGET_TRIPLE_OS_VERSION": .plString("linux"), - "LLVM_TARGET_TRIPLE_SUFFIX": .plString("-android$(ANDROID_DEPLOYMENT_TARGET)"), + "LLVM_TARGET_TRIPLE_OS_VERSION": .plString("$(SWIFT_PLATFORM_TARGET_PREFIX)"), + "LLVM_TARGET_TRIPLE_SUFFIX": .plString("-android$($(DEPLOYMENT_TARGET_SETTING_NAME))"), ].merging(swiftSettings, uniquingKeysWith: { _, new in new })), "SupportedTargets": .plDict([ "android": .plDict([ diff --git a/Sources/SWBGenericUnixPlatform/Plugin.swift b/Sources/SWBGenericUnixPlatform/Plugin.swift index 0d6cd88c..632a4cae 100644 --- a/Sources/SWBGenericUnixPlatform/Plugin.swift +++ b/Sources/SWBGenericUnixPlatform/Plugin.swift @@ -15,11 +15,44 @@ import SWBCore import Foundation @PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) { + let plugin = GenericUnixPlugin() manager.register(GenericUnixDeveloperDirectoryExtension(), type: DeveloperDirectoryExtensionPoint.self) manager.register(GenericUnixPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self) manager.register(GenericUnixPlatformInfoExtension(), type: PlatformInfoExtensionPoint.self) - manager.register(GenericUnixSDKRegistryExtension(), type: SDKRegistryExtensionPoint.self) - manager.register(GenericUnixToolchainRegistryExtension(), type: ToolchainRegistryExtensionPoint.self) + manager.register(GenericUnixSDKRegistryExtension(plugin: plugin), type: SDKRegistryExtensionPoint.self) + manager.register(GenericUnixToolchainRegistryExtension(plugin: plugin), type: ToolchainRegistryExtensionPoint.self) +} + +final class GenericUnixPlugin: Sendable { + func swiftExecutablePath(fs: any FSProxy) -> Path? { + [ + Environment.current["SWIFT_EXEC"].map(Path.init), + StackedSearchPath(environment: .current, fs: fs).lookup(Path("swift")) + ].compactMap { $0 }.first(where: fs.exists) + } + + func swiftTargetInfo(swiftExecutablePath: Path) async throws -> SwiftTargetInfo { + let args = ["-print-target-info"] + let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: swiftExecutablePath.str), arguments: args) + guard executionResult.exitStatus.isSuccess else { + throw RunProcessNonZeroExitError(args: [swiftExecutablePath.str] + args, workingDirectory: nil, environment: [:], status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) + } + return try JSONDecoder().decode(SwiftTargetInfo.self, from: executionResult.stdout) + } +} + +struct SwiftTargetInfo: Decodable { + struct TargetInfo: Decodable { + let triple: LLVMTriple + let unversionedTriple: LLVMTriple + } + let target: TargetInfo +} + +extension SwiftTargetInfo.TargetInfo { + var tripleVersion: String? { + triple != unversionedTriple && triple.system.hasPrefix(unversionedTriple.system) ? String(triple.system.dropFirst(unversionedTriple.system.count)).nilIfEmpty : nil + } } struct GenericUnixDeveloperDirectoryExtension: DeveloperDirectoryExtension { @@ -69,9 +102,11 @@ struct GenericUnixPlatformInfoExtension: PlatformInfoExtension { } struct GenericUnixSDKRegistryExtension: SDKRegistryExtension { + let plugin: GenericUnixPlugin + func additionalSDKs(context: any SDKRegistryExtensionAdditionalSDKsContext) async throws -> [(path: Path, platform: SWBCore.Platform?, data: [String: PropertyListItem])] { let operatingSystem = context.hostOperatingSystem - guard operatingSystem.createFallbackSystemToolchain, let platform = try context.platformRegistry.lookup(name: operatingSystem.xcodePlatformName) else { + guard operatingSystem.createFallbackSystemToolchain, let platform = try context.platformRegistry.lookup(name: operatingSystem.xcodePlatformName), let swift = plugin.swiftExecutablePath(fs: context.fs) else { return [] } @@ -100,6 +135,23 @@ struct GenericUnixSDKRegistryExtension: SDKRegistryExtension { tripleEnvironment = "" } + let swiftTargetInfo = try await plugin.swiftTargetInfo(swiftExecutablePath: swift) + + let deploymentTargetSettings: [String: PropertyListItem] + if operatingSystem == .freebsd { + guard let tripleVersion = swiftTargetInfo.target.tripleVersion else { + throw StubError.error("Unknown FreeBSD triple version") + } + deploymentTargetSettings = [ + "DeploymentTargetSettingName": .plString("FREEBSD_DEPLOYMENT_TARGET"), + "DefaultDeploymentTarget": .plString(tripleVersion), + "MinimumDeploymentTarget": .plString(tripleVersion), + "MaximumDeploymentTarget": .plString(tripleVersion), + ] + } else { + deploymentTargetSettings = [:] + } + return try [(.root, platform, [ "Type": .plString("SDK"), "Version": .plString(Version(ProcessInfo.processInfo.operatingSystemVersion).zeroTrimmed.description), @@ -114,62 +166,54 @@ struct GenericUnixSDKRegistryExtension: SDKRegistryExtension { "LLVMTargetTripleEnvironment": .plString(tripleEnvironment), "LLVMTargetTripleSys": .plString(operatingSystem.xcodePlatformName), "LLVMTargetTripleVendor": .plString("unknown"), - ]) + ].merging(deploymentTargetSettings, uniquingKeysWith: { _, new in new })) ]), ])] } } struct GenericUnixToolchainRegistryExtension: ToolchainRegistryExtension { + let plugin: GenericUnixPlugin + func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async throws -> [Toolchain] { let operatingSystem = context.hostOperatingSystem - guard operatingSystem.createFallbackSystemToolchain else { + let fs = context.fs + guard operatingSystem.createFallbackSystemToolchain, let swift = plugin.swiftExecutablePath(fs: fs) else { return [] } - let fs = context.fs - - for swift in [ - Environment.current["SWIFT_EXEC"].map(Path.init), - StackedSearchPath(environment: .current, fs: fs).lookup(Path("swift")) - ].compactMap(\.self) { - if fs.exists(swift) { - let realSwiftPath = try fs.realpath(swift).dirname.normalize() - let hasUsrBin = realSwiftPath.str.hasSuffix("/usr/bin") - let hasUsrLocalBin = realSwiftPath.str.hasSuffix("/usr/local/bin") - let path: Path - switch (hasUsrBin, hasUsrLocalBin) { - case (true, false): - path = realSwiftPath.dirname.dirname - case (false, true): - path = realSwiftPath.dirname.dirname.dirname - case (false, false): - throw StubError.error("Unexpected toolchain layout for Swift installation path: \(realSwiftPath)") - case (true, true): - preconditionFailure() - } - let llvmDirectories = try Array(fs.listdir(Path("/usr/lib")).filter { $0.hasPrefix("llvm-") }.sorted().reversed()) - let llvmDirectoriesLocal = try Array(fs.listdir(Path("/usr/local")).filter { $0.hasPrefix("llvm") }.sorted().reversed()) - return [ - Toolchain( - identifier: ToolchainRegistry.defaultToolchainIdentifier, - displayName: "Default", - version: Version(), - aliases: ["default"], - path: path, - frameworkPaths: [], - libraryPaths: llvmDirectories.map { "/usr/lib/\($0)/lib" } + llvmDirectoriesLocal.map { "/usr/local/\($0)/lib" } + ["/usr/lib64"], - defaultSettings: [:], - overrideSettings: [:], - defaultSettingsWhenPrimary: [:], - executableSearchPaths: realSwiftPath.dirname.relativeSubpath(from: path).map { [path.join($0).join("bin")] } ?? [], - testingLibraryPlatformNames: [], - fs: fs) - ] - } + let realSwiftPath = try fs.realpath(swift).dirname.normalize() + let hasUsrBin = realSwiftPath.str.hasSuffix("/usr/bin") + let hasUsrLocalBin = realSwiftPath.str.hasSuffix("/usr/local/bin") + let path: Path + switch (hasUsrBin, hasUsrLocalBin) { + case (true, false): + path = realSwiftPath.dirname.dirname + case (false, true): + path = realSwiftPath.dirname.dirname.dirname + case (false, false): + throw StubError.error("Unexpected toolchain layout for Swift installation path: \(realSwiftPath)") + case (true, true): + preconditionFailure() } - - return [] + let llvmDirectories = try Array(fs.listdir(Path("/usr/lib")).filter { $0.hasPrefix("llvm-") }.sorted().reversed()) + let llvmDirectoriesLocal = try Array(fs.listdir(Path("/usr/local")).filter { $0.hasPrefix("llvm") }.sorted().reversed()) + return [ + Toolchain( + identifier: ToolchainRegistry.defaultToolchainIdentifier, + displayName: "Default", + version: Version(), + aliases: ["default"], + path: path, + frameworkPaths: [], + libraryPaths: llvmDirectories.map { "/usr/lib/\($0)/lib" } + llvmDirectoriesLocal.map { "/usr/local/\($0)/lib" } + ["/usr/lib64"], + defaultSettings: [:], + overrideSettings: [:], + defaultSettingsWhenPrimary: [:], + executableSearchPaths: realSwiftPath.dirname.relativeSubpath(from: path).map { [path.join($0).join("bin")] } ?? [], + testingLibraryPlatformNames: [], + fs: fs) + ] } } diff --git a/Sources/SWBTestSupport/CoreTestSupport.swift b/Sources/SWBTestSupport/CoreTestSupport.swift index 1bf280ea..5ce4bedf 100644 --- a/Sources/SWBTestSupport/CoreTestSupport.swift +++ b/Sources/SWBTestSupport/CoreTestSupport.swift @@ -56,7 +56,7 @@ extension Core { developerPath = .xcode(xcodeDeveloperDirPath) } else { // In the context of auto-generated package schemes, try to infer the active Xcode. - let potentialDeveloperPath = getEnvironmentVariable("PATH")?.components(separatedBy: String(Path.pathEnvironmentSeparator)).first.map(Path.init)?.dirname.dirname + let potentialDeveloperPath = getEnvironmentVariable(.path)?.components(separatedBy: String(Path.pathEnvironmentSeparator)).first.map(Path.init)?.dirname.dirname let versionInfo = potentialDeveloperPath?.dirname.join("version.plist") if let versionInfo = versionInfo, (try? PropertyList.fromPath(versionInfo, fs: localFS))?.dictValue?["ProjectName"] == "IDEApplication" { developerPath = potentialDeveloperPath.map { .xcode($0) } diff --git a/Sources/SWBUtil/CMakeLists.txt b/Sources/SWBUtil/CMakeLists.txt index 9d2d611d..681fc86e 100644 --- a/Sources/SWBUtil/CMakeLists.txt +++ b/Sources/SWBUtil/CMakeLists.txt @@ -55,6 +55,7 @@ add_library(SWBUtil LazyCache.swift Library.swift LineReader.swift + LLVMTriple.swift Lock.swift MachO.swift Math.swift diff --git a/Sources/SWBUtil/LLVMTriple.swift b/Sources/SWBUtil/LLVMTriple.swift new file mode 100644 index 00000000..2ec27fa5 --- /dev/null +++ b/Sources/SWBUtil/LLVMTriple.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +public struct LLVMTriple: Decodable, Equatable, Sendable, CustomStringConvertible { + public var arch: String + public var vendor: String + public var system: String + public var environment: String? + + public var description: String { + if let environment { + return "\(arch)-\(vendor)-\(system)-\(environment)" + } + return "\(arch)-\(vendor)-\(system)" + } + + public init(_ string: String) throws { + guard let match = try #/(?[^-]+)-(?[^-]+)-(?[^-]+)(-(?[^-]+))?/#.wholeMatch(in: string) else { + throw LLVMTripleError.invalidTripleStringFormat(string) + } + self.arch = String(match.output.arch) + self.vendor = String(match.output.vendor) + self.system = String(match.output.system) + self.environment = match.output.environment.map { String($0) } + } + + public init(from decoder: any Swift.Decoder) throws { + self = try Self(decoder.singleValueContainer().decode(String.self)) + } +} + +enum LLVMTripleError: Error, CustomStringConvertible { + case invalidTripleStringFormat(String) + + var description: String { + switch self { + case let .invalidTripleStringFormat(tripleString): + "Invalid triple string format: \(tripleString)" + } + } +} diff --git a/Sources/SWBWindowsPlatform/VSInstallation.swift b/Sources/SWBWindowsPlatform/VSInstallation.swift index 23ab705c..68c35abb 100644 --- a/Sources/SWBWindowsPlatform/VSInstallation.swift +++ b/Sources/SWBWindowsPlatform/VSInstallation.swift @@ -57,14 +57,14 @@ public struct VSInstallation: Decodable, Sendable { ] let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: vswhere.str), arguments: args) guard executionResult.exitStatus.isSuccess else { - throw RunProcessNonZeroExitError(args: args, workingDirectory: nil, environment: [:], status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) + throw RunProcessNonZeroExitError(args: [vswhere.str] + args, workingDirectory: nil, environment: [:], status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) } return try JSONDecoder().decode([VSInstallation].self, from: executionResult.stdout) } private static func vswherePath(fs: any FSProxy) throws -> Path? { var paths: [Path] = [] - if let path = try POSIX.getenv("PATH") { + if let path = getEnvironmentVariable(.path) { paths.append(contentsOf: path.split(separator: Path.pathEnvironmentSeparator).map(Path.init).filter { // PATH may contain unexpanded shell variable references $0.isAbsolute