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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ brew bundle install

## Supported platforms and minimum versions

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

| Platform | Supported Version as Host | Supported Version as Target |
| -: | :- | :- |
| macOS (arm64) | ✅ macOS 13.0+ ||
| macOS (x86_64) | ✅ macOS 13.0+[^1] ||
| FreeBSD | ✅ 14.3+ | ✅ 14.3+ |
| Ubuntu | ✅ 20.04+ | ✅ 20.04+ |
| Debian | ✅ 11, 12[^2] | ✅ 11, 12[^2] |
| RHEL | ✅ Fedora 39, UBI 9 | ✅ Fedora 39, UBI 9[^3] |
Expand Down
80 changes: 79 additions & 1 deletion Sources/GeneratorCLI/GeneratorCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct GeneratorCLI: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "swift-sdk-generator",
subcommands: [MakeLinuxSDK.self, MakeWasmSDK.self],
subcommands: [MakeLinuxSDK.self, MakeFreeBSDSDK.self, MakeWasmSDK.self],
defaultSubcommand: MakeLinuxSDK.self
)

Expand Down Expand Up @@ -304,6 +304,84 @@ extension GeneratorCLI {
}
}

struct MakeFreeBSDSDK: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "make-freebsd-sdk",
abstract: "Generate a Swift SDK bundle for FreeBSD.",
discussion: """
The default `--target` triple is FreeBSD for x86_64
"""
)

@OptionGroup
var generatorOptions: GeneratorOptions

@Option(
help: """
Swift toolchain for FreeBSD from which to copy the Swift libraries. \
This must match the architecture of the target triple.
"""
)
var fromSwiftToolchain: String? = nil

@Option(
help: """
Version of FreeBSD to use as a target platform. Example: 14.3
"""
)
var freeBSDVersion: String

func deriveTargetTriple() throws -> Triple {
if let target = generatorOptions.target, target.os == .freeBSD {
return target
}
if let arch = generatorOptions.targetArch {
let target = Triple(arch: arch, vendor: nil, os: .freeBSD)
appLogger.warning(
"""
Using `--target-arch \(arch)` defaults to `\(target.triple)`. \
Use `--target` if you want to pass the full target triple.
"""
)
return target
} else {
// If no target is given, use the architecture of the host.
let hostTriple = try self.generatorOptions.deriveHostTriple()
let hostArch = hostTriple.arch!
return Triple(arch: hostArch, vendor: nil, os: .freeBSD)
}
}

func run() async throws {
let freebsdVersion = try FreeBSD(self.freeBSDVersion)
guard freebsdVersion.isSupportedVersion() else {
throw StringError("Only FreeBSD versions 14.3 or higher are supported.")
}

let hostTriple = try self.generatorOptions.deriveHostTriple()
let targetTriple = try self.deriveTargetTriple()

let sourceSwiftToolchain: FilePath?
if let fromSwiftToolchain {
sourceSwiftToolchain = .init(fromSwiftToolchain)
} else {
sourceSwiftToolchain = nil
}

let recipe = FreeBSDRecipe(
freeBSDVersion: freebsdVersion,
mainTargetTriple: targetTriple,
sourceSwiftToolchain: sourceSwiftToolchain,
logger: loggerWithLevel(from: self.generatorOptions)
)
try await GeneratorCLI.run(
recipe: recipe,
targetTriple: targetTriple,
options: self.generatorOptions
)
}
}

struct MakeWasmSDK: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "make-wasm-sdk",
Expand Down
20 changes: 18 additions & 2 deletions Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ public actor SwiftSDKGenerator {
self.fileManager.fileExists(atPath: path.string)
}

func doesDirectoryExist(at path: FilePath) -> Bool {
var isDirectory: ObjCBool = false
let itemExists = self.fileManager.fileExists(atPath: path.string, isDirectory: &isDirectory)
return itemExists && isDirectory.boolValue
}

func removeFile(at path: FilePath) throws {
try self.fileManager.removeItem(atPath: path.string)
}
Expand Down Expand Up @@ -245,16 +251,24 @@ public actor SwiftSDKGenerator {
func untar(
file: FilePath,
into directoryPath: FilePath,
stripComponents: Int? = nil
stripComponents: Int? = nil,
paths: [String]? = nil
) async throws {
let stripComponentsOption: String
if let stripComponents {
stripComponentsOption = "--strip-components=\(stripComponents)"
} else {
stripComponentsOption = ""
}

var command = #"tar -C "\#(directoryPath)" \#(stripComponentsOption) -xf "\#(file)""#
if let paths {
for path in paths {
command += #" "\#(path)""#
}
}
try await Shell.run(
#"tar -C "\#(directoryPath)" \#(stripComponentsOption) -xf "\#(file)""#,
command,
shouldLogCommands: self.isVerbose
)
}
Expand Down Expand Up @@ -298,6 +312,8 @@ public actor SwiftSDKGenerator {
} else {
try await self.gunzip(file: file, into: directoryPath)
}
case "txz":
try await self.untar(file: file, into: directoryPath)
case "deb":
try await self.unpack(debFile: file, into: directoryPath)
case "pkg":
Expand Down
52 changes: 52 additions & 0 deletions Sources/SwiftSDKGenerator/PlatformModels/FreeBSD.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//===----------------------------------------------------------------------===//
//
// 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 https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation

public struct FreeBSD: Sendable {
public let version: String

public let majorVersion: Int
public let minorVersion: Int

public func isSupportedVersion() -> Bool {
if majorVersion >= 15 {
return true
} else if majorVersion == 14, minorVersion >= 3 {
return true
} else {
return false
}
}

public init(_ versionString: String) throws {
guard !versionString.isEmpty else {
throw GeneratorError.invalidVersionString(
string: versionString,
reason: "The version string cannot be empty."
)
}

let versionComponents = versionString.split(separator: ".")
guard let majorVersion = Int(versionComponents[0]) else {
throw GeneratorError.unknownFreeBSDVersion(version: versionString)
}

self.version = versionString
self.majorVersion = majorVersion
if versionComponents.count > 1, let minorVersion = Int(versionComponents[1]) {
self.minorVersion = minorVersion
} else {
minorVersion = 0
}
}
}
Loading