Skip to content
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
24 changes: 0 additions & 24 deletions Sources/SWBAndroidPlatform/AndroidSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 #/(?<arch>.+)-(?<vendor>.+)-(?<system>.+)-(?<environment>.+)/#.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
Expand Down
13 changes: 8 additions & 5 deletions Sources/SWBAndroidPlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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([
Expand Down
138 changes: 91 additions & 47 deletions Sources/SWBGenericUnixPlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 []
}

Expand Down Expand Up @@ -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),
Expand All @@ -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)
]
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SWBTestSupport/CoreTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBUtil/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ add_library(SWBUtil
LazyCache.swift
Library.swift
LineReader.swift
LLVMTriple.swift
Lock.swift
MachO.swift
Math.swift
Expand Down
50 changes: 50 additions & 0 deletions Sources/SWBUtil/LLVMTriple.swift
Original file line number Diff line number Diff line change
@@ -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 #/(?<arch>[^-]+)-(?<vendor>[^-]+)-(?<system>[^-]+)(-(?<environment>[^-]+))?/#.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)"
}
}
}
4 changes: 2 additions & 2 deletions Sources/SWBWindowsPlatform/VSInstallation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down