Skip to content

Commit c18bf54

Browse files
Add support for targeting FreeBSD
The Swift SDK Generator project should be able to generate SDKs that target FreeBSD systems. FreeBSD is much simpler than Linux. We only require the user to provide their desired architecture and FreeBSD version. This script will then download the FreeBSD base system content and create a sysroot suitable for use with Swift Package Manager. Because there are not currently any Swift toolchains for FreeBSD available on swift.org, we require the user to provide a path to a pre-built toolchain if they want the SDK to support building Swift code. In the future, we can download the Swift toolchain like we do for Linux in lieu of requiring the user to provide it.
1 parent 349caaf commit c18bf54

File tree

6 files changed

+350
-4
lines changed

6 files changed

+350
-4
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ brew bundle install
4242

4343
## Supported platforms and minimum versions
4444

45-
macOS as a host platform and Linux as both host and target platforms are supported by the generator.
45+
FreeBSD and Linux are supported as both host and target platforms. macOS is only supported as a host platform.
4646
The generator also allows cross-compiling between any Linux distributions officially supported by the Swift project.
4747

4848
| Platform | Supported Version as Host | Supported Version as Target |
4949
| -: | :- | :- |
5050
| macOS (arm64) | ✅ macOS 13.0+ ||
5151
| macOS (x86_64) | ✅ macOS 13.0+[^1] ||
52+
| FreeBSD | ✅ 14.3+ | ✅ 14.3+ |
5253
| Ubuntu | ✅ 20.04+ | ✅ 20.04+ |
5354
| Debian | ✅ 11, 12[^2] | ✅ 11, 12[^2] |
5455
| RHEL | ✅ Fedora 39, UBI 9 | ✅ Fedora 39, UBI 9[^3] |

Sources/GeneratorCLI/GeneratorCLI.swift

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct GeneratorCLI: AsyncParsableCommand {
2323

2424
static let configuration = CommandConfiguration(
2525
commandName: "swift-sdk-generator",
26-
subcommands: [MakeLinuxSDK.self, MakeWasmSDK.self],
26+
subcommands: [MakeLinuxSDK.self, MakeFreeBSDSDK.self, MakeWasmSDK.self],
2727
defaultSubcommand: MakeLinuxSDK.self
2828
)
2929

@@ -304,6 +304,84 @@ extension GeneratorCLI {
304304
}
305305
}
306306

307+
struct MakeFreeBSDSDK: AsyncParsableCommand {
308+
static let configuration = CommandConfiguration(
309+
commandName: "make-freebsd-sdk",
310+
abstract: "Generate a Swift SDK bundle for FreeBSD.",
311+
discussion: """
312+
The default `--target` triple is FreeBSD for x86_64
313+
"""
314+
)
315+
316+
@OptionGroup
317+
var generatorOptions: GeneratorOptions
318+
319+
@Option(
320+
help: """
321+
Swift toolchain for FreeBSD from which to copy the Swift libraries. \
322+
This must match the architecture of the target triple.
323+
"""
324+
)
325+
var fromSwiftToolchain: String? = nil
326+
327+
@Option(
328+
help: """
329+
Version of FreeBSD to use as a target platform. Example: 14.3
330+
"""
331+
)
332+
var freeBSDVersion: String
333+
334+
func deriveTargetTriple() throws -> Triple {
335+
if let target = generatorOptions.target, target.os == .freeBSD {
336+
return target
337+
}
338+
if let arch = generatorOptions.targetArch {
339+
let target = Triple(arch: arch, vendor: nil, os: .freeBSD)
340+
appLogger.warning(
341+
"""
342+
Using `--target-arch \(arch)` defaults to `\(target.triple)`. \
343+
Use `--target` if you want to pass the full target triple.
344+
"""
345+
)
346+
return target
347+
} else {
348+
// If no target is given, use the architecture of the host.
349+
let hostTriple = try self.generatorOptions.deriveHostTriple()
350+
let hostArch = hostTriple.arch!
351+
return Triple(arch: hostArch, vendor: nil, os: .freeBSD)
352+
}
353+
}
354+
355+
func run() async throws {
356+
let freebsdVersion = try FreeBSD(self.freeBSDVersion)
357+
guard freebsdVersion.isSupportedVersion() else {
358+
throw StringError("Only FreeBSD versions 14.3 or higher are supported.")
359+
}
360+
361+
let hostTriple = try self.generatorOptions.deriveHostTriple()
362+
let targetTriple = try self.deriveTargetTriple()
363+
364+
let sourceSwiftToolchain: FilePath?
365+
if let fromSwiftToolchain {
366+
sourceSwiftToolchain = .init(fromSwiftToolchain)
367+
} else {
368+
sourceSwiftToolchain = nil
369+
}
370+
371+
let recipe = FreeBSDRecipe(
372+
freeBSDVersion: freebsdVersion,
373+
mainTargetTriple: targetTriple,
374+
sourceSwiftToolchain: sourceSwiftToolchain,
375+
logger: loggerWithLevel(from: self.generatorOptions)
376+
)
377+
try await GeneratorCLI.run(
378+
recipe: recipe,
379+
targetTriple: targetTriple,
380+
options: self.generatorOptions
381+
)
382+
}
383+
}
384+
307385
struct MakeWasmSDK: AsyncParsableCommand {
308386
static let configuration = CommandConfiguration(
309387
commandName: "make-wasm-sdk",

Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ public actor SwiftSDKGenerator {
149149
self.fileManager.fileExists(atPath: path.string)
150150
}
151151

152+
func doesDirectoryExist(at path: FilePath) -> Bool {
153+
var isDirectory: ObjCBool = false
154+
let itemExists = self.fileManager.fileExists(atPath: path.string, isDirectory: &isDirectory)
155+
return itemExists && isDirectory.boolValue
156+
}
157+
152158
func removeFile(at path: FilePath) throws {
153159
try self.fileManager.removeItem(atPath: path.string)
154160
}
@@ -245,16 +251,24 @@ public actor SwiftSDKGenerator {
245251
func untar(
246252
file: FilePath,
247253
into directoryPath: FilePath,
248-
stripComponents: Int? = nil
254+
stripComponents: Int? = nil,
255+
paths: [String]? = nil
249256
) async throws {
250257
let stripComponentsOption: String
251258
if let stripComponents {
252259
stripComponentsOption = "--strip-components=\(stripComponents)"
253260
} else {
254261
stripComponentsOption = ""
255262
}
263+
264+
var command = #"tar -C "\#(directoryPath)" \#(stripComponentsOption) -xf "\#(file)""#
265+
if let paths {
266+
for path in paths {
267+
command += #" "\#(path)""#
268+
}
269+
}
256270
try await Shell.run(
257-
#"tar -C "\#(directoryPath)" \#(stripComponentsOption) -xf "\#(file)""#,
271+
command,
258272
shouldLogCommands: self.isVerbose
259273
)
260274
}
@@ -298,6 +312,8 @@ public actor SwiftSDKGenerator {
298312
} else {
299313
try await self.gunzip(file: file, into: directoryPath)
300314
}
315+
case "txz":
316+
try await self.untar(file: file, into: directoryPath)
301317
case "deb":
302318
try await self.unpack(debFile: file, into: directoryPath)
303319
case "pkg":
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
public struct FreeBSD: Sendable {
16+
public let version: String
17+
18+
public let majorVersion: Int
19+
public let minorVersion: Int
20+
21+
public func isSupportedVersion() -> Bool {
22+
if majorVersion >= 15 {
23+
return true
24+
} else if majorVersion == 14, minorVersion >= 3 {
25+
return true
26+
} else {
27+
return false
28+
}
29+
}
30+
31+
public init(_ versionString: String) throws {
32+
guard !versionString.isEmpty else {
33+
throw GeneratorError.invalidVersionString(
34+
string: versionString,
35+
reason: "The version string cannot be empty."
36+
)
37+
}
38+
39+
let versionComponents = versionString.split(separator: ".")
40+
guard let majorVersion = Int(versionComponents[0]) else {
41+
throw GeneratorError.unknownFreeBSDVersion(version: versionString)
42+
}
43+
44+
self.version = versionString
45+
self.majorVersion = majorVersion
46+
if versionComponents.count > 1, let minorVersion = Int(versionComponents[1]) {
47+
self.minorVersion = minorVersion
48+
} else {
49+
minorVersion = 0
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)