Skip to content

Commit

Permalink
Add FreeBSD support
Browse files Browse the repository at this point in the history
This allows building Swift Build for FreeBSD hosts, as well as building for a FreeBSD target from a FreeBSD host.
  • Loading branch information
jakepetroules committed Feb 7, 2025
1 parent 0f05cd2 commit 0c69645
Show file tree
Hide file tree
Showing 19 changed files with 102 additions and 34 deletions.
22 changes: 15 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ let package = Package(
"SWBBuildSystem",
"SWBServiceCore",
"SWBTaskExecution",
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .android, .windows])),
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .openbsd, .android, .windows, .custom("freebsd")])),
],
swiftSettings: swiftSettings(languageMode: .v5)),
.target(
Expand Down Expand Up @@ -183,8 +183,8 @@ let package = Package(
"SWBCSupport",
"SWBLibc",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Crypto", package: "swift-crypto", condition: .when(platforms: [.linux, .android])),
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .android, .windows])),
.product(name: "Crypto", package: "swift-crypto", condition: .when(platforms: [.linux, .openbsd, .android, .custom("freebsd")])),
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .openbsd, .android, .windows, .custom("freebsd")])),
],
swiftSettings: swiftSettings(languageMode: .v5)),
.target(
Expand Down Expand Up @@ -412,12 +412,20 @@ if useLocalDependencies {
package.dependencies += [.package(path: "../llbuild"),]
}
} else {
#if os(FreeBSD)
package.dependencies += [
// https://github.com/apple/swift-system/commit/4fa2a719c5d225fc763f21f2d341c0c8d825d65e is not yet in a tag
.package(url: "https://github.com/apple/swift-system.git", branch: "main"),
]
#else
package.dependencies += [
// https://github.com/apple/swift-crypto/issues/262
// 3.7.1 introduced a regression which fails to link on aarch64-windows; revert to <4.0.0 for the upper bound when this is fixed
.package(url: "https://github.com/apple/swift-crypto.git", "2.0.0"..<"3.7.1"),
.package(url: "https://github.com/apple/swift-driver.git", branch: "main"),
.package(url: "https://github.com/apple/swift-system.git", .upToNextMajor(from: "1.4.0")),
]
#endif

package.dependencies += [
.package(url: "https://github.com/apple/swift-crypto.git", "2.0.0"..<"4.0.0"),
.package(url: "https://github.com/apple/swift-driver.git", branch: "main"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.3"),
]
if !useLLBuildFramework {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SWBCore/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,6 @@ struct CoreRegistryDelegate : PlatformRegistryDelegate, SDKRegistryDelegate, Spe
extension OperatingSystem {
/// Whether the Core is allowed to create a fallback toolchain, SDK, and platform for this operating system in cases where no others have been provided.
internal var createFallbackSystemToolchain: Bool {
return self == .linux
return self == .linux || self == .freebsd
}
}
4 changes: 3 additions & 1 deletion Sources/SWBCore/SDKRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,10 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible {
private func fallbackSystemSDKSettings(operatingSystem: OperatingSystem) throws -> [String: PropertyListItem] {
let defaultProperties: [String: PropertyListItem]
switch operatingSystem {
case .linux:
case .linux, .freebsd:
defaultProperties = [
"LIBTOOL_USE_RESPONSE_FILE": .plString("NO"), // TODO: Need to make this specific to FreeBSD

// Workaround to avoid `-add_ast_path` on Linux, apparently this needs to perform some "swift modulewrap" step instead.
"GCC_GENERATE_DEBUGGING_SYMBOLS": .plString("NO"),

Expand Down
2 changes: 2 additions & 0 deletions Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ public final class BuiltinMacros {
public static let LIBRARY_SEARCH_PATHS = BuiltinMacros.declarePathListMacro("LIBRARY_SEARCH_PATHS")
public static let LIBTOOL = BuiltinMacros.declarePathMacro("LIBTOOL")
public static let LIBTOOL_DEPENDENCY_INFO_FILE = BuiltinMacros.declarePathMacro("LIBTOOL_DEPENDENCY_INFO_FILE")
public static let LIBTOOL_USE_RESPONSE_FILE = BuiltinMacros.declareBooleanMacro("LIBTOOL_USE_RESPONSE_FILE")
public static let LINKER = BuiltinMacros.declareStringMacro("LINKER")
public static let ALTERNATE_LINKER = BuiltinMacros.declareStringMacro("ALTERNATE_LINKER")
public static let LINK_OBJC_RUNTIME = BuiltinMacros.declareBooleanMacro("LINK_OBJC_RUNTIME")
Expand Down Expand Up @@ -1853,6 +1854,7 @@ public final class BuiltinMacros {
LIBRARY_SEARCH_PATHS,
LIBTOOL,
LIBTOOL_DEPENDENCY_INFO_FILE,
LIBTOOL_USE_RESPONSE_FILE,
LINKER,
LINK_OBJC_RUNTIME,
LINK_WITH_STANDARD_LIBRARIES,
Expand Down
2 changes: 2 additions & 0 deletions Sources/SWBCore/Settings/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5269,6 +5269,8 @@ extension OperatingSystem {
return "windows"
case .linux:
return "linux"
case .freebsd:
return "freebsd"
case .android:
return "android"
case .unknown:
Expand Down
19 changes: 12 additions & 7 deletions Sources/SWBCore/Specs/Tools/LinkerTools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1420,17 +1420,22 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType {

override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) async {
var inputPaths = cbc.inputs.map({ $0.absolutePath })
var specialArgs = [String]()

// Define the linker file list.
let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__)
if !fileListPath.isEmpty {
let contents = cbc.inputs.map({ return $0.absolutePath.strWithPosixSlashes + "\n" }).joined(separator: "")
cbc.producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: [], output: fileListPath), delegate, contents: ByteString(encodingAsUTF8: contents), permissions: nil, preparesForIndexing: false, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
inputPaths.append(fileListPath)
if cbc.scope.evaluate(BuiltinMacros.LIBTOOL_USE_RESPONSE_FILE) {
// Define the linker file list.
let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__)
if !fileListPath.isEmpty {
let contents = cbc.inputs.map({ return $0.absolutePath.strWithPosixSlashes + "\n" }).joined(separator: "")
cbc.producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: [], output: fileListPath), delegate, contents: ByteString(encodingAsUTF8: contents), permissions: nil, preparesForIndexing: false, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
inputPaths.append(fileListPath)
}
} else {
specialArgs.append(contentsOf: cbc.inputs.map { $0.absolutePath.str })
inputPaths.append(contentsOf: cbc.inputs.map { $0.absolutePath })
}

// Add arguments for the contents of the Link Binaries build phase.
var specialArgs = [String]()
specialArgs.append(contentsOf: libraries.flatMap { specifier -> [String] in
let basename = specifier.path.basename

Expand Down
21 changes: 17 additions & 4 deletions Sources/SWBCore/ToolchainRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -440,10 +440,23 @@ public final class ToolchainRegistry: @unchecked Sendable {
return
}

if let swift = StackedSearchPath(environment: ProcessInfo.processInfo.cleanEnvironment, fs: fs).lookup(Path("swift")), fs.exists(swift) && swift.normalize().str.hasSuffix("/usr/bin/swift") {
let path = swift.dirname.dirname.dirname
let llvmDirectories = try fs.listdir(Path("/usr/lib")).filter { $0.hasPrefix("llvm-") }.sorted().reversed()
try register(Toolchain(Self.defaultToolchainIdentifier, "Default", Version(), [], path, [], llvmDirectories.map { "/usr/lib/\($0)/lib" } + ["/usr/lib64"], [:], [:], [:], executableSearchPaths: [path.join("usr").join("bin"), path.join("usr").join("local").join("bin"), path.join("usr").join("libexec")], fs: fs))
if let swift = StackedSearchPath(environment: ProcessInfo.processInfo.cleanEnvironment, fs: fs).lookup(Path("swift")), fs.exists(swift) {
let hasUsrBin = swift.normalize().str.hasSuffix("/usr/bin/swift")
let hasUsrLocalBin = swift.normalize().str.hasSuffix("/usr/local/bin/swift")
let path: Path
switch (hasUsrBin, hasUsrLocalBin) {
case (true, false):
path = swift.dirname.dirname.dirname
case (false, true):
path = swift.dirname.dirname.dirname.dirname
case (false, false):
return
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())
try register(Toolchain(Self.defaultToolchainIdentifier, "Default", Version(), [], path, [], llvmDirectories.map { "/usr/lib/\($0)/lib" } + llvmDirectoriesLocal.map { "/usr/local/\($0)/lib" } + ["/usr/lib64"], [:], [:], [:], executableSearchPaths: [path.join("usr").join("bin"), path.join("usr").join("local").join("bin"), path.join("usr").join("libexec")], fs: fs))
}
}

Expand Down
5 changes: 4 additions & 1 deletion Sources/SWBGenericUnixPlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ struct GenericUnixPlatformSpecsExtension: SpecificationsExtension {
}

func specificationDomains() -> [String: [String]] {
["linux": ["generic-unix"]]
[
"linux": ["generic-unix"],
"freebsd": ["generic-unix"],
]
}
}
1 change: 1 addition & 0 deletions Sources/SWBGenericUnixPlatform/UnixLibtool.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
Name = __INPUT_FILE_LIST_PATH__;
Type = Path;
// this is set up for us as a read-only property
Condition = "$(LIBTOOL_USE_RESPONSE_FILE) != NO";
DefaultValue = "$(LINK_FILE_LIST_$(variant)_$(arch))";
CommandLineArgs = (
"@$(value)",
Expand Down
10 changes: 10 additions & 0 deletions Sources/SWBTestSupport/RunDestinationTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ extension _RunDestinationInfo {
windows
case .linux:
linux
case .freebsd:
freebsd
case .android:
android
case .unknown:
Expand Down Expand Up @@ -259,6 +261,14 @@ extension _RunDestinationInfo {
return .init(platform: "linux", sdk: "linux", sdkVariant: "linux", targetArchitecture: arch, supportedArchitectures: ["x86_64", "aarch64"], disableOnlyActiveArch: false)
}

/// A run destination targeting FreeBSD generic device, using the public SDK.
package static var freebsd: Self {
guard let arch = Architecture.hostStringValue else {
preconditionFailure("Unknown architecture \(Architecture.host.stringValue ?? "<nil>")")
}
return .init(platform: "freebsd", sdk: "freebsd", sdkVariant: "freebsd", targetArchitecture: arch, supportedArchitectures: ["x86_64", "aarch64"], disableOnlyActiveArch: false)
}

/// A run destination targeting Android generic device, using the public SDK.
package static var android: Self {
return .init(platform: "android", sdk: "android", sdkVariant: "android", targetArchitecture: "undefined_arch", supportedArchitectures: ["armv7", "aarch64", "riscv64", "i686", "x86_64"], disableOnlyActiveArch: true)
Expand Down
3 changes: 3 additions & 0 deletions Sources/SWBTestSupport/SkippedTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ extension KnownSDK {
return windows
case .success(.linux):
return linux
case .success(.freebsd):
return freebsd
case .success(.android):
return android
case .success(.unknown), .failure:
Expand All @@ -68,6 +70,7 @@ extension KnownSDK {
extension KnownSDK {
package static let windows: Self = "windows"
package static let linux: Self = "linux"
package static let freebsd: Self = "freebsd"
package static let android: Self = "android"
package static let qnx: Self = "qnx"
package static let wasi: Self = "wasi"
Expand Down
6 changes: 6 additions & 0 deletions Sources/SWBUniversalPlatform/Specs/Libtool.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
// Input file lists
{ Name = __INPUT_FILE_LIST_PATH__;
Type = Path;
Condition = "$(LIBTOOL_USE_RESPONSE_FILE) != NO";
DefaultValue = "$(LINK_FILE_LIST_$(variant)_$(arch))"; // this is set up for us as a read-only property
CommandLineFlag = "-filelist";
IsInputDependency = Yes;
Expand Down Expand Up @@ -104,6 +105,11 @@
DefaultValue = "$(OBJECT_FILE_DIR_$(CURRENT_VARIANT))/$(CURRENT_ARCH)/$(PRODUCT_NAME)_libtool_dependency_info.dat";
},

{
Name = "LIBTOOL_USE_RESPONSE_FILE";
Type = Boolean;
DefaultValue = YES;
},
);
}
)
8 changes: 7 additions & 1 deletion Sources/SWBUtil/Architecture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,13 @@ public struct Architecture: Sendable {
if uname(&buf) == 0 {
return withUnsafeBytes(of: &buf.machine) { buf in
let data = Data(buf)
return String(decoding: data[0...(data.lastIndex(where: { $0 != 0 }) ?? 0)], as: UTF8.self)
let value = String(decoding: data[0...(data.lastIndex(where: { $0 != 0 }) ?? 0)], as: UTF8.self)
switch value {
case "amd64":
return "x86_64"
default:
return value
}
}
}
#endif
Expand Down
6 changes: 3 additions & 3 deletions Sources/SWBUtil/FSProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ class LocalFS: FSProxy, @unchecked Sendable {
}

func listExtendedAttributes(_ path: Path) throws -> [String] {
#if os(Windows)
#if os(Windows) || os(FreeBSD) // FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
// no xattrs on Windows
return []
#else
Expand Down Expand Up @@ -796,7 +796,7 @@ class LocalFS: FSProxy, @unchecked Sendable {
}

func setExtendedAttribute(_ path: Path, key: String, value: ByteString) throws {
#if os(Windows)
#if os(Windows) || os(FreeBSD) // FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
// no xattrs on Windows
#else
try value.bytes.withUnsafeBufferPointer { buf throws -> Void in
Expand All @@ -813,7 +813,7 @@ class LocalFS: FSProxy, @unchecked Sendable {
}

func getExtendedAttribute(_ path: Path, key: String) throws -> ByteString? {
#if os(Windows)
#if os(Windows) || os(FreeBSD) // FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
return nil // no xattrs on Windows
#else
var bufferSize = 4096
Expand Down
2 changes: 1 addition & 1 deletion Sources/SWBUtil/Lock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public final class Lock: @unchecked Sendable {
let mutex: UnsafeMutablePointer<SRWLOCK> = UnsafeMutablePointer.allocate(capacity: 1)
#else
@usableFromInline
let mutex: UnsafeMutablePointer<pthread_mutex_t> = UnsafeMutablePointer.allocate(capacity: 1)
let mutex: UnsafeMutablePointer<pthread_mutex_t?> = UnsafeMutablePointer.allocate(capacity: 1)
#endif

public init() {
Expand Down
5 changes: 4 additions & 1 deletion Sources/SWBUtil/ProcessInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ extension ProcessInfo {
return .windows
#elseif os(Linux)
return .linux
#elseif os(FreeBSD)
return .freebsd
#else
if try FileManager.default.isReadableFile(atPath: systemVersionPlistURL.filePath.str) {
switch try systemVersion().productName {
Expand Down Expand Up @@ -125,6 +127,7 @@ public enum OperatingSystem: Hashable, Sendable {
case visionOS(simulator: Bool)
case windows
case linux
case freebsd
case android
case unknown

Expand Down Expand Up @@ -153,7 +156,7 @@ public enum OperatingSystem: Hashable, Sendable {
return .macho
case .windows:
return .pe
case .linux, .android, .unknown:
case .linux, .freebsd, .android, .unknown:
return .elf
}
}
Expand Down
12 changes: 8 additions & 4 deletions Tests/SWBBuildSystemTests/BuildOperationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ fileprivate struct BuildOperationTests: CoreBasedTests {
type: .dynamicLibrary,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"DYLIB_INSTALL_NAME_BASE": "@rpath",
"DYLIB_INSTALL_NAME_BASE[sdk=linux*]": "$ORIGIN",
"DYLIB_INSTALL_NAME_BASE": "$ORIGIN",
"DYLIB_INSTALL_NAME_BASE[sdk=macosx*]": "@rpath",

// FIXME: Find a way to make these default
"EXECUTABLE_PREFIX": "lib",
Expand Down Expand Up @@ -145,8 +145,12 @@ fileprivate struct BuildOperationTests: CoreBasedTests {

let toolchain = try #require(try await getCore().toolchainRegistry.defaultToolchain)
let environment: [String: String]
if destination.platform == "linux" {
environment = ["LD_LIBRARY_PATH": toolchain.path.join("usr/lib/swift/linux").str]
if ["linux", "freebsd"].contains(destination.platform) {
// FIXME: Should be for "any ELF platform", not hardcoded to Linux and FreeBSD
environment = ["LD_LIBRARY_PATH": [
toolchain.path.join("usr/lib/swift/\(destination.platform)").str,
toolchain.path.join("usr/local/lib/swift/\(destination.platform)").str,
].joined(separator: String(Path.pathEnvironmentSeparator))]
} else {
environment = [:]
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/SWBUtilTests/FSProxyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ import SWBTestSupport
// String.utf8CString *includes* the trailing null byte
#if canImport(Darwin)
#expect(setxattr(testDataPath.str, "attr.string", "true", "true".utf8CString.count, 0, 0) == 0)
#elseif !os(Windows)
#elseif !os(Windows) && !os(FreeBSD) // FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
#expect(setxattr(testDataPath.str, "attr.string", "true", "true".utf8CString.count, 0) == 0)
#endif
#expect(try localFS.getExtendedAttribute(testDataPath, key: "attr.string") == "true\0")
Expand All @@ -574,7 +574,7 @@ import SWBTestSupport
// String.utf8CString *includes* the trailing null byte
#if canImport(Darwin)
#expect(setxattr(testDataPath.str, "attr.string", "tr\0ue", "tr\0ue".utf8CString.count, 0, 0) == 0)
#elseif !os(Windows)
#elseif !os(Windows) && !os(FreeBSD) // FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
#expect(setxattr(testDataPath.str, "attr.string", "tr\0ue", "tr\0ue".utf8CString.count, 0) == 0)
#endif
#expect(try localFS.getExtendedAttribute(testDataPath, key: "attr.string") == "tr\0ue\0")
Expand Down
2 changes: 1 addition & 1 deletion Tests/SWBUtilTests/MiscTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import SWBUtil
#expect(SWBUtil.userCacheDir().str.hasPrefix("/var/folders"))
case .android:
#expect(SWBUtil.userCacheDir().str.hasPrefix("/data/local/tmp"))
case .linux, .unknown:
case .linux, .freebsd, .unknown:
#expect(SWBUtil.userCacheDir().str.hasPrefix("/tmp"))
}
}
Expand Down

0 comments on commit 0c69645

Please sign in to comment.