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
20 changes: 18 additions & 2 deletions Sources/SWBGenericUnixPlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,24 @@ 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:
// FreeBSD is always LLVM-based.
return true
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:
// Cross-compiling.
return operatingSystem != context.hostOperatingSystem
}
}()

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

Expand Down
165 changes: 165 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,110 @@ public enum OperatingSystem: Hashable, Sendable {
return .elf
}
}

private func detectHostLinuxDistribution() -> LinuxDistribution? {
return detectHostLinuxDistribution(fs: localFS)
}

/// Detects the Linux distribution by examining system files with an injected filesystem
/// Start with the "generic" /etc/os-release then fallback
/// to various distribution named files.
public func detectHostLinuxDistribution(fs: any FSProxy) -> LinuxDistribution? {
// Try /etc/os-release first (standard)
let osReleasePath = Path("/etc/os-release")
if fs.exists(osReleasePath) {
if let osReleaseData = try? fs.read(osReleasePath),
let osRelease = String(data: Data(osReleaseData.bytes), encoding: .utf8) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer the stdlib constructor instead of the Foundation one:

Suggested change
let osRelease = String(data: Data(osReleaseData.bytes), encoding: .utf8) {
let osRelease = String(decoding: Data(osReleaseData.bytes), as: UTF8.self) {

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 fs.exists(Path(file)) {
return LinuxDistribution(kind: kind)
}
}

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 Expand Up @@ -255,3 +419,4 @@ extension FixedWidthInteger {
return self != 0 ? self : other
}
}

Loading
Loading