Skip to content

Add FreeBSD platform support #999

Add FreeBSD platform support

Add FreeBSD platform support #999

Workflow file for this run

name: Pull Request
permissions:
contents: read
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: [main, 'release/*']
workflow_dispatch:
jobs:
soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
license_header_check_enabled: false
license_header_check_project_name: "Swift.org"
api_breakage_check_enabled: false
docs_check_enabled: false
format_check_enabled: false
shell_check_enabled: false
unacceptable_language_check_enabled: true
freebsd-aarch64-tests:
name: Test / FreeBSD aarch64
runs-on: ubuntu-latest
if: ${{ github.repository == 'networkextension/swiftly' || github.repository == 'swiftlang/swiftly' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install cloudflared
run: |
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared
- name: Set up SSH key and known hosts
env:
FREEBSD_SSH_KEY: ${{ secrets.FREEBSD_SSH_KEY }}
FREEBSD_CF_HOSTNAME: ${{ secrets.FREEBSD_CF_HOSTNAME }}
run: |
mkdir -p "$HOME/.ssh"
printf '%s\n' "$FREEBSD_SSH_KEY" > "$HOME/.ssh/freebsd_ci_key"
chmod 600 "$HOME/.ssh/freebsd_ci_key"
printf '%s ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFqpy2Vs6oX1K5khxWW7INHC8a6meSaSvNDVXpCSv1Gj\n' \
"$FREEBSD_CF_HOSTNAME" >> "$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/known_hosts"
printf 'Host freebsd-ci\n HostName %s\n User swift\n IdentityFile %s/.ssh/freebsd_ci_key\n ProxyCommand cloudflared access tcp --hostname %%h\n' \
"$FREEBSD_CF_HOSTNAME" "$HOME" > "$HOME/.ssh/config"
chmod 600 "$HOME/.ssh/config"
- name: Sync source to board
run: |
ssh -F "$HOME/.ssh/config" freebsd-ci 'mkdir -p /home/swift/ci/swiftly'
rsync -avz --delete \
--exclude='.build' --exclude='.git' \
-e "ssh -F $HOME/.ssh/config" \
. freebsd-ci:/home/swift/ci/swiftly/
- name: Build and Test on FreeBSD aarch64
run: |
ssh -F "$HOME/.ssh/config" freebsd-ci /bin/sh << 'ENDSSH'
set -e
# Stop any swift processes left over from a previous cancelled/timed-out run.
# This prevents concurrent build/test from corrupting the shared .build directory.
pkill -u swift -x swift-test 2>/dev/null || true
pkill -u swift -x swift-build 2>/dev/null || true
pkill -u swift -f "swift-package resolve" 2>/dev/null || true
pkill -u swift -f "swiftlyPackageTests.xctest" 2>/dev/null || true
. "$HOME/swift632-env.sh"
cd "$HOME/ci/swiftly"
# Reset swift-nio checkout before resolve so SwiftPM can update it cleanly.
# Without this, files added by a previous networkextension branch switch
# (e.g. .github/workflows/freebsd-aarch64.yml) cause "local changes would
# be overwritten" when the branch updates between runs.
_NIODIR_PRE=$(find .build/checkouts -maxdepth 1 -name "swift-nio" -type d 2>/dev/null | head -1)
if [ -n "$_NIODIR_PRE" ]; then
chmod -R u+w "$_NIODIR_PRE" 2>/dev/null || true
git config --global --add safe.directory "$(realpath "$_NIODIR_PRE")" 2>/dev/null || true
git -C "$_NIODIR_PRE" reset --hard 2>/dev/null || true
git -C "$_NIODIR_PRE" clean -ffd 2>/dev/null || true
fi
swift package resolve 2>&1
# SwiftPM on FreeBSD creates all checkout files as mode 444 (read-only).
# Make them writable before any git operations or source patching below.
chmod -R u+w .build/checkouts 2>/dev/null || true
# swift-nio: switch checkout to FreeBSD-patched fork branch
# networkextension/swift-nio freebsd-compat-2.93.0 is based on the 2.93.0 tag
# and carries all FreeBSD compatibility fixes as proper source edits.
NIODIR=$(find .build/checkouts -maxdepth 1 -name "swift-nio" -type d 2>/dev/null | head -1)
if [ -n "$NIODIR" ]; then
chmod -R u+w "$NIODIR/.git" 2>/dev/null || true
git config --global --add safe.directory "$(realpath "$NIODIR")" 2>/dev/null || true
git -C "$NIODIR" reset --hard 2>/dev/null || true
git -C "$NIODIR" clean -ffd 2>/dev/null || true
git -C "$NIODIR" remote add networkextension https://github.com/networkextension/swift-nio.git 2>/dev/null || true
git -C "$NIODIR" fetch networkextension freebsd-compat-2.93.0
git -C "$NIODIR" checkout networkextension/freebsd-compat-2.93.0
echo "Switched swift-nio to networkextension/freebsd-compat-2.93.0"
fi
# CNIOBoringSSL: FreeBSD sys/time.h guards gettimeofday behind __XSI_VISIBLE,
# which is 0 unless _XOPEN_SOURCE>=500. BoringSSL doesn't define it.
# Inject _XOPEN_SOURCE 600 + sys/time.h unconditionally at line 1.
# Also strip any prior v1 patch (2-line) before applying the new 5-line one.
SSL_LIB=$(find .build/checkouts -path "*/CNIOBoringSSL*/ssl/ssl_lib.cc" 2>/dev/null | head -1)
if [ -n "$SSL_LIB" ]; then
grep -qF 'FreeBSD-gettimeofday-xsi' "$SSL_LIB" || {
grep -q 'FreeBSD-gettimeofday' "$SSL_LIB" && sed -i '' '1,2d' "$SSL_LIB"
awk 'NR==1{print "/* FreeBSD-gettimeofday-xsi */\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 600\n#endif\n#include <sys/time.h>"}1' "$SSL_LIB" > /tmp/_ssl.cc
mv /tmp/_ssl.cc "$SSL_LIB"
echo "Patched $SSL_LIB"
}
fi
# async-http-client CAsyncHTTPClient.c: strptime_l is behind __BSD_VISIBLE in
# FreeBSD time.h; no locale.h included for FreeBSD. Forward-declare both.
AHC=$(find .build/checkouts -path "*/CAsyncHTTPClient/CAsyncHTTPClient.c" 2>/dev/null | head -1)
if [ -n "$AHC" ]; then
grep -qF 'FreeBSD-strptime-fix' "$AHC" || {
awk 'NR==1{print "/* FreeBSD-strptime-fix */\n#if defined(__FreeBSD__)\n#include <locale.h>\nextern char *strptime_l(const char *, const char *, struct tm *, locale_t);\n#endif"}1' \
"$AHC" > /tmp/_ahc.c
mv /tmp/_ahc.c "$AHC"
echo "Patched $AHC"
}
fi
# CNIOBoringSSL: getentropy in unistd.h is behind __BSD_VISIBLE on FreeBSD.
# Forward-declare it directly so no feature macros are needed.
GE=$(find .build/checkouts -path "*/CNIOBoringSSL*/crypto/rand/getentropy.cc" 2>/dev/null | head -1)
if [ -n "$GE" ]; then
grep -qF 'FreeBSD-getentropy-fix' "$GE" || {
awk 'NR==1{print "/* FreeBSD-getentropy-fix */\n#if defined(__FreeBSD__)\nextern \"C\" int getentropy(void*, __SIZE_TYPE__);\n#endif"}1' "$GE" > /tmp/_ge.cc
mv /tmp/_ge.cc "$GE"
echo "Patched $GE"
}
fi
# CNIOBoringSSL umbrella header: arpa/inet.h is not included on FreeBSD, so
# inet_ntop/inet_pton are invisible to Swift code (e.g. NIOSSL SubjectAlternativeName.swift).
# Header lives at swift-nio-ssl/Sources/CNIOBoringSSL/include/CNIOBoringSSL.h (no subdir).
CNIO_HDR=$(find .build/checkouts -path "*/CNIOBoringSSL/include/CNIOBoringSSL.h" 2>/dev/null | head -1)
if [ -n "$CNIO_HDR" ]; then
grep -qF 'FreeBSD-arpa-inet-v2' "$CNIO_HDR" || {
sed -i '' '/^\/\* FreeBSD-arpa-inet/,$d' "$CNIO_HDR"
printf '\n/* FreeBSD-arpa-inet-v2: inet_ntop/inet_pton are #defined to __inet_ntop/__inet_pton\n' >> "$CNIO_HDR"
printf ' * in <arpa/inet.h> on FreeBSD, hiding them from the Swift Clang importer.\n' >> "$CNIO_HDR"
printf ' * Both symbols are exported by libc at the same address. Undef and re-declare. */\n' >> "$CNIO_HDR"
printf '#if defined(__FreeBSD__)\n#include <arpa/inet.h>\n#undef inet_ntop\n#undef inet_pton\n' >> "$CNIO_HDR"
printf 'const char *inet_ntop(int, const void * __restrict, char * __restrict, unsigned int);\n' >> "$CNIO_HDR"
printf 'int inet_pton(int, const char * __restrict, void * __restrict);\n#endif\n' >> "$CNIO_HDR"
echo "Patched $CNIO_HDR"
}
fi
# Other NIO-style lock files (NIOLock.swift etc, not swift-nio itself):
# FreeBSD pthread types are opaque pointers like OpenBSD
find .build/checkouts \( -name "Locks.swift" -o -name "NIOLock.swift" \) \
! -path "*/swift-nio/*" 2>/dev/null | while read LOCKS; do
grep -qF "pthread" "$LOCKS" || continue
grep -qF "os(OpenBSD) || os(FreeBSD)" "$LOCKS" || \
sed -i '' 's/os(OpenBSD)/os(OpenBSD) || os(FreeBSD)/g' "$LOCKS"
grep -qF "pthread_rwlock_t?>" "$LOCKS" || \
sed -i '' 's/pthread_rwlock_t> =/pthread_rwlock_t?> =/' "$LOCKS"
grep -qF "LockPrimitive = pthread_mutex_t?" "$LOCKS" || \
sed -i '' 's/typealias LockPrimitive = pthread_mutex_t$/typealias LockPrimitive = pthread_mutex_t?/' "$LOCKS"
grep -qF "pthread_mutexattr_t(bitPattern:" "$LOCKS" || \
sed -i '' 's/var attr = pthread_mutexattr_t()/var attr: pthread_mutexattr_t? = pthread_mutexattr_t(bitPattern: 0)/' "$LOCKS"
echo "Patched $LOCKS"
done
# swift-log Logging.swift: Glibc.stdout/stderr non-optional on FreeBSD (unlike Linux)
LOGGING=$(find .build/checkouts -path "*/swift-log*/Logging.swift" 2>/dev/null | head -1)
if [ -n "$LOGGING" ]; then
grep -qF "Glibc.stdout!" "$LOGGING" && \
sed -i '' 's/Glibc\.stdout!/Glibc.stdout/g; s/Glibc\.stderr!/Glibc.stderr/g' "$LOGGING" && \
echo "Patched $LOGGING"
fi
# swift-subprocess Thread.swift: two FreeBSD fixes —
# 1) MutexType/ConditionType need optional typealiases (opaque pthread types)
# 2) pthread_t is opaque on FreeBSD: exclude from canImport(Glibc) so FreeBSD
# uses pthread_t? (like macOS); _subprocess_pthread_create wants ptr-to-optional
SUBTHREAD=$(find .build/checkouts -path "*/swift-subprocess*/Thread.swift" 2>/dev/null | head -1)
if [ -n "$SUBTHREAD" ]; then
grep -qF "pthread_mutex_t?" "$SUBTHREAD" || {
awk '/^private typealias MutexType = pthread_mutex_t$/{
print "#if os(FreeBSD) || os(OpenBSD)\nprivate typealias MutexType = pthread_mutex_t?\nprivate typealias ConditionType = pthread_cond_t?\n#else\n" $0; next}
/^private typealias ConditionType = pthread_cond_t$/{print $0 "\n#endif"; next}1' \
"$SUBTHREAD" > /tmp/_th.swift
mv /tmp/_th.swift "$SUBTHREAD"
echo "Patched $SUBTHREAD (typealiases)"
}
# Remove stray column-0 #endif left by a prior bad undo (awk guard race)
awk 'prev == " var thread = pthread_t()" && /^#endif$/{next} {prev=$0; print}' \
"$SUBTHREAD" > /tmp/_th.swift && mv /tmp/_th.swift "$SUBTHREAD"
# pthread_t is also opaque on FreeBSD: exclude from canImport(Glibc)
# so FreeBSD falls into #else branch and uses pthread_t? like macOS
grep -qF '!os(FreeBSD) && !os(OpenBSD)) || canImport(Bionic)' "$SUBTHREAD" || {
sed -i '' 's/#if canImport(Glibc) || canImport(Bionic)/#if (canImport(Glibc) \&\& !os(FreeBSD) \&\& !os(OpenBSD)) || canImport(Bionic)/g' "$SUBTHREAD"
echo "Patched $SUBTHREAD (pthread_t? fix)"
}
fi
# swift-service-lifecycle Lock.swift: only #if os(Windows)/#else, no FreeBSD case.
# Insert #elseif os(OpenBSD||FreeBSD) with pthread_mutex_t? before the #else branch,
# and fix pthread_mutexattr_t() which has no default init on FreeBSD.
SVC=$(find .build/checkouts -path "*/swift-service-lifecycle*/ConcurrencyHelpers/Lock.swift" 2>/dev/null | head -1)
if [ -n "$SVC" ]; then
grep -qF "LockPrimitive = pthread_mutex_t?" "$SVC" || {
awk '/^typealias LockPrimitive = SRWLOCK$/{a=1; print; next}
a&&/^#else$/{print "#elseif os(OpenBSD) || os(FreeBSD)"; a=0; b=1; next}
b&&/^@usableFromInline$/{b=0; print; c=1; next}
c&&/^typealias LockPrimitive = pthread_mutex_t$/{print "typealias LockPrimitive = pthread_mutex_t?\n#else\n@usableFromInline\n"$0; c=0; next}
1' "$SVC" > /tmp/_svc.swift
mv /tmp/_svc.swift "$SVC"
sed -i '' 's/var attr = pthread_mutexattr_t()/var attr: pthread_mutexattr_t? = pthread_mutexattr_t(bitPattern: 0)/' "$SVC"
echo "Patched $SVC"
}
fi
# swift-async-algorithms Locking.swift: Glibc branch uses pthread_mutex_t.
# Add os(FreeBSD)||os(OpenBSD) branch BEFORE it (2-space indent = typealias block only).
LOCKING=$(find .build/checkouts -path "*/swift-async-algorithms*/AsyncAlgorithms/Locking.swift" 2>/dev/null | head -1)
if [ -n "$LOCKING" ]; then
grep -qF "os(FreeBSD) || os(OpenBSD)" "$LOCKING" || {
sed -i '' 's/^ #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) || canImport(wasi_pthread)$/ #elseif os(FreeBSD) || os(OpenBSD)\n typealias Primitive = pthread_mutex_t?\n #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) || canImport(wasi_pthread)/' "$LOCKING"
echo "Patched $LOCKING"
}
fi
# NIOSSL LinuxCABundle.swift: add FreeBSD CA cert search paths.
# The file already has os(FreeBSD) in its #if guard but only lists Linux paths.
CABUNDLE=$(find .build/checkouts -path "*/NIOSSL/LinuxCABundle.swift" 2>/dev/null | head -1)
if [ -n "$CABUNDLE" ]; then
grep -qF '/usr/local/share/certs/ca-root-nss.crt' "$CABUNDLE" || {
awk '/\/etc\/pki\/tls\/certs\/ca-bundle.crt/{print; print " \"/etc/ssl/cert.pem\", // FreeBSD (base)"; print " \"/usr/local/etc/ssl/cert.pem\", // FreeBSD (ca_root_nss)"; print " \"/usr/local/share/certs/ca-root-nss.crt\", // FreeBSD (ca_root_nss)"; next}1' "$CABUNDLE" > /tmp/_cab.swift
mv /tmp/_cab.swift "$CABUNDLE"
echo "Patched $CABUNDLE"
}
fi
# NIOSSL SSLContext.swift: add FreeBSD branch to platformDefaultConfiguration.
# Linux branch calls .withCString on non-optional; FreeBSD must guard-let first
# because LinuxCABundle declares rootCAFilePath as String? (optional).
SSLCTX=$(find .build/checkouts -path "*/NIOSSL/SSLContext.swift" 2>/dev/null | head -1)
if [ -n "$SSLCTX" ]; then
grep -qF 'os(FreeBSD)' "$SSLCTX" || {
awk '/^ #elseif os\(Android\)$/ && !done{print " #elseif os(FreeBSD)"; print " guard let caFile = rootCAFilePath else { return }"; print " let result = caFile.withCString { fp in"; print " CNIOBoringSSL_SSL_CTX_load_verify_locations(context, fp, nil)"; print " }"; print " if result == 0 {"; print " let errorStack = BoringSSLError.buildErrorStack()"; print " throw BoringSSLError.unknownError(errorStack)"; print " }"; done=1}{print}' "$SSLCTX" > /tmp/_ctx.swift
mv /tmp/_ctx.swift "$SSLCTX"
echo "Patched $SSLCTX"
}
fi
swift build 2>&1
swift test --filter SwiftlyTests 2>&1
ENDSSH
timeout-minutes: 90
freebsd-aarch64-14-tests:
name: Test / FreeBSD 14.4 aarch64
runs-on: ubuntu-latest
if: ${{ github.repository == 'networkextension/swiftly' || github.repository == 'swiftlang/swiftly' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install cloudflared
run: |
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install -y cloudflared
- name: Set up SSH key and known hosts
env:
FREEBSD_SSH_KEY_144: ${{ secrets.FREEBSD_SSH_KEY }}
FREEBSD_CF_HOSTNAME_144: ${{ secrets.FREEBSD_CF_HOSTNAME_144 }}
run: |
mkdir -p "$HOME/.ssh"
printf '%s\n' "$FREEBSD_SSH_KEY_144" > "$HOME/.ssh/freebsd_ci_key_144"
chmod 600 "$HOME/.ssh/freebsd_ci_key_144"
printf '%s ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQes9chPEJ0dX9K07yo8gtd8JzNw9mrPn7KwKC46Jje\n' \
"$FREEBSD_CF_HOSTNAME_144" >> "$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/known_hosts"
printf 'Host freebsd-ci-2\n HostName %s\n User swift\n IdentityFile %s/.ssh/freebsd_ci_key_144\n ProxyCommand cloudflared access tcp --hostname %%h\n' \
"$FREEBSD_CF_HOSTNAME_144" "$HOME" > "$HOME/.ssh/config"
chmod 600 "$HOME/.ssh/config"
- name: Sync source to board
run: |
ssh -F "$HOME/.ssh/config" freebsd-ci-2 'mkdir -p /home/swift/ci/swiftly'
rsync -avz --delete \
--exclude='.build' --exclude='.git' \
-e "ssh -F $HOME/.ssh/config" \
. freebsd-ci-2:/home/swift/ci/swiftly/
- name: Build and Test on FreeBSD 14.4 aarch64
run: |
ssh -F "$HOME/.ssh/config" freebsd-ci-2 /bin/sh << 'ENDSSH'
set -e
# Ensure gnupg is installed — tests use gpg to sign mock toolchain archives.
which gpg >/dev/null 2>&1 || doas pkg install -y gnupg 2>&1 || true
# Stop any swift processes left over from a previous cancelled/timed-out run.
pkill -u swift -x swift-test 2>/dev/null || true
pkill -u swift -x swift-build 2>/dev/null || true
pkill -u swift -f "swift-package resolve" 2>/dev/null || true
pkill -u swift -f "swiftlyPackageTests.xctest" 2>/dev/null || true
. "$HOME/swift632-env.sh"
cd "$HOME/ci/swiftly"
_NIODIR_PRE=$(find .build/checkouts -maxdepth 1 -name "swift-nio" -type d 2>/dev/null | head -1)
if [ -n "$_NIODIR_PRE" ]; then
chmod -R u+w "$_NIODIR_PRE" 2>/dev/null || true
git config --global --add safe.directory "$(realpath "$_NIODIR_PRE")" 2>/dev/null || true
git -C "$_NIODIR_PRE" reset --hard 2>/dev/null || true
git -C "$_NIODIR_PRE" clean -ffd 2>/dev/null || true
fi
swift package resolve 2>&1
chmod -R u+w .build/checkouts 2>/dev/null || true
NIODIR=$(find .build/checkouts -maxdepth 1 -name "swift-nio" -type d 2>/dev/null | head -1)
if [ -n "$NIODIR" ]; then
chmod -R u+w "$NIODIR/.git" 2>/dev/null || true
git config --global --add safe.directory "$(realpath "$NIODIR")" 2>/dev/null || true
git -C "$NIODIR" reset --hard 2>/dev/null || true
git -C "$NIODIR" clean -ffd 2>/dev/null || true
git -C "$NIODIR" remote add networkextension https://github.com/networkextension/swift-nio.git 2>/dev/null || true
git -C "$NIODIR" fetch networkextension freebsd-compat-2.93.0
git -C "$NIODIR" checkout networkextension/freebsd-compat-2.93.0
echo "Switched swift-nio to networkextension/freebsd-compat-2.93.0"
fi
SSL_LIB=$(find .build/checkouts -path "*/CNIOBoringSSL*/ssl/ssl_lib.cc" 2>/dev/null | head -1)
if [ -n "$SSL_LIB" ]; then
grep -qF 'FreeBSD-gettimeofday-xsi' "$SSL_LIB" || {
grep -q 'FreeBSD-gettimeofday' "$SSL_LIB" && sed -i '' '1,2d' "$SSL_LIB"
awk 'NR==1{print "/* FreeBSD-gettimeofday-xsi */\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 600\n#endif\n#include <sys/time.h>"}1' "$SSL_LIB" > /tmp/_ssl.cc
mv /tmp/_ssl.cc "$SSL_LIB"
echo "Patched $SSL_LIB"
}
fi
AHC=$(find .build/checkouts -path "*/CAsyncHTTPClient/CAsyncHTTPClient.c" 2>/dev/null | head -1)
if [ -n "$AHC" ]; then
grep -qF 'FreeBSD-strptime-fix' "$AHC" || {
awk 'NR==1{print "/* FreeBSD-strptime-fix */\n#if defined(__FreeBSD__)\n#include <locale.h>\nextern char *strptime_l(const char *, const char *, struct tm *, locale_t);\n#endif"}1' \
"$AHC" > /tmp/_ahc.c
mv /tmp/_ahc.c "$AHC"
echo "Patched $AHC"
}
fi
GE=$(find .build/checkouts -path "*/CNIOBoringSSL*/crypto/rand/getentropy.cc" 2>/dev/null | head -1)
if [ -n "$GE" ]; then
grep -qF 'FreeBSD-getentropy-fix' "$GE" || {
awk 'NR==1{print "/* FreeBSD-getentropy-fix */\n#if defined(__FreeBSD__)\nextern \"C\" int getentropy(void*, __SIZE_TYPE__);\n#endif"}1' "$GE" > /tmp/_ge.cc
mv /tmp/_ge.cc "$GE"
echo "Patched $GE"
}
fi
CNIO_HDR=$(find .build/checkouts -path "*/CNIOBoringSSL/include/CNIOBoringSSL.h" 2>/dev/null | head -1)
if [ -n "$CNIO_HDR" ]; then
grep -qF 'FreeBSD-arpa-inet-v2' "$CNIO_HDR" || {
sed -i '' '/^\/\* FreeBSD-arpa-inet/,$d' "$CNIO_HDR"
printf '\n/* FreeBSD-arpa-inet-v2: inet_ntop/inet_pton are #defined to __inet_ntop/__inet_pton\n' >> "$CNIO_HDR"
printf ' * in <arpa/inet.h> on FreeBSD, hiding them from the Swift Clang importer.\n' >> "$CNIO_HDR"
printf ' * Both symbols are exported by libc at the same address. Undef and re-declare. */\n' >> "$CNIO_HDR"
printf '#if defined(__FreeBSD__)\n#include <arpa/inet.h>\n#undef inet_ntop\n#undef inet_pton\n' >> "$CNIO_HDR"
printf 'const char *inet_ntop(int, const void * __restrict, char * __restrict, unsigned int);\n' >> "$CNIO_HDR"
printf 'int inet_pton(int, const char * __restrict, void * __restrict);\n#endif\n' >> "$CNIO_HDR"
echo "Patched $CNIO_HDR"
}
fi
find .build/checkouts \( -name "Locks.swift" -o -name "NIOLock.swift" \) \
! -path "*/swift-nio/*" 2>/dev/null | while read LOCKS; do
grep -qF "pthread" "$LOCKS" || continue
grep -qF "os(OpenBSD) || os(FreeBSD)" "$LOCKS" || \
sed -i '' 's/os(OpenBSD)/os(OpenBSD) || os(FreeBSD)/g' "$LOCKS"
grep -qF "pthread_rwlock_t?>" "$LOCKS" || \
sed -i '' 's/pthread_rwlock_t> =/pthread_rwlock_t?> =/' "$LOCKS"
grep -qF "LockPrimitive = pthread_mutex_t?" "$LOCKS" || \
sed -i '' 's/typealias LockPrimitive = pthread_mutex_t$/typealias LockPrimitive = pthread_mutex_t?/' "$LOCKS"
grep -qF "pthread_mutexattr_t(bitPattern:" "$LOCKS" || \
sed -i '' 's/var attr = pthread_mutexattr_t()/var attr: pthread_mutexattr_t? = pthread_mutexattr_t(bitPattern: 0)/' "$LOCKS"
echo "Patched $LOCKS"
done
LOGGING=$(find .build/checkouts -path "*/swift-log*/Logging.swift" 2>/dev/null | head -1)
if [ -n "$LOGGING" ]; then
grep -qF "Glibc.stdout!" "$LOGGING" && \
sed -i '' 's/Glibc\.stdout!/Glibc.stdout/g; s/Glibc\.stderr!/Glibc.stderr/g' "$LOGGING" && \
echo "Patched $LOGGING"
fi
SUBTHREAD=$(find .build/checkouts -path "*/swift-subprocess*/Thread.swift" 2>/dev/null | head -1)
if [ -n "$SUBTHREAD" ]; then
grep -qF "pthread_mutex_t?" "$SUBTHREAD" || {
awk '/^private typealias MutexType = pthread_mutex_t$/{
print "#if os(FreeBSD) || os(OpenBSD)\nprivate typealias MutexType = pthread_mutex_t?\nprivate typealias ConditionType = pthread_cond_t?\n#else\n" $0; next}
/^private typealias ConditionType = pthread_cond_t$/{print $0 "\n#endif"; next}1' \
"$SUBTHREAD" > /tmp/_th.swift
mv /tmp/_th.swift "$SUBTHREAD"
echo "Patched $SUBTHREAD (typealiases)"
}
awk 'prev == " var thread = pthread_t()" && /^#endif$/{next} {prev=$0; print}' \
"$SUBTHREAD" > /tmp/_th.swift && mv /tmp/_th.swift "$SUBTHREAD"
grep -qF '!os(FreeBSD) && !os(OpenBSD)) || canImport(Bionic)' "$SUBTHREAD" || {
sed -i '' 's/#if canImport(Glibc) || canImport(Bionic)/#if (canImport(Glibc) \&\& !os(FreeBSD) \&\& !os(OpenBSD)) || canImport(Bionic)/g' "$SUBTHREAD"
echo "Patched $SUBTHREAD (pthread_t? fix)"
}
fi
SVC=$(find .build/checkouts -path "*/swift-service-lifecycle*/ConcurrencyHelpers/Lock.swift" 2>/dev/null | head -1)
if [ -n "$SVC" ]; then
grep -qF "LockPrimitive = pthread_mutex_t?" "$SVC" || {
awk '/^typealias LockPrimitive = SRWLOCK$/{a=1; print; next}
a&&/^#else$/{print "#elseif os(OpenBSD) || os(FreeBSD)"; a=0; b=1; next}
b&&/^@usableFromInline$/{b=0; print; c=1; next}
c&&/^typealias LockPrimitive = pthread_mutex_t$/{print "typealias LockPrimitive = pthread_mutex_t?\n#else\n@usableFromInline\n"$0; c=0; next}
1' "$SVC" > /tmp/_svc.swift
mv /tmp/_svc.swift "$SVC"
sed -i '' 's/var attr = pthread_mutexattr_t()/var attr: pthread_mutexattr_t? = pthread_mutexattr_t(bitPattern: 0)/' "$SVC"
echo "Patched $SVC"
}
fi
LOCKING=$(find .build/checkouts -path "*/swift-async-algorithms*/AsyncAlgorithms/Locking.swift" 2>/dev/null | head -1)
if [ -n "$LOCKING" ]; then
grep -qF "os(FreeBSD) || os(OpenBSD)" "$LOCKING" || {
sed -i '' 's/^ #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) || canImport(wasi_pthread)$/ #elseif os(FreeBSD) || os(OpenBSD)\n typealias Primitive = pthread_mutex_t?\n #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) || canImport(wasi_pthread)/' "$LOCKING"
echo "Patched $LOCKING"
}
fi
# NIOSSL LinuxCABundle.swift: add FreeBSD CA cert search paths.
CABUNDLE=$(find .build/checkouts -path "*/NIOSSL/LinuxCABundle.swift" 2>/dev/null | head -1)
if [ -n "$CABUNDLE" ]; then
grep -qF '/usr/local/share/certs/ca-root-nss.crt' "$CABUNDLE" || {
awk '/\/etc\/pki\/tls\/certs\/ca-bundle.crt/{print; print " \"/etc/ssl/cert.pem\", // FreeBSD (base)"; print " \"/usr/local/etc/ssl/cert.pem\", // FreeBSD (ca_root_nss)"; print " \"/usr/local/share/certs/ca-root-nss.crt\", // FreeBSD (ca_root_nss)"; next}1' "$CABUNDLE" > /tmp/_cab.swift
mv /tmp/_cab.swift "$CABUNDLE"
echo "Patched $CABUNDLE"
}
fi
# NIOSSL SSLContext.swift: add FreeBSD branch to platformDefaultConfiguration.
SSLCTX=$(find .build/checkouts -path "*/NIOSSL/SSLContext.swift" 2>/dev/null | head -1)
if [ -n "$SSLCTX" ]; then
grep -qF 'os(FreeBSD)' "$SSLCTX" || {
awk '/^ #elseif os\(Android\)$/ && !done{print " #elseif os(FreeBSD)"; print " guard let caFile = rootCAFilePath else { return }"; print " let result = caFile.withCString { fp in"; print " CNIOBoringSSL_SSL_CTX_load_verify_locations(context, fp, nil)"; print " }"; print " if result == 0 {"; print " let errorStack = BoringSSLError.buildErrorStack()"; print " throw BoringSSLError.unknownError(errorStack)"; print " }"; done=1}{print}' "$SSLCTX" > /tmp/_ctx.swift
mv /tmp/_ctx.swift "$SSLCTX"
echo "Patched $SSLCTX"
}
fi
swift build 2>&1
swift test --filter SwiftlyTests 2>&1
ENDSSH
timeout-minutes: 90
macos-tests-selfhosted:
name: Test (Self Hosted) / macOS Tahoe ARM64
runs-on: [self-hosted, macos, tahoe, ARM64]
if: ${{ github.repository == 'swiftlang/swiftly' }}
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh --install-swiftly
- name: Build and Test
run: swift test
timeout-minutes: 60
tests-selfhosted:
name: Test (Self Hosted) / ${{ matrix.container }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
container: ["ubuntu:22.04", "ubuntu:24.04", "redhat/ubi9", "debian:12", "fedora:39"]
container:
image: ${{ matrix.container }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh --install-swiftly
- name: Build and Test
# UBI 9 - See https://github.com/swiftlang/swift/issues/80908
# UBI 9 - See https://github.com/swiftlang/swift/issues/80909
run: bash -c 'if [[ "${{ matrix.container }}" == "redhat/ubi9" ]]; then swift build --build-tests; else swift test; fi'
# Verify new platforms without a released Swiftly build
tests-bootstrapped:
name: Test (Bootstrapped) / ${{ matrix.container }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
container: ["swiftlang/swift:nightly-6.3-fedora41"]
container:
image: ${{ matrix.container }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh
- name: Build and Test
run: swift test
releasebuildcheck:
name: Release Build Check / Linux
runs-on: ubuntu-latest
container:
image: "redhat/ubi9"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh --install-swiftly
- name: Build Artifact
run: swift run build-swiftly-release --test --skip "999.0.0"
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: swiftly-release-x86_64
path: .build/release/swiftly-*.tar.gz
if-no-files-found: error
retention-days: 1
- name: Upload Tests
uses: actions/upload-artifact@v4
with:
name: test-swiftly-linux-x86_64
path: .build/debug/test-swiftly-linux-x86_64.tar.gz
if-no-files-found: error
retention-days: 1
releasebuildcheckmacos:
name: Release Build Check / macOS
runs-on: [self-hosted, macos, tahoe, ARM64]
if: ${{ github.repository == 'swiftlang/swiftly' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh --install-swiftly
- name: Build Artifact
run: swift run build-swiftly-release --test --skip "999.0.0"
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: swiftly-release-darwin
path: .build/release/swiftly-*.pkg
if-no-files-found: error
retention-days: 1
- name: Upload Tests
uses: actions/upload-artifact@v4
with:
name: test-swiftly-macos
path: .build/release/test-swiftly-macos.tar.gz
if-no-files-found: error
retention-days: 1
releasetest:
name: Test Release / ${{matrix.shell.pkg}}
needs: releasebuildcheck
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shell: [
{"pkg": "bash", "bin": "/bin/bash"},
{"pkg": "zsh", "bin": "/bin/zsh"},
{"pkg": "fish", "bin": "/bin/fish"}
]
container:
image: "ubuntu:24.04"
steps:
- name: Prepare System
run: apt-get update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install ca-certificates gpg tzdata ${{matrix.shell.pkg}} && chsh -s ${{matrix.shell.bin}}
- name: Download Release
uses: actions/download-artifact@v4
with:
name: swiftly-release-x86_64
- name: Download Tests
uses: actions/download-artifact@v4
with:
name: test-swiftly-linux-x86_64
- name: Extract and Run Workflow Tests
run: cp swiftly-*.tar.gz /root/swiftly.tar.gz && cp test-swiftly-*.tar.gz /root && cd /root && tar zxf test-swiftly-*.tar.gz && ./test-swiftly -y ./swiftly.tar.gz
release-custom-install-test:
name: Test Release - Custom Install Location
needs: releasebuildcheck
runs-on: ubuntu-latest
container:
image: "ubuntu:24.04"
steps:
- name: Prepare System
run: apt-get update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install ca-certificates gpg tzdata
- name: Download Release
uses: actions/download-artifact@v4
with:
name: swiftly-release-x86_64
- name: Download Tests
uses: actions/download-artifact@v4
with:
name: test-swiftly-linux-x86_64
- name: Extract and Run Workflow Tests
run: cp swiftly-*.tar.gz /root/swiftly.tar.gz && cp test-swiftly-*.tar.gz /root && cd /root && tar zxf test-swiftly-*.tar.gz && ./test-swiftly -y --custom-location ./swiftly.tar.gz
formatcheck:
name: Format Check
runs-on: ubuntu-latest
container:
image: ubuntu:22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh --install-swiftly
- name: Check format
run: swift run swiftformat --lint --dryrun . || (echo "Please run 'swift run swiftformat .' to format the source code."; exit 1)
docscheck:
name: Documentation Check
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
steps:
- name: Install git
run: apt-get update && apt-get -y install git
- name: Checkout repository
uses: actions/checkout@v4
- name: Prepare the action
run: ./scripts/prep-gh-action.sh --install-swiftly
- name: Generate Swiftly CLI Reference and Check for Differences
run: swift package plugin --allow-writing-to-package-directory generate-docs-reference && bash -c 'git config --global --add safe.directory $(pwd)' && git diff --exit-code Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md || (echo "The documentation hasn't been updated with the latest swiftly command-line reference. Please run 'swift package plugin generate-docs-reference' and commit/push the changes."; exit 1)
- name: Generate Documentation Set
run: swift package --allow-writing-to-directory .build/docs generate-documentation --target SwiftlyDocs --output-path .build/docs
- name: Upload Documentation Artifacts
uses: actions/upload-artifact@v4
with:
name: swiftly-docs
path: .build/docs/**
if-no-files-found: error
retention-days: 1