Building Zynx
This page is the build and test reference for contributors. Zynx uses CMake and Ninja for the real build graph, but the supported public interface is make. Normal development should not require calling CMake directly.
Quick Path
Build and test with the system LLVM packages:
make
make testThis configures build/cmake with Ninja, builds ./zynx, and builds the runtime archive build/lib/libzxrt.a. The standard library is used and installed from source; the build does not produce a precompiled libzynx.a.
Requirements
- CMake 3.20 or newer
- Ninja
- Clang or another C23-capable C compiler
- LLVM development package with CMake package files
- libclang development package for bindgen support
On Debian/Ubuntu this usually means installing the matching LLVM and libclang development packages, for example llvm-18-dev, clang-18, libclang-18-dev, and lld-18. Ubuntu may expose libclang as a versioned shared object such as libclang-18.so.1; the build accepts that layout, but clang-c/Index.h must still be installed or provided with CLANG_C_INCLUDE_DIR.
The default configuration links against the system LLVM and shared libclang. The static configuration can build a pinned bundled LLVM/Clang when the system packages do not provide static libclang.
Supported Hosts And Targets
Build hosts (where the Zynx compiler itself builds and runs):
| Host | Status | Notes |
|---|---|---|
| Linux (x86-64) | Supported, CI-gated | Primary platform; linux:test and linux:sanitizer jobs run on every push. |
| macOS (Apple Silicon) | Supported | macos:test job is opt-in (set the ENABLE_MACOS_CI repository variable to 1). |
| Windows | Unsupported | The current tree has no supported Windows build path or CI runner. |
Compilation targets are provided by the optional SDK. SDK targets are built on a Linux host by default. Linux targets are produced by default; Darwin targets are available through DARWIN_SDK_PROVIDER (xcode, zig, or portable). See SDK Reference for target lists, layouts, source pins, and discovery.
Target CPU And Features
The build target selects the instruction set every ordinary function may use. Public manifests, package metadata, cache keys, and future public feature flags name features with stable Zynx Feature Registry names, never LLVM spellings.
--mcpu <native|baseline|name>— the target CPU. Host builds default tonative(the host CPU and its full feature set); cross builds default tobaseline(the target architecture's portable baseline, no CPU pinning). A concrete CPU name pins that CPU.--mcpu nativewith a cross--targetis an error.- The exact public flag/configuration spelling for enabling or disabling registry features is not fixed yet. Whatever surface is selected must resolve to normalized stable Feature IDs before semantic analysis or cache lookup. Raw
--mattr <+llvm-feature,-llvm-feature,...>is retained only as an internal/debug backend escape hatch during migration; it is not portable public build metadata and cannot appear in semantic interfaces or artifacts.
The stdlib and target-payload caches are keyed by the resolved CPU, normalized Feature IDs, and Feature Registry version. Different normalized build feature sets never share code-generation objects. Cache entries under ZYNX_CACHE_DIR (or the default user cache directory) remain content-addressed implementation details rather than a public LLVM-feature contract.
Whole-build settings establish the artifact baseline. To raise the native feature set of one function, use typed @target_feature(.X86Avx2, .X86Fma) and gate baseline call sites at runtime with std.cpu.has. For Wasm, a per-function feature is legal only when already present in the artifact baseline. The call-safety rules are in Memory And Unsafe.
Common Commands
| Command | Purpose |
|---|---|
make | Configure and build using system LLVM/shared libclang. |
make configure | Regenerate the CMake/Ninja build files. |
make test | Run core and tooling tests. |
make install | Install under PREFIX, default /usr/local. |
make uninstall | Remove installed Zynx files and SDKs from PREFIX. |
make libs | Build the compiler and runtime archive. |
make lint | Run clang-tidy through compile_commands.json. |
make static | Build with bundled LLVM and static LLVM/Clang/libclang. |
make deps-llvm | Download, build, and install the bundled LLVM dependency. |
make sdk | Build default Linux SDK targets on a Linux host. |
make sdk TARGET=... | Build one Zynx SDK target by triple. |
make sdk SDK_TARGETS="..." | Build an explicit SDK target set. |
make sdk-clean TARGET=... | Remove one SDK target build/install. |
make clean | Remove Zynx build outputs while preserving bundled LLVM deps. |
make distclean | Remove Zynx outputs, bundled deps, and SDK builds. |
Install locations can be changed with PREFIX:
make install PREFIX=/tmp/zynx-installTest Capabilities And CI Tiers
The default test suite is hermetic by policy. Tests that need host facilities declare them in a sibling .requires file using one marker per line:
requires loopback
requires filesystem-write
requires dynamic-linking
requires subprocess
requires network-externalKnown capabilities are:
loopback: local IPv4 TCP/UDP sockets can be opened and bound.filesystem-write: the runner can create and remove temporary files.dynamic-linking: native dynamic payloads can be built and loaded.subprocess: child processes can be spawned.network-external: the test may contact network resources outside the host.
network-external is forbidden in the default tier. It must be explicitly allowed with --allow-capability network-external, and it should not be part of normal CI.
Default local and CI runs may skip tests whose capabilities are unavailable, but skips are reported with the capability name and reason:
python3 tests/run.pyThe host-capability tier is responsible for proving that skipped host tests actually run on a suitable machine. CI runs loopback-gated tests separately and fails immediately if loopback is unavailable:
python3 tests/run.py --require-capability loopback --only-requiring-capability loopbackPlatform-specific tiers should use the same pattern with explicit --require-capability and, for non-hermetic tests, --allow-capability. For full validation, run the default suite plus the host-capability tier on at least one host with loopback sockets enabled.
Capability probes can be overridden for runner/tooling tests with TEST_CAPABILITY_<NAME>=0 or 1, using uppercase names and underscores, for example TEST_CAPABILITY_LOOPBACK=0.
A test's .requires file is runner metadata only: it says which facilities the test host must make available before selecting the test. It is not a Zynx @host grant, does not satisfy an executable's requirement contract, and never implies filesystem or network authority inside the program. Programs with @host requirements are launched through the separately selected zynx-launch document defined by RFC 0068 through RFC 0070.
Option Reference
| Option | Default | Meaning |
|---|---|---|
BUILDTYPE | debug | debug maps to CMake Debug; release maps to MinSizeRel. |
PREFIX | /usr/local | Install prefix used by make install and make uninstall. |
CC | clang | C compiler for Zynx. |
CXX | derived from CC | C++ linker/compiler used for the final executable link. |
AR | llvm-ar or ar | Archive tool for static libraries. |
LLVM_PROVIDER | system | system uses installed LLVM; bundled uses build/deps/llvm/install. |
LLVM_LINK | shared | shared links shared LLVM/libclang; static links static LLVM/Clang/libclang. |
LLVM_VERSION | 22.1.5 | Bundled LLVM release. A new version needs a matching pinned hash. |
LLVM_TARGETS | Native | LLVM targets built for the bundled dependency. |
LLVM_BUNDLED_PREFIX | build/deps/llvm/install | Install prefix for bundled LLVM. |
LLVM_BUILDTYPE | Release | CMake build type used for bundled LLVM itself. |
LLVM_CCACHE | auto | auto, on, or off for bundled LLVM compiler launcher use. |
LLVM_JOBS | empty | Optional parallel job count for the bundled LLVM install build. |
LLVM_CONFIG | llvm-config | Used to derive system LLVM package paths. |
ZYNX_USE_LLD | auto | auto, on, or off for using lld on ELF links. |
ZYNX_LINK_ICF | safe | off, safe, or all identical code folding with lld. |
ZYNX_LINK_MAP | OFF | Write a linker map into the build artifact directory. |
EXTRA_CFLAGS | empty | Extra C compile flags for Zynx targets. |
EXTRA_LDFLAGS | empty | Extra executable linker flags. |
SDK_ROOT | build/sdk | Install root for in-tree Zynx SDK targets. |
SDK_TARGETS | all default SDK targets | SDK targets built by make sdk. |
TARGET | empty | Convenience selector for make sdk TARGET=<triple>. |
SDK_PACKAGE | ON | Create tar.gz archives for built SDK targets. |
SDK_JOBS | empty | Optional parallel job count for SDK dependency builds. |
SDK_MUSL_VERSION | 1.2.6 | musl release used by default SDK targets. |
DARWIN_SDK_PROVIDER | auto | Darwin SDK provider: auto, xcode, zig, or portable. |
DARWIN_SDKROOT | xcrun --sdk macosx --show-sdk-path | Apple SDK root used by the xcode Darwin SDK provider. |
DARWIN_ZIG | zig on PATH | Zig executable used by the zig Darwin SDK provider. |
DARWIN_SDK_SOURCE | third_party/zig-darwin | Portable Darwin SDK source; accepts the bundled Zig Darwin subset, a Zig lib dir, Zig libc dir, Zig libc/darwin dir, or Apple SDK root. |
DARWIN_MIN_VERSION | 11.0 | Minimum macOS deployment version for Darwin SDK targets. |
DARWIN_SDK_VERSION | xcrun --sdk macosx --show-sdk-version | SDK version recorded in Darwin manifests. |
Build Types
Use BUILDTYPE=debug or BUILDTYPE=release:
make BUILDTYPE=debug
make BUILDTYPE=releasedebug maps to CMake Debug, disables optimization, and defines ZYNX_DEBUG.
release maps to CMake MinSizeRel, uses size-oriented optimization, defines ZYNX_RELEASE, enables linker dead-code removal where supported, and strips the final executable.
make static defaults to release unless BUILDTYPE is set explicitly:
make static # Release/MinSizeRel by default.
make static BUILDTYPE=debug # Debug static build.LLVM Modes
The two main knobs are:
LLVM_PROVIDER=system|bundled
LLVM_LINK=shared|staticThe normal default is:
LLVM_PROVIDER=system
LLVM_LINK=sharedIn this mode Zynx uses the system LLVM package and links bindgen directly to a shared libclang at build time. There is no runtime LIBCLANG_PATH discovery or dlopen path for bindgen.
LLVM 18 and newer are supported for the normal build. Zynx handles the llvm.coro.end intrinsic return-type change between LLVM 21 and LLVM 22 when bootstrapping async-heavy standard library modules such as std.async.io.
The static mode is:
make staticThis target forces:
LLVM_PROVIDER=bundled
LLVM_LINK=staticThe resulting zynx executable statically links LLVM, Clang, and libclang into the compiler. It may still depend on normal platform libraries such as libc, libm, libdl, pthreads, or macOS system frameworks.
Finding System LLVM
For system LLVM builds, CMake first honors explicit package directories:
make LLVM_DIR=/path/to/lib/cmake/llvm Clang_DIR=/path/to/lib/cmake/clangIf only LLVM_CONFIG is set, the build derives LLVM_DIR from:
llvm-config --cmakedirand derives the nearby Clang package directory from that.
Useful overrides:
make LLVM_CONFIG=/opt/homebrew/opt/llvm/bin/llvm-config
make LIBCLANG_LIBRARY=/path/to/libclang.so
make CLANG_C_INCLUDE_DIR=/path/to/includeOn some Linux distributions, ClangConfig.cmake references static Clang archives that are not installed. Shared mode avoids importing the full Clang CMake package and only needs clang-c/Index.h plus a shared libclang.
Bundled LLVM
make deps-llvm builds the pinned bundled LLVM dependency into:
build/deps/llvm/installThe bundled dependency currently builds LLVM 22.1.5 from the release tarball with a pinned SHA-256 hash in cmake/deps/llvm/CMakeLists.txt. Changing LLVM_VERSION requires updating that hash.
The bundled build enables only the Clang project and disables shared LLVM libraries, LLVM tools, utilities, tests, docs, examples, benchmarks, bindings, zlib, zstd, libxml2, curl, and libedit. It builds native targets by default:
make deps-llvm LLVM_TARGETS=NativeUseful dependency-build knobs:
make deps-llvm LLVM_JOBS=$(nproc)
make deps-llvm LLVM_BUILDTYPE=MinSizeRel
make deps-llvm LLVM_CCACHE=on
make deps-llvm LLVM_BUNDLED_PREFIX=/opt/zynx-llvmOn macOS, use the system CPU count command instead:
make deps-llvm LLVM_JOBS=$(sysctl -n hw.ncpu)LLVM_CCACHE=auto uses ccache when it is installed. LLVM_CCACHE=on requires it and fails if it cannot be found.
Zynx SDK
The SDK is an opt-in cross toolchain for static Linux targets and Darwin overlays. Build the default Linux SDK target set with:
make sdkDefault Linux SDK packaging is Linux-host-only in the current SDK setup. Use the SDK Reference for target lists, Darwin overlays, source pins, discovery order, and zynx sdk management commands.
Cleaning
make clean removes Zynx build products but keeps the expensive bundled LLVM and SDK trees:
build/deps
build/sdkUse make distclean when dependency and SDK builds should be removed too:
make clean # Fast cleanup; keeps bundled LLVM and SDKs.
make distclean # Full cleanup; removes build/deps and build/sdk.Both the main CMake cache and the LLVM dependency-driver cache are refreshed automatically when a checkout is moved to a different path. This keeps copied worktrees from failing with stale absolute paths while preserving already-built bundled LLVM installs when possible.
Binary Size
The normal shared build stays small because LLVM and libclang live in shared libraries supplied by the system.
The static build is much larger because libclang.a pulls in a large amount of Clang and LLVM. The build already enables section splitting, dead-code removal, and release stripping. On Linux, static mode also uses lld automatically when available so identical code folding can reduce the final executable further.
Useful size/debugging knobs:
make static ZYNX_USE_LLD=on
make static ZYNX_USE_LLD=on ZYNX_LINK_ICF=safe
make static ZYNX_USE_LLD=on ZYNX_LINK_ICF=all
make static ZYNX_LINK_MAP=ONZYNX_LINK_ICF=safe is the default for static ELF links when lld is used. ZYNX_LINK_ICF=all can shrink more, but it is a more aggressive linker option and should be tested carefully.
ZYNX_LINK_MAP=ON writes a linker map into the build artifact directory, which is useful for seeing which libraries and object files dominate the static binary.
Check dynamic dependencies after a static build with:
otool -L ./zynx # macOS
ldd ./zynx # LinuxStatic mode should not show dynamic libLLVM, libclang, or libclang-cpp dependencies.
Extra Flags
The make wrapper preserves the usual compiler and linker overrides:
make CC=clang CXX=clang++
make EXTRA_CFLAGS="-Wno-conversion"
make EXTRA_LDFLAGS="-fuse-ld=lld"Sanitizer runtime targets also remain available:
make test-runtime-sanitize
make test-runtime-tsanGenerate or refresh the compilation database with:
make compile_commands.json
make refresh-compile-commandsNext Steps
- Use the Install Guide for the shortest user-facing setup path.
- Use the SDK Reference for cross-target packaging and discovery.
- Use the Package Reference for
zynx.json,zynx.lock, and.zxmbundle validation.