Skip to content

Commit 2c41fba

Browse files
committed
Add FreeBSD support
This allows building Swift Build for FreeBSD hosts, as well as building for a FreeBSD target from a FreeBSD host. Also adds some speculative support for targeting OpenBSD on OpenBSD hosts, since SwiftPM has minimal support.
1 parent b78e04a commit 2c41fba

20 files changed

+116
-23
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ let package = Package(
110110
"SWBBuildSystem",
111111
"SWBServiceCore",
112112
"SWBTaskExecution",
113-
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .android, .windows])),
113+
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .openbsd, .android, .windows, .custom("freebsd")])),
114114
],
115115
exclude: ["CMakeLists.txt"],
116116
swiftSettings: swiftSettings(languageMode: .v5)),
@@ -201,7 +201,7 @@ let package = Package(
201201
"SWBCSupport",
202202
"SWBLibc",
203203
.product(name: "ArgumentParser", package: "swift-argument-parser"),
204-
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .android, .windows])),
204+
.product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux, .openbsd, .android, .windows, .custom("freebsd")])),
205205
],
206206
exclude: ["CMakeLists.txt"],
207207
swiftSettings: swiftSettings(languageMode: .v5)),

Sources/SWBCore/Settings/Settings.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5307,6 +5307,10 @@ extension OperatingSystem {
53075307
return "windows"
53085308
case .linux:
53095309
return "linux"
5310+
case .freebsd:
5311+
return "freebsd"
5312+
case .openbsd:
5313+
return "openbsd"
53105314
case .android:
53115315
return "android"
53125316
case .unknown:

Sources/SWBGenericUnixPlatform/Plugin.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ struct GenericUnixPlatformSpecsExtension: SpecificationsExtension {
3939
}
4040

4141
func specificationDomains() -> [String: [String]] {
42-
["linux": ["generic-unix"]]
42+
[
43+
"linux": ["generic-unix"],
44+
"freebsd": ["generic-unix"],
45+
"openbsd": ["generic-unix"],
46+
]
4347
}
4448
}
4549

@@ -73,9 +77,9 @@ struct GenericUnixSDKRegistryExtension: SDKRegistryExtension {
7377

7478
let defaultProperties: [String: PropertyListItem]
7579
switch operatingSystem {
76-
case .linux:
80+
case .linux, .freebsd:
7781
defaultProperties = [
78-
// Workaround to avoid `-dependency_info` on Linux.
82+
// Workaround to avoid `-dependency_info`.
7983
"LD_DEPENDENCY_INFO_FILE": .plString(""),
8084

8185
"GENERATE_TEXT_BASED_STUBS": "NO",
@@ -167,6 +171,6 @@ struct GenericUnixToolchainRegistryExtension: ToolchainRegistryExtension {
167171
extension OperatingSystem {
168172
/// 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.
169173
var createFallbackSystemToolchain: Bool {
170-
return self == .linux
174+
return self == .linux || self == .freebsd || self == .openbsd
171175
}
172176
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
(
14+
{
15+
Domain = freebsd;
16+
Identifier = com.apple.pbx.linkers.libtool;
17+
BasedOn = generic-unix:com.apple.pbx.linkers.libtool;
18+
Type = Linker;
19+
Options = (
20+
{
21+
Name = "LIBTOOL_USE_RESPONSE_FILE";
22+
Type = Boolean;
23+
DefaultValue = NO;
24+
},
25+
);
26+
},
27+
)

Sources/SWBTestSupport/RunDestinationTestSupport.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ extension _RunDestinationInfo {
9898
windows
9999
case .linux:
100100
linux
101+
case .freebsd:
102+
freebsd
103+
case .openbsd:
104+
openbsd
101105
case .android:
102106
android
103107
case .unknown:
@@ -259,6 +263,22 @@ extension _RunDestinationInfo {
259263
return .init(platform: "linux", sdk: "linux", sdkVariant: "linux", targetArchitecture: arch, supportedArchitectures: ["x86_64", "aarch64"], disableOnlyActiveArch: false)
260264
}
261265

266+
/// A run destination targeting FreeBSD generic device, using the public SDK.
267+
package static var freebsd: Self {
268+
guard let arch = Architecture.hostStringValue else {
269+
preconditionFailure("Unknown architecture \(Architecture.host.stringValue ?? "<nil>")")
270+
}
271+
return .init(platform: "freebsd", sdk: "freebsd", sdkVariant: "freebsd", targetArchitecture: arch, supportedArchitectures: ["x86_64", "aarch64"], disableOnlyActiveArch: false)
272+
}
273+
274+
/// A run destination targeting OpenBSD generic device, using the public SDK.
275+
package static var openbsd: Self {
276+
guard let arch = Architecture.hostStringValue else {
277+
preconditionFailure("Unknown architecture \(Architecture.host.stringValue ?? "<nil>")")
278+
}
279+
return .init(platform: "openbsd", sdk: "openbsd", sdkVariant: "openbsd", targetArchitecture: arch, supportedArchitectures: ["x86_64", "aarch64"], disableOnlyActiveArch: false)
280+
}
281+
262282
/// A run destination targeting Android generic device, using the public SDK.
263283
package static var android: Self {
264284
return .init(platform: "android", sdk: "android", sdkVariant: "android", targetArchitecture: "undefined_arch", supportedArchitectures: ["armv7", "aarch64", "riscv64", "i686", "x86_64"], disableOnlyActiveArch: true)

Sources/SWBTestSupport/SkippedTestSupport.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ extension KnownSDK {
4949
return windows
5050
case .success(.linux):
5151
return linux
52+
case .success(.freebsd):
53+
return freebsd
54+
case .success(.openbsd):
55+
return openbsd
5256
case .success(.android):
5357
return android
5458
case .success(.unknown), .failure:
@@ -69,6 +73,8 @@ extension KnownSDK {
6973
extension KnownSDK {
7074
package static let windows: Self = "windows"
7175
package static let linux: Self = "linux"
76+
package static let freebsd: Self = "freebsd"
77+
package static let openbsd: Self = "openbsd"
7278
package static let android: Self = "android"
7379
package static let qnx: Self = "qnx"
7480
package static let wasi: Self = "wasi"
@@ -196,7 +202,7 @@ extension Trait where Self == Testing.ConditionTrait {
196202
}
197203
}
198204

199-
package static func requireSystemPackages(apt: String..., yum: String..., sourceLocation: SourceLocation = #_sourceLocation) -> Self {
205+
package static func requireSystemPackages(apt: String..., yum: String..., freebsd: String..., sourceLocation: SourceLocation = #_sourceLocation) -> Self {
200206
enabled("required system packages are not installed") {
201207
func checkInstalled(hostOS: OperatingSystem, packageManagerPath: Path, args: [String], packages: [String], regex: Regex<(Substring, name: Substring)>) async throws -> Bool {
202208
if try ProcessInfo.processInfo.hostOperatingSystem() == hostOS && localFS.exists(packageManagerPath) {
@@ -222,7 +228,9 @@ extension Trait where Self == Testing.ConditionTrait {
222228
// spelled `--installed` in newer versions of yum, but Amazon Linux 2 is on older versions
223229
let yum = try await checkInstalled(hostOS: .linux, packageManagerPath: Path("/usr/bin/yum"), args: ["list", "installed", "yum"], packages: yum, regex: #/(?<name>.+)\./#)
224230

225-
return apt && yum
231+
let freebsd = try await checkInstalled(hostOS: .freebsd, packageManagerPath: Path("/usr/sbin/pkg"), args: ["info"], packages: freebsd, regex: #/^Name(?:[ ]+): (?<name>.+)$/#)
232+
233+
return apt && yum && freebsd
226234
}
227235
}
228236

Sources/SWBUtil/Architecture.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,18 @@ public struct Architecture: Sendable {
9898
if uname(&buf) == 0 {
9999
return withUnsafeBytes(of: &buf.machine) { buf in
100100
let data = Data(buf)
101-
return String(decoding: data[0...(data.lastIndex(where: { $0 != 0 }) ?? 0)], as: UTF8.self)
101+
let value = String(decoding: data[0...(data.lastIndex(where: { $0 != 0 }) ?? 0)], as: UTF8.self)
102+
#if os(FreeBSD)
103+
switch value {
104+
case "amd64":
105+
return "x86_64"
106+
case "arm64":
107+
return "aarch64"
108+
default:
109+
break
110+
}
111+
#endif
112+
return value
102113
}
103114
}
104115
return nil

Sources/SWBUtil/FSProxy.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ class LocalFS: FSProxy, @unchecked Sendable {
718718
#if os(Windows)
719719
// Implement ADS on Windows? See also https://github.com/swiftlang/swift-foundation/issues/1166
720720
return []
721+
#elseif os(FreeBSD)
722+
// FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
723+
return []
721724
#elseif os(OpenBSD)
722725
// OpenBSD no longer supports extended attributes
723726
return []
@@ -758,6 +761,8 @@ class LocalFS: FSProxy, @unchecked Sendable {
758761
func setExtendedAttribute(_ path: Path, key: String, value: ByteString) throws {
759762
#if os(Windows)
760763
// Implement ADS on Windows? See also https://github.com/swiftlang/swift-foundation/issues/1166
764+
#elseif os(FreeBSD)
765+
// FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
761766
#elseif os(OpenBSD)
762767
// OpenBSD no longer supports extended attributes
763768
#else
@@ -778,6 +783,9 @@ class LocalFS: FSProxy, @unchecked Sendable {
778783
#if os(Windows)
779784
// Implement ADS on Windows? See also https://github.com/swiftlang/swift-foundation/issues/1166
780785
return nil
786+
#elseif os(FreeBSD)
787+
// FreeBSD blocked on https://github.com/swiftlang/swift/pull/77836
788+
return nil
781789
#elseif os(OpenBSD)
782790
// OpenBSD no longer supports extended attributes
783791
return nil

Sources/SWBUtil/Lock.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public final class Lock: @unchecked Sendable {
2828
#if os(Windows)
2929
@usableFromInline
3030
let mutex: UnsafeMutablePointer<SRWLOCK> = UnsafeMutablePointer.allocate(capacity: 1)
31-
#elseif os(OpenBSD)
31+
#elseif os(FreeBSD) || os(OpenBSD)
3232
@usableFromInline
3333
let mutex: UnsafeMutablePointer<pthread_mutex_t?> = UnsafeMutablePointer.allocate(capacity: 1)
3434
#else

Sources/SWBUtil/Process.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ extension Process {
7070
case .linux:
7171
// Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support
7272
FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false
73+
case .openbsd:
74+
true
7375
default:
7476
false
7577
}

Sources/SWBUtil/ProcessInfo.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ extension ProcessInfo {
9999
return .windows
100100
#elseif os(Linux)
101101
return .linux
102+
#elseif os(FreeBSD)
103+
return .freebsd
104+
#elseif os(OpenBSD)
105+
return .openbsd
102106
#else
103107
if try FileManager.default.isReadableFile(atPath: systemVersionPlistURL.filePath.str) {
104108
switch try systemVersion().productName {
@@ -129,6 +133,8 @@ public enum OperatingSystem: Hashable, Sendable {
129133
case visionOS(simulator: Bool)
130134
case windows
131135
case linux
136+
case freebsd
137+
case openbsd
132138
case android
133139
case unknown
134140

@@ -157,7 +163,7 @@ public enum OperatingSystem: Hashable, Sendable {
157163
return .macho
158164
case .windows:
159165
return .pe
160-
case .linux, .android, .unknown:
166+
case .linux, .freebsd, .openbsd, .android, .unknown:
161167
return .elf
162168
}
163169
}

Tests/SWBCoreTests/CommandLineToolSpecDiscoveredInfoTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ import SWBMacro
242242
}
243243
}
244244

245-
@Test(.skipHostOS(.windows), .requireSystemPackages(apt: "libtool", yum: "libtool"))
245+
@Test(.skipHostOS(.windows), .requireSystemPackages(apt: "libtool", yum: "libtool", freebsd: "libtool"))
246246
func discoveredLibtoolSpecInfo() async throws {
247247
try await withSpec(LibtoolLinkerSpec.self, .deferred) { (info: DiscoveredLibtoolLinkerToolSpecInfo) in
248248
#expect(info.toolPath.basename == "libtool")

Tests/SWBCoreTests/FileTextEncodingTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ import SWBTestSupport
2626
#expect(FileTextEncoding("utf8") != FileTextEncoding.utf8)
2727
}
2828

29-
@Test(.skipHostOS(.windows, "feature not available on Windows due to missing CF APIs"),
30-
.skipHostOS(.linux, "feature not available on Linux due to missing CF APIs"))
29+
@Test(.requireHostOS(.macOS)) // requires CoreFoundation which is macOS-only
3130
func encoding() throws {
3231
#expect(FileTextEncoding.utf8.stringEncoding == String.Encoding.utf8)
3332
#expect(FileTextEncoding.utf16.stringEncoding == String.Encoding.utf16)

Tests/SWBCoreTests/SettingsTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ import SWBMacro
17731773
#expect(!core.platformRegistry.platforms.isEmpty)
17741774
for developmentTeam in ["ABCDWXYZ", ""] {
17751775
for platform in core.platformRegistry.platforms {
1776-
if ["android", "linux", "qnx", "windows"].contains(platform.name) {
1776+
if ["android", "freebsd", "linux", "qnx", "windows"].contains(platform.name) {
17771777
continue
17781778
}
17791779
for sdk in platform.sdks {

Tests/SWBUtilTests/ElapsedTimerTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
import Foundation
1414
import SWBUtil
1515
import Testing
16+
import SWBTestSupport
1617

1718
@Suite fileprivate struct ElapsedTimerTests {
18-
@Test
19+
@Test(.skipHostOS(.freebsd, "Currently hangs on FreeBSD"))
1920
func time() async throws {
2021
do {
2122
let delta = try await ElapsedTimer.measure {

Tests/SWBUtilTests/FSProxyTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ import SWBTestSupport
488488
case .android, .linux:
489489
// This will _usually_ be correct on Linux-derived OSes (see above), but not always.
490490
#expect(current_gid == ownership.group)
491-
case .macOS, .iOS, .tvOS, .watchOS, .visionOS:
491+
case .macOS, .iOS, .tvOS, .watchOS, .visionOS, .freebsd, .openbsd:
492492
#expect(parentDirOwnership.group == ownership.group)
493493
case .windows:
494494
// POSIX permissions don't exist, so everything is hardcoded to zero.
@@ -566,7 +566,7 @@ import SWBTestSupport
566566
}
567567
}
568568

569-
@Test(.skipHostOS(.windows))
569+
@Test(.skipHostOS(.windows), .skipHostOS(.freebsd, "Blocked on https://github.com/swiftlang/swift/pull/77836"))
570570
func extendedAttributesSupport() throws {
571571
try withTemporaryDirectory { (tmpDir: Path) in
572572
// Many filesystems on other platforms (e.g. various non-ext4 temporary filesystems on Linux) don't support xattrs and will return ENOTSUP.

Tests/SWBUtilTests/FileHandleTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import SystemPackage
2222
#endif
2323

2424
@Suite fileprivate struct FileHandleTests {
25-
@Test
25+
@Test(.skipHostOS(.freebsd, "Currently crashes on FreeBSD"))
2626
func asyncReadFileDescriptor() async throws {
2727
let fs = localFS
2828
try await withTemporaryDirectory(fs: fs) { testDataPath in

Tests/SWBUtilTests/HeavyCacheTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Foundation
1414
import Testing
1515
@_spi(Testing) import SWBUtil
1616
import Synchronization
17+
import SWBTestSupport
1718

1819
@Suite
1920
fileprivate struct HeavyCacheTests {
@@ -105,7 +106,7 @@ fileprivate struct HeavyCacheTests {
105106
}
106107

107108
/// Check initial TTL.
108-
@Test
109+
@Test(.skipHostOS(.freebsd, "Currently hangs on FreeBSD"))
109110
func TTL_initial() async throws {
110111
let fudgeFactor = 10.0
111112
let ttl = Duration.seconds(0.01)
@@ -124,7 +125,7 @@ fileprivate struct HeavyCacheTests {
124125
}
125126

126127
/// Check TTL set after the fact.
127-
@Test
128+
@Test(.skipHostOS(.freebsd, "Currently hangs on FreeBSD"))
128129
func TTL_after() async throws {
129130
let fudgeFactor = 10.0
130131
let ttl = Duration.seconds(0.01)

Tests/SWBUtilTests/MiscTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import SWBUtil
2525
#expect(SWBUtil.userCacheDir().str.hasPrefix("/var/folders"))
2626
case .android:
2727
#expect(SWBUtil.userCacheDir().str.hasPrefix("/data/local/tmp"))
28-
case .linux, .unknown:
28+
case .linux, .freebsd, .openbsd, .unknown:
2929
#expect(SWBUtil.userCacheDir().str.hasPrefix("/tmp"))
3030
}
3131
}

Tests/SWBUtilTests/RateLimiterTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import Foundation
1414
import Testing
1515
import SWBUtil
16+
import SWBTestSupport
1617

17-
@Suite fileprivate struct RateLimiterTests {
18+
@Suite(.skipHostOS(.freebsd, "Currently hangs on FreeBSD"))
19+
fileprivate struct RateLimiterTests {
1820
@Test
1921
func rateLimiterSeconds() async throws {
2022
let timer = ElapsedTimer()

0 commit comments

Comments
 (0)