Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 16 additions & 2 deletions Sources/SWBGenericUnixPlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,22 @@ struct GenericUnixSDKRegistryExtension: SDKRegistryExtension {
defaultProperties = [:]
}

if operatingSystem == .freebsd || operatingSystem != context.hostOperatingSystem {
// FreeBSD is always LLVM-based, and if we're cross-compiling, use lld
let shouldUseLLD = {
switch operatingSystem {
case .freebsd:
return true // FreeBSD is always LLVM-based.
case .linux:
// Amazon Linux 2 has a gold linker bug see: https://sourceware.org/bugzilla/show_bug.cgi?id=23016.
guard let distribution = operatingSystem.distribution else {
return false
}
return distribution.kind == .amazon && distribution.version == "2"
default:
return operatingSystem != context.hostOperatingSystem // Cross-compiling.
}
}()

if shouldUseLLD {
defaultProperties["ALTERNATE_LINKER"] = "lld"
}

Expand Down
155 changes: 155 additions & 0 deletions Sources/SWBUtil/ProcessInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,56 @@ extension ProcessInfo {
return .unknown
#endif
}


}

public struct LinuxDistribution: Hashable, Sendable {
public enum Kind: String, CaseIterable, Hashable, Sendable {
case unknown
case ubuntu
case debian
case amazon = "amzn"
case centos
case rhel
case fedora
case suse
case alpine
case arch

/// The display name for the distribution kind
public var displayName: String {
switch self {
case .unknown: return "Unknown Linux"
case .ubuntu: return "Ubuntu"
case .debian: return "Debian"
case .amazon: return "Amazon Linux"
case .centos: return "CentOS"
case .rhel: return "Red Hat Enterprise Linux"
case .fedora: return "Fedora"
case .suse: return "SUSE"
case .alpine: return "Alpine Linux"
case .arch: return "Arch Linux"
}
}
}

public let kind: Kind
public let version: String?

public init(kind: Kind, version: String? = nil) {
self.kind = kind
self.version = version
}

/// The display name for the distribution including version if available
public var displayName: String {
if let version = version {
return "\(kind.displayName) \(version)"
} else {
return kind.displayName
}
}
}

public enum OperatingSystem: Hashable, Sendable {
Expand Down Expand Up @@ -157,6 +207,16 @@ public enum OperatingSystem: Hashable, Sendable {
}
}

/// The distribution if this is a Linux operating system
public var distribution: LinuxDistribution? {
switch self {
case .linux:
return detectHostLinuxDistribution()
default:
return nil
}
}

public var imageFormat: ImageFormat {
switch self {
case .macOS, .iOS, .tvOS, .watchOS, .visionOS:
Expand All @@ -167,6 +227,101 @@ public enum OperatingSystem: Hashable, Sendable {
return .elf
}
}

/// Detects the Linux distribution by examining system files
/// Start with the "generic" /etc/os-release then fallback
/// to various distribution named files.
private func detectHostLinuxDistribution() -> LinuxDistribution? {
#if os(Linux)
// Try /etc/os-release first (standard)
if let osRelease = try? String(contentsOfFile: "/etc/os-release") {
if let distribution = parseOSRelease(osRelease) {
return distribution
}
}
// Fallback to distribution-specific files
let distributionFiles: [(String, LinuxDistribution.Kind)] = [
("/etc/ubuntu-release", .ubuntu),
("/etc/debian_version", .debian),
("/etc/amazon-release", .amazon),
("/etc/centos-release", .centos),
("/etc/redhat-release", .rhel),
("/etc/fedora-release", .fedora),
("/etc/SuSE-release", .suse),
("/etc/alpine-release", .alpine),
("/etc/arch-release", .arch),
]
for (file, kind) in distributionFiles {
if FileManager.default.fileExists(atPath: file) {
return LinuxDistribution(kind: kind)
}
}
#endif
return nil
}

/// Parses /etc/os-release content to determine distribution and version
/// Fallback to just getting the distribution from specific files.
private func parseOSRelease(_ content: String) -> LinuxDistribution? {
let lines = content.components(separatedBy: .newlines)
var id: String?
var idLike: String?
var versionId: String?

// Parse out ID, ID_LIKE and VERSION_ID
for line in lines {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("ID=") {
id = String(trimmed.dropFirst(3)).trimmingCharacters(in: CharacterSet(charactersIn: "\""))
} else if trimmed.hasPrefix("ID_LIKE=") {
idLike = String(trimmed.dropFirst(8)).trimmingCharacters(in: CharacterSet(charactersIn: "\""))
} else if trimmed.hasPrefix("VERSION_ID=") {
versionId = String(trimmed.dropFirst(11)).trimmingCharacters(in: CharacterSet(charactersIn: "\""))
}
}

// Check ID first
if let id = id {
let kind: LinuxDistribution.Kind?
switch id.lowercased() {
case "ubuntu": kind = .ubuntu
case "debian": kind = .debian
case "amzn": kind = .amazon
case "centos": kind = .centos
case "rhel": kind = .rhel
case "fedora": kind = .fedora
case "suse", "opensuse", "opensuse-leap", "opensuse-tumbleweed": kind = .suse
case "alpine": kind = .alpine
case "arch": kind = .arch
default: kind = nil
}

if let kind = kind {
return LinuxDistribution(kind: kind, version: versionId)
}
}

// Check ID_LIKE as fallback
if let idLike = idLike {
let likes = idLike.components(separatedBy: .whitespaces)
for like in likes {
let kind: LinuxDistribution.Kind?
switch like.lowercased() {
case "ubuntu": kind = .ubuntu
case "debian": kind = .debian
case "rhel", "fedora": kind = .rhel
case "suse": kind = .suse
case "arch": kind = .arch
default: kind = nil
}

if let kind = kind {
return LinuxDistribution(kind: kind, version: versionId)
}
}
}
return nil
}
}

public enum ImageFormat {
Expand Down
Loading
Loading