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 14, 2025
1 parent 303fe88 commit 134bea2
Show file tree
Hide file tree
Showing 20 changed files with 127 additions and 34 deletions.
22 changes: 15 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,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")])),
],
exclude: ["CMakeLists.txt"],
swiftSettings: swiftSettings(languageMode: .v5)),
Expand Down Expand Up @@ -197,8 +197,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")])),
],
exclude: ["CMakeLists.txt"],
swiftSettings: swiftSettings(languageMode: .v5)),
Expand Down Expand Up @@ -442,12 +442,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
}
}
2 changes: 1 addition & 1 deletion Sources/SWBCore/SDKRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send
private func fallbackSystemSDKSettings(operatingSystem: OperatingSystem) throws -> [String: PropertyListItem] {
let defaultProperties: [String: PropertyListItem]
switch operatingSystem {
case .linux:
case .linux, .freebsd:
defaultProperties = [
// 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 @@ -809,6 +809,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 @@ -1861,6 +1862,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 @@ -5281,6 +5281,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 @@ -1372,17 +1372,22 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u

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
27 changes: 27 additions & 0 deletions Sources/SWBGenericUnixPlatform/FreeBSDLibtool.xcspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

(
{
Domain = freebsd;
Identifier = com.apple.pbx.linkers.libtool;
BasedOn = generic-unix:com.apple.pbx.linkers.libtool;
Type = Linker;
Options = (
{
Name = "LIBTOOL_USE_RESPONSE_FILE";
Type = Boolean;
DefaultValue = NO;
},
);
},
)
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
}
}
}
return nil
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 134bea2

Please sign in to comment.