SwiftOS Porting Guide
This guide explains how to evaluate and port source-built software to SwiftOS. It is written for application authors, package maintainers, and release owners who need to decide whether a program can run on the current SwiftOS ABI and how to adapt it without importing legacy platform assumptions.
SwiftOS is not Linux, does not provide a Linux ABI, and does not run existing ELF binaries unchanged. Porting means rebuilding from source against the SwiftOS syscall surface, native Embedded Swift bridge, or C/newlib compatibility layer.
Use this guide with:
- Compatibility Guide for platform-level compatibility decisions.
- Developer Guide for native SwiftOS build patterns.
- Application Cookbook for copy-paste app recipes.
- API Reference for syscall numbers, structures, bridge calls, and handle rights.
- Package Guide for package payload and package-store workflows.
- Package Build Automation Guide for the current
swportcatalog/recipe checks, Lua cross-build fixture, CI, and repository publishing workflow. - Administration Guide for accounts, capabilities, and image configuration.
Porting Model
SwiftOS ports are static, source-built, and explicit about their dependencies.
| Property | Current SwiftOS requirement |
|---|---|
| CPU target | aarch64-none-none-elf |
| Binary format | Static ELF user program |
| Dynamic loader | None |
| Kernel ABI | SwiftOS POSIX-like syscalls, not Linux syscalls |
| C library | newlib port plus project-owned compatibility shims |
| Preferred new app path | Native Embedded Swift |
| Durable installed files | Immutable base image or read-only package payload |
| Runtime writable state | /tmp, RAM-backed and cleared on reboot |
| Networking | Requires virtio-net boot profile and capNet |
The porting goal is not to emulate a legacy Unix distribution. Prefer small source changes, static linking, explicit capability requirements, and tests that prove the exact workflow under QEMU.
First Decision
Do not start with the build system. Start by classifying the program.
| Program shape | Recommended path |
|---|---|
| New first-party tool | Native Embedded Swift |
| Tiny low-level probe | Raw C with userland/lib/syscall.h |
| Existing C program needing libc | C/newlib compatibility path |
| Network daemon | Native Swift or C/newlib, plus explicit capNet test |
CLI tool with filesystem reads and /tmp writes |
Native Swift or C/newlib |
Program requiring Linux /proc, /sys, ioctls, epoll, dlopen, or shared libraries |
Redesign, stub, or defer |
| Binary-only software | Not compatible |
| Software needing persistent writable root | Redesign state storage or defer |
If the program is small and core to SwiftOS, rewrite it natively. If the program is an upstream C utility with portable libc assumptions, try newlib. If it depends on Linux kernel surfaces, treat the missing surface as a product decision, not a local hack.
Compatibility Triage
Before editing code, answer these questions.
| Question | Continue when | Stop or redesign when |
|---|---|---|
| Is source available? | Yes | Binary-only |
| Can it link statically? | Yes | Requires shared libraries or plugins |
Can storage be read-only plus /tmp? |
Yes | Requires persistent mutable root |
| Can missing OS APIs be removed or shimmed narrowly? | Yes | Requires broad Linux emulation |
| Does it need networking? | QEMU profile and capNet are acceptable |
It assumes host networking configuration APIs |
| Does it need process control? | spawn, execve, pipes, poll, or limited fork are enough |
Needs full Unix job control or procfs |
| Does it need threads? | Current thread_create and futex-like waits are enough |
Needs full pthreads contract |
| Does it need files larger than the current test envelope? | You can add focused tests | It needs unimplemented storage behavior |
Record the decision in the package notes or PR description. A good port should make its missing assumptions obvious before code review.
Choose A Build Path
Native Embedded Swift
Use this for new SwiftOS tools and services.
Reference files:
| File | Use |
|---|---|
userland/lib/swift_user.h |
Swift bridge declarations |
userland/lib/swift_user.c |
Runtime hooks and bridge implementation |
userland/user.ld |
User ELF linker script |
userland/*.swift |
Existing native tools |
Rules:
- Do not import Foundation.
- Keep binaries static.
- Use
swiftos_*bridge calls for OS services. - Check negative return values explicitly.
- Document required capability bits.
- Add a QEMU test for user-visible behavior.
Raw C Syscalls
Use this for small ABI probes or utilities that do not need libc.
Reference files:
| File | Use |
|---|---|
userland/lib/syscall.h |
Syscall numbers and inline wrappers |
userland/lib/fs.h |
File structures and flags |
userland/lib/termios.h |
Minimal terminal ABI |
Raw wrappers expose SwiftOS return conventions directly. Most errors are
negative errno-like values. Do not assume libc-style -1 plus errno unless
you are using a compatibility wrapper that documents it.
C/newlib Compatibility
Use this for C ports that expect printf, malloc, basic POSIX-shaped file
APIs, socket declarations, or common libc entry points.
Reference files:
| File | Use |
|---|---|
userland/lib/crt0_newlib.S |
newlib startup |
userland/lib/newlib_syscalls.c |
newlib bottom end |
userland/compat/stubs.c |
Compatibility shims |
userland/compat/* |
Header compatibility surface |
Build the sysroot when needed:
make newlib
Then model the Makefile target on an existing newlib-linked program. Keep the link static and stage the resulting ELF into the base image or a package payload.
Common Code Adaptations
| Upstream assumption | SwiftOS adaptation |
|---|---|
| Linux syscall numbers | Use syscall.h wrappers or newlib shims |
Dynamic linking or dlopen |
Build a static binary and disable plugin loading |
/proc or /sys |
Use SwiftOS process/sysinfo APIs when available, or remove the feature |
Persistent /var or /etc writes |
Use /tmp for scratch; rebuild base image for defaults |
fork as a server concurrency primitive |
Prefer poll, threads/futex where suitable, or a native service design |
epoll, kqueue, netlink |
Use current poll and socket syscalls or defer feature |
| Unix users/groups as security policy | Map to SwiftOS principals and capability masks |
| Runtime package downloads | Use host-built package payloads or signed repository fixtures today |
| Shell scripts relying on many external tools | Keep scripts small or port the required tools first |
When adding a shim, keep it narrow. A stub that fails clearly is better than a large compatibility layer that silently lies about behavior.
Filesystem Porting
SwiftOS has an immutable base image and RAM-backed /tmp.
| Need | Recommended path |
|---|---|
| Read default config | Stage it under base/etc/ or another base-image path |
| Read web/static assets | Stage them under base/www/ or a package payload |
| Read model or data blobs | Stage them under models/ and pack into /models |
| Write runtime scratch | Use /tmp |
| Persist runtime data across reboot | Not a current guest contract |
| Install optional software | Use a read-only package payload, package-store image, local .swpkg, or signed repository fixture |
Example guest check:
cat /etc/motd
echo ok >/tmp/port-check.txt
cat /tmp/port-check.txt
Writes outside writable tmpfs should fail. Do not work around that by adding mutable root assumptions to the port.
Networking Porting
Networking requires:
- QEMU launched with a virtio-net device.
- A principal with
capNet. - A program built against current socket and poll APIs.
Start with a narrow smoke:
./tests/tcp_echo_test.sh
./tests/udp_echo_test.sh
./tests/dns_test.sh
./tests/socket_test.sh
Use these reference programs:
| Source | Pattern |
|---|---|
userland/httpd.swift |
Poll-driven TCP server |
userland/tcpecho.swift |
TCP accept/read/write loop |
userland/udpecho.swift |
UDP socket and peer handling |
userland/tcpget.swift |
Simple TCP client |
userland/nslookup.swift |
DNS resolver use |
userland/tlsget.swift |
TLS 1.3 smoke client path |
Current limits:
- No target-side interface configuration command.
- No production certificate trust store policy.
- No Linux netlink, iptables, or routing-tool compatibility.
/bin/httpdand/bin/llmdboth bind guest TCP 8080; test one at a time.
Process And Concurrency Porting
Current process and concurrency tools include:
| Need | Current surface |
|---|---|
| Run another image | spawn, spawn_handles, execve |
| Wait for a child | waitpid |
| Pipes and redirection | pipe, dup, dup2, fcntl, poll, select, pselect |
| Nonblocking fd helpers | pipe2, socket(... SOCK_NONBLOCK ...), accept4, and O_NONBLOCK pipe read/write |
| Local full-duplex pairs | socketpair(AF_UNIX, SOCK_STREAM) with SOCK_NONBLOCK/SOCK_CLOEXEC, bidirectional IO, and poll readiness |
| Event notification | eventfd, eventfd_read, eventfd_write, plus poll/select readiness |
| Async event-loop wake | A worker thread can eventfd_write while the main thread blocks in poll |
| Timed condition waits | pthread_cond_timedwait supports realtime and libuv-style CLOCK_MONOTONIC deadlines |
| Signal lifecycle | sigaction, signal, raise, kill(pid, 0), kill(pid, SIGTERM), and waitpid signaled status |
| Threads | thread_create |
| Wait/wake primitive | futex |
| C pthread facade | pthread_create, pthread_join, mutexes, condition variables, once, and thread-specific data through newlib compat |
| C thread sync facade | sem_init, sem_wait, sem_post, sem_timedwait, pthread_rwlock_*, and pthread_barrier_* through newlib compat |
| Memory mapping | mmap, mmap_file, munmap, mprotect |
| Runtime clocks | clock_gettime, clock_getres, nanosleep through newlib compat |
Guidance:
- Prefer explicit
spawn_handlesfor new designs that pass authority. - Treat broad
forkinheritance as compatibility, not the long-term model. - Use
pollfor small servers before adding more process concurrency. - Respect W^X:
PROT_WRITE | PROT_EXECmappings are rejected. - Add a stress or QEMU acceptance test when touching concurrency behavior.
Packaging A Port
Use a package payload when the port should not become part of the default base image.
Current package flow:
make package-fixture
make package-overlay-test
make package-store-test
make package-local-install-test
make package-repo-install-test
Package content is read-only at runtime. Put binaries under /usr/bin in the
payload namespace unless the package guide says otherwise.
For source ports, use the ports tool and the current seed fixtures:
make ports-catalog-test
make ports-recipe-test
make ports-lua-repo-fixture
build/swpkg inspect build/lua.swpkg
build/pkgrepo inspect build/lua-repo-root/aarch64/current/catalog.signed
make package-lua-repo-install-test
make ports-zlib-repo-fixture
make ports-bzip2-repo-fixture
make ports-zstd-repo-fixture
make ports-xz-repo-fixture
make ports-libarchive-repo-fixture
make ports-ca-certificates-repo-fixture
make ports-pcre2-repo-fixture
make ports-tzdata-repo-fixture
make ports-nginx-repo-fixture
make ports-sqlite-repo-fixture
make ports-seed-repo-fixture
make package-ports-seed-repo-install-test
make ports-static-host-publish
make package-static-host-repo-install-test
make ports-hosted-url-verify-test
make package-static-host-dns-repo-install-test
The checked Lua, zlib, bzip2, zstd, xz, libarchive, ca-certificates, OpenSSL, pcre2,
tzdata, nginx, and sqlite recipes are the reference shapes today. Together they
prove static AArch64 builds or data-only staging, signed local repository
fixtures, guest pkg install by package name, Lua runtime checks, minigzip,
bzip2, zstd, and xz compression/decompression smokes, bsdtar tar
create/list smoke, CA and zoneinfo marker reads, a pcre2grep regex match,
nginx version/marker checks, a SQLite in-memory query, and static-host
publication from build/ports-static-host-root. The hosted URL
smoke also proves the target package manager can use a DNS-resolved HTTP
repository hostname for the same twelve-package seed.
Before publishing a package recipe, record:
- Source origin and license.
- Build flags and static-link choices.
- Installed paths.
- Required capability bits.
- Guest command used for verification.
- Known disabled upstream features.
- Acceptance test name.
See Package Guide and Server Software Catalog.
Testing A Port
Every shipped port needs an executable proof.
| Port behavior | Recommended proof |
|---|---|
| Pure parser, archive reader, or formatter | Host unit test |
| Command output | QEMU serial login test |
| File read/write behavior | QEMU test plus /tmp and base-image checks |
| Package visibility | Package overlay or package-store test |
| Guest package install | Local .swpkg or signed repository install test |
| Source-port recipe | ports-catalog-test, ports-recipe-test, the relevant port fixture, and a QEMU package install smoke when runnable |
| TCP service | QEMU network test plus host curl or nc |
| UDP service | QEMU network test plus host UDP client |
| DNS client | Resolver test |
| TLS client | TLS fixture test |
| AI/model asset loader | Model-bundle host test plus serving smoke |
Minimum gates for a user-visible port:
make build base-image
./tests/boot_test.sh
Use make test before calling a broad userland or compatibility change
release-ready.
Documentation For A Port
When a port becomes a supported SwiftOS workflow, update the relevant docs:
| Change | Docs to update |
|---|---|
| New command in the default image | COMMAND_REFERENCE.md, USER_GUIDE.md, tests |
| New network service | SERVICE_GUIDE.md, NETWORKING_GUIDE.md, tests |
| New package payload | PACKAGE_GUIDE.md, package format notes, tests |
| New source-port recipe | PACKAGE_BUILD_AUTOMATION.md, PORTING_GUIDE.md, package catalog, tests |
| New public ABI shim | API_REFERENCE.md, DEVELOPER_GUIDE.md, headers |
| New admin workflow | ADMINISTRATION_GUIDE.md, TROUBLESHOOTING.md |
| New known limit | RELEASE_NOTES.md, COMPATIBILITY_GUIDE.md |
Prefer documenting the exact current behavior. Future goals belong in roadmap documents unless the feature already works.
Port Review Checklist
Before merging a port:
- The program is rebuilt from source for
aarch64-none-none-elf. - The binary is static.
- No Linux ABI, dynamic loader,
/proc,/sys, or kernel-module assumption remains in the supported path. - Runtime writes are under
/tmpor an explicitly documented service. - Installed defaults are in the base image or package payload.
- Required capability bits are documented.
- Negative SwiftOS syscall returns are handled.
- Network tests name the QEMU forwarding and guest port.
- A focused test proves the main user-visible behavior.
- User-facing docs and troubleshooting notes are updated.
Useful Reference Programs
| Source | Pattern |
|---|---|
userland/echo.swift |
Argument scanning and stdout writes |
userland/cat.swift |
File read loop |
userland/ls.swift |
Directory entries, stat records, name lookup |
userland/head.swift |
Simple file processing |
userland/wc.swift |
Text counting and stdin/file handling |
userland/httpd.swift |
Poll-driven static HTTP server |
userland/tcpecho.swift |
TCP server |
userland/udpecho.swift |
UDP server |
userland/threadsdemo.swift |
Threads, atomics, futex |
userland/pthreadprobe.c |
newlib compat pthread create/join, mutexes, condition variables, once, and thread-specific data |
userland/threadsyncprobe.c |
newlib compat POSIX semaphores and pthread read/write locks |
userland/selectprobe.c |
newlib compat select/pselect readiness over poll |
userland/eventfdprobe.c |
newlib compat eventfd counter semantics and poll/select readiness |
userland/uvwakeprobe.c |
newlib compat libuv-style pthread-to-eventfd wake over blocking poll |
userland/uvsemprobe.c |
newlib compat POSIX semaphores for libuv's Unix semaphore wrappers |
userland/uvrwlockprobe.c |
newlib compat pthread read/write locks for libuv's Unix rwlock wrappers |
userland/uvmutexprobe.c |
newlib compat error-check and recursive mutex types for libuv's Unix mutex wrappers |
userland/uvthreadnameprobe.c |
newlib compat thread names for libuv's Unix thread helpers |
userland/uvthreadstackprobe.c |
newlib compat thread stack sizing for libuv's uv_thread_create_ex path |
userland/uvkeyonceprobe.c |
newlib compat thread-local keys, once guards, identity, join, and detach for libuv's Unix thread layer |
userland/uvenvprobe.c, userland/envchild.c |
newlib compat environment mutation plus execve envp propagation for libuv child-process setup |
userland/uvbarrierprobe.c |
newlib compat reusable pthread barriers for libuv's native barrier path |
userland/uvcondprobe.c |
newlib compat monotonic timed condition waits for libuv's Unix thread layer |
userland/uvsocketpairprobe.c |
newlib compat full-duplex AF_UNIX socketpair for libuv local streams |
userland/uvsignalprobe.c |
newlib compat pthread signal-mask facade and handler self-pipe wake for libuv signal watchers |
userland/uvatforkprobe.c |
newlib compat pthread_atfork handler ordering for libuv fork reinitialization |
userland/uvspawnprobe.c |
newlib compat fork/exec process-spawn handshake for libuv's Unix uv_spawn path |
userland/signalprobe.c |
newlib compat signal disposition, current-process handler frames, kill probes, SIGTERM child termination, and waitpid status |
userland/mmapdemo.swift |
mmap, mprotect, W^X |
userland/clockprobe.c |
newlib compat realtime and monotonic clocks |
userland/mprotectprobe.c |
newlib compat mmap, mprotect, executable memory, W^X |
userland/largemmapprobe.c |
newlib compat multi-MiB mmap, partial mprotect, and munmap reuse |
userland/mmapreserveprobe.c |
newlib compat PROT_NONE reservation, mprotect commit/decommit, and reserved JIT path |
userland/mapfixedprobe.c |
newlib compat MAP_FIXED, MAP_FIXED_NOREPLACE, guard-page recommit, and fixed JIT path |
userland/llmd.swift |
File-backed mmap, model bundles, TCP serving |
userland/spawndemo.c |
Spawn and explicit handle inheritance |
userland/c4b_sockxfer.c |
IPC endpoint handle transfer |
Use these as working examples before inventing a new pattern.
When To Defer A Port
Deferring is the right choice when the port would require a broad compatibility fiction.
Defer when the program requires:
- Linux kernel modules or device nodes that SwiftOS does not expose.
- Dynamic libraries or plugin loading as a core feature.
- Persistent database or writable-root semantics that cannot be redesigned.
- Full pthreads or process-group behavior not covered by current APIs.
/proc,/sys, netlink, cgroups, namespaces, seccomp, or containers.- GPU, CUDA, ROCm, or desktop graphics stacks.
- Production TLS trust policy before the trust-store work lands.
Record the blocker in the package catalog or roadmap so the dependency is visible and can be scheduled deliberately.