Add FreeBSD platform support#565
Conversation
Introduces a `FreeBSDPlatform` target (`struct FreeBSD: Platform`) so swiftly compiles and runs on FreeBSD, alongside the existing Linux and macOS platforms. - New `FreeBSDPlatform` target, modeled on `LinuxPlatform` (FreeBSD's toolchain layout is tarball-based like Linux). `detectPlatform` returns a single `.freebsd` `PlatformDefinition`; install/extract/dirs reuse the portable POSIX/Foundation paths. - Wired into `Swiftly`/`TestSwiftly` (`#elseif os(FreeBSD)`), and the POSIX `os(macOS) || os(Linux)` guards in `SwiftlyCore` (ProcessInfo, Platform, Platform+Process, Terminal) now include FreeBSD. - Added `PlatformDefinition.freebsd`. - `FreeBSDPlatform` is an unconditional package dependency that self-guards with `#if os(FreeBSD)`, because `PackageDescription` currently has no `.freebsd` platform (so `.when(platforms: [.freebsd])` is unavailable). - The release-packaging tool's `run()` throws on FreeBSD for now (the Linux release path is musl-SDK specific). Verified: builds and runs (`swiftly --help` / `--version`) natively on FreeBSD 15.1/aarch64.
Two follow-ups to the FreeBSDPlatform support: - FreeBSD.swift: verifySwiftlySystemPrerequisites checked the Linux CA bundle paths (/etc/ssl/certs/ca-certificates.crt, /etc/pki/tls/certs/ca-bundle.crt), which don't exist on FreeBSD, so it always reported "ca-certificates not installed". Check the FreeBSD trust store instead: /usr/local/etc/ssl/cert.pem (security/ca_root_nss), /etc/ssl/cert.pem (base), and /usr/local/share/certs/ca-root-nss.crt. - SwiftlyTests.swift: the MockToolchainDownloader helpers (makeMockedSwiftly/makeMockedToolchain, the signatures cache, and the on-disk toolchain verifier) were guarded #if os(Linux)/#elseif os(macOS) only, so the test target failed to compile on FreeBSD. Add os(FreeBSD) to the Linux branches; the Linux mock builds a .tar.gz + GPG signature, which is exactly what FreeBSDPlatform consumes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
You can use |
…rm dep Per review feedback (@kkebo): instead of depending on FreeBSDPlatform unconditionally (and relying on the target's internal `#if os(FreeBSD)` self-guard), gate the dependency with `.when(platforms: [.custom("freebsd")])`, matching the LinuxPlatform/MacOSPlatform entries. This keeps FreeBSDPlatform out of the dependency graph entirely on non-FreeBSD builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @kkebo — that's much cleaner. Applied in 4f25b50: the three |
SelfUpdate.execute hit `fatalError("Unsupported OS")` on FreeBSD, which
crashed the whole test process (SIGTRAP) and aborted the suite. Treat
FreeBSD like Linux (tarball-based): build a freebsd/swiftly-*.tar.gz
download URL for an explicitly-requested version, and match the Linux
release artifact in the auto-update path (the swift.org website API has
no FreeBSD platform yet; this can switch to a dedicated FreeBSD artifact
once one is published).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Without these additions, running the test suite on FreeBSD would fail:
1. `HTTPClient.platformDef` maps swift.org API platform name strings
(e.g. "Ubuntu 22.04") to `PlatformDefinition`. Adding "FreeBSD"
lets `Platform.matches(_:)` return true when filtering mock releases.
2. `HTTPClient.getSnapshotToolchains` had no `freebsd` case — it would
throw "No snapshot toolchains available". FreeBSD goes through the
same `.init(platform.name)` path as ubuntu2404/debian12/fedora.
3. `MockToolchainDownloader.getReleaseToolchains()` in the test suite
maps the detected platform to a swift.org-style platform name string.
Without a `PlatformDefinition.freebsd` case it would throw
`SwiftlyTestError("Could not detect the current platform in test")`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WDxhuavrKQXkFay9JMFAhK
|
Awesome to see more FreeBSD progress! |
Adds a `freebsd-aarch64-tests` job that runs on ubuntu-latest, installs cloudflared, and SSHes into the FreeBSD aarch64 board via a named Cloudflare Tunnel to build and run the swiftly test suite natively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Heredoc + GitHub Actions expression expansion was unreliable. Switch to env: vars + printf for robust config file creation. Also fix ~ expansion in rsync -e arg by using $HOME. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@jakepetroules Thanks! Here is a full status update. Test results on FreeBSD 15.1 aarch64 (Swift 6.3.2-RELEASE, NXP DPAA2 board)HTTPClientTests — all pass with NIOSSL CA fixThe prior failure ( The NIOSSL fix ( -#if os(Linux)
+#if os(Linux) || os(FreeBSD)Plus FreeBSD CA paths: UpdateTests — all passAll other suites (PlatformTests, InstallTests, UninstallTests, LinkTests, ListTests, UseTests, etc.) confirmed passing in earlier runs on the same board. Dependency patches to build and test on FreeBSD
swift-nio /
swift-nio / NIOCore + NIOPosix:
swift-nio-ssl:
Related: Synchronization.Mutex deadlock
Happy to open separate upstream PRs to |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cloudflared access ssh requires a Cloudflare Access JWT service token for automated sessions; tcp tunneling bypasses the Access layer and only requires the SSH key we already have. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes redundantReturn, indent, trailingCommas, braces, hoistAwait/Try, redundantOptionalBinding, unusedArguments flagged by swiftformat lint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous commit used 0.61.1 which formats switch expressions differently. Re-run with the exact version pinned in Package.swift. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace debug ssh uname -a with actual swift build + swift test - Fix swiftformat: remove trailing comma from detectPlatform params, fix indent on multi-line SwiftlyError message string Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use heredoc + /bin/sh to avoid tcsh login shell on board - Collapse SwiftlyError message: onto one line (swiftformat indent rule) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CNIOBoringSSL/ssl_lib.cc calls gettimeofday() but doesn't include <sys/time.h> on FreeBSD. Pass -Xcc -include to both build and test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-Xcc -include sys/time.h breaks C++ TUs (sys/_types.h conflicts with BoringSSL defines). Instead patch the checkout directly before building: add #include "sys/time.h" to ssl_lib.cc so gettimeofday is declared. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FreeBSD's pthread_mutex_t and pthread_mutexattr_t are OpaquePointer (like OpenBSD), not structs. Treat FreeBSD the same as OpenBSD in Locks.swift so the optional-pointer API and bitPattern init are used. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ReadWriteLock class had no OpenBSD branch, so FreeBSD fell through to the non-optional UnsafeMutablePointer<pthread_rwlock_t> path. FreeBSD's pthread_rwlock_t is OpaquePointer and the API expects the optional form. Switch to idempotent Python patch covering both Lock and ReadWriteLock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Python code at column-0 inside a YAML literal block terminates the block early (YAML ends scalars when indentation drops below the block level). Switch back to sed-only approach: two guarded idempotent sed commands cover both the Lock class (os(OpenBSD) → also FreeBSD) and ReadWriteLock class (pthread_rwlock_t → optional form for FreeBSD's opaque pointer). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multiple packages (swift-log, swift-distributed-tracing, etc.) vendor a copy of NIO's Locks.swift with the same pthread_rwlock_t type mismatch. Apply fixes to all Locks.swift files under .build/checkouts at once to avoid one-by-one failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reeBSD On FreeBSD, canImport(Glibc) is true but Glibc.stdout/stderr are non-optional (UnsafeMutablePointer<FILE>, not Optional). The Glibc.stdout! and Glibc.stderr! force-unwraps that work on Linux fail on FreeBSD. Strip the ! for those specific qualified references. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
swift-http-types has NIOLock.swift with same pthread opaque pointer issues. Also fix LockPrimitive typealias: pthread_mutex_t -> pthread_mutex_t? so UnsafeMutablePointer<LockPrimitive> matches what pthread_mutex_init expects on FreeBSD. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs in the previous sed-based patch: 1. BSD sed does not reliably emit a newline for \n in the replacement string 2. #include "sys/time.h" (quoted/relative) finds BoringSSL's own shadowed sys/time.h rather than the system header; <sys/time.h> (angle brackets) bypasses BoringSSL's -I paths Switch to awk for reliable line insertion before ssl_local.h, and use <sys/time.h> to guarantee the system declaration of gettimeofday. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ranch Files without an os(OpenBSD) branch fall through to the generic pthread path with 'var attr = pthread_mutexattr_t()'. On FreeBSD, pthread_mutexattr_t is OpaquePointer and has no default init — needs bitPattern: 0 (produces nil, which pthread_mutexattr_init then initializes to a valid attr). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The file already has <sys/time.h> inside #else !OPENSSL_WINDOWS, but gettimeofday remains undeclared in the BSSL namespace on FreeBSD. Guard on a unique marker and insert the include unconditionally at line 1, before any #if blocks and before BSSL_NAMESPACE_BEGIN. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FreeBSD's sys/time.h guards gettimeofday behind #if __XSI_VISIBLE, which is 0 unless _XOPEN_SOURCE>=500. BoringSSL compilation never defines it, so gettimeofday was invisible even with the include present. Adding _XOPEN_SOURCE 600 before the include makes the declaration visible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MutexType/ConditionType are pthread_mutex_t/pthread_cond_t which map to OpaquePointer on FreeBSD (opaque, not a struct). Wrap them in an #if os(FreeBSD) || os(OpenBSD) block to use the optional form so UnsafeMutablePointer<MutexType> matches what pthread_mutex_init expects. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getentropy is in unistd.h behind #if __BSD_VISIBLE on FreeBSD, which is 0 when BoringSSL's compile flags restrict POSIX visibility. Forward- declaring extern "C" int getentropy(void*, size_t) at line 1 bypasses the feature macro issue entirely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pthread_t is OpaquePointer on FreeBSD (no default initializer). Use unsafeBitCast(0 as UInt) as a placeholder — _subprocess_pthread_create takes _Nonnull ptr so the variable must stay non-optional; pthread_create overwrites it before any read. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ibc) pthread_t on FreeBSD is an opaque C pointer; _subprocess_pthread_create takes pthread_t* which Swift imports as UnsafeMutablePointer<pthread_t?>. Excluding FreeBSD from the canImport(Glibc) guard makes it use the #else branch (pthread_t?) which matches macOS and the expected function type. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- verifySystemPrerequisitesForInstall: replace Linux package database with FreeBSD deps (libuuid, python311, sqlite3) from swift-installer-scripts - isSystemPackageInstalled: use pkg info -e instead of apt-get/dnf/yum - getShell: fall back to /bin/sh, not /bin/bash (bash not default on FreeBSD)
Foundation.Process.run() calls _CFRunLoopGet0() to register child-process monitoring with the current thread's run loop. On FreeBSD, __CFRunLoopCreate hits a TRAP assertion when invoked from a libdispatch DispatchWorker thread (any Swift async context), causing the test binary to die with signal 5. Replace all three async usages (version(), makeMockedSwiftlyRelease, makeMockedToolchain) with Subprocess.run, which uses posix_spawn internally and is concurrency-safe on all platforms. Also simplify the GPG sign step to always use --pinentry-mode loopback (GPG 2.1+, as shipped by FreeBSD). Verified: swift test --filter SwiftlyTests exits 0 on FreeBSD 15.1 aarch64. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add FreeBSD 14.4 aarch64 job (board .29, secret FREEBSD_CF_HOSTNAME_144) - Patch NIOSSL to load CA certs on FreeBSD (fix CERTIFICATE_VERIFY_FAILED in HTTPClientTests): add FreeBSD paths to LinuxCABundle.rootCAFileSearchPaths and add #elseif os(FreeBSD) branch to SSLContext.platformDefaultConfiguration - Stop orphaned swift-test/swift-build processes at CI job start - 90-minute timeout on both FreeBSD jobs - Use doas for privilege escalation on FreeBSD 14.4 board Both boards verified green: all 170 SwiftlyTests pass on FreeBSD 15.1 aarch64 and FreeBSD 14.4 aarch64. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Subprocess+BSD.swift uses DispatchSource makeProcessSource (kqueue EVFILT_PROC | NOTE_EXIT) to detect child process exit. On FreeBSD 15.1, when the child exits between the WNOHANG check and kqueue registration, the NOTE_EXIT event is missed and monitorProcessTermination waits forever. This caused swift test to hang for 90 minutes and timeout. Fix: patch Subprocess+BSD.swift in the CI checkout to replace the DispatchSource approach with a 10ms-interval polling loop using waitid(WNOHANG). Polling is race-free for process termination detection and has negligible overhead for the short-lived processes in the tests. Applied to both FreeBSD 15.1 and 14.4 jobs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@jakepetroules thanks for the review — addressed all three in
Separately, the underlying |
## Summary `platformDefaultConfiguration()` in `SSLContext.swift` has branches for Linux and Android but not FreeBSD. On FreeBSD the function is a no-op: no CA trust roots are loaded, so every HTTPS client connection fails immediately with `CERTIFICATE_VERIFY_FAILED`. `LinuxCABundle.swift` already compiles for FreeBSD (`#if os(Linux) || os(FreeBSD)`) and defines `rootCAFilePath`/`rootCADirectoryPath`, but the search path arrays contained only Linux distro paths — no FreeBSD path was ever matched, so `rootCAFilePath` and `rootCADirectoryPath` were always `nil`. ## Changes **`SSLContext.swift`** — one line: ```diff -#if os(Linux) +#if os(Linux) || os(FreeBSD) ``` **`LinuxCABundle.swift`** — add FreeBSD paths to both search arrays: ```swift // rootCAFileSearchPaths additions: "/usr/local/etc/ssl/cert.pem", // security/openssl port "/etc/ssl/cert.pem", // base system (FreeBSD 14+) "/usr/local/share/certs/ca-root-nss.crt", // security/ca_root_nss port // rootCADirectorySearchPaths addition: "/usr/local/share/certs", // security/ca_root_nss port ``` ## Testing Verified on FreeBSD 15.1 aarch64 (NXP DPAA2, Swift 6.3.2-RELEASE). With this fix, all HTTPS requests to `swift.org` and `download.swift.org` succeed. Without it, every connection fails at the TLS handshake regardless of which CA bundle is installed. Tested as part of the swiftly FreeBSD port (swiftlang/swiftly#565), where `HTTPClientTests` makes live HTTPS requests: ``` ✔ Test getSwiftOrgGPGKeys() passed after 0.700 seconds. ✔ Test getSwiftToolchain() passed after 109.816 seconds. ✔ Test getSwiftlyLinuxReleases(url:) with 6 test cases passed after 31.636 seconds. ✔ Suite HTTPClientTests passed after 183.785 seconds. ``` 🤖 Generated with [Claude Code](https://claude.ai/claude-code) --------- Co-authored-by: lushan.ai <kequeue@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cory Benfield <lukasa@apple.com>
…orm-support change
Summary
Introduces a `FreeBSDPlatform` target so swiftly compiles, runs, and passes its full test suite on FreeBSD, alongside the existing Linux and macOS platforms.
Approach
Testing
All 170 SwiftlyTests pass on two FreeBSD boards:
CI runs via Cloudflare Tunnel to self-hosted boards (see workflow). HTTPClientTests (21 tests) require real network access to swift.org and also pass.
Tests use `Subprocess.run` instead of `Foundation.Process` on FreeBSD — `Process.run()` triggers a `_CFRunLoopCreate` assertion when called from a libdispatch worker thread on FreeBSD, causing SIGTRAP. `Subprocess` uses `posix_spawn` internally and is concurrency-safe.