Skip to content

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:

bash
make
make test

This 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):

HostStatusNotes
Linux (x86-64)Supported, CI-gatedPrimary platform; linux:test and linux:sanitizer jobs run on every push.
macOS (Apple Silicon)Supportedmacos:test job is opt-in (set the ENABLE_MACOS_CI repository variable to 1).
WindowsUnsupportedThe 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 to native (the host CPU and its full feature set); cross builds default to baseline (the target architecture's portable baseline, no CPU pinning). A concrete CPU name pins that CPU. --mcpu native with a cross --target is 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

CommandPurpose
makeConfigure and build using system LLVM/shared libclang.
make configureRegenerate the CMake/Ninja build files.
make testRun core and tooling tests.
make installInstall under PREFIX, default /usr/local.
make uninstallRemove installed Zynx files and SDKs from PREFIX.
make libsBuild the compiler and runtime archive.
make lintRun clang-tidy through compile_commands.json.
make staticBuild with bundled LLVM and static LLVM/Clang/libclang.
make deps-llvmDownload, build, and install the bundled LLVM dependency.
make sdkBuild 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 cleanRemove Zynx build outputs while preserving bundled LLVM deps.
make distcleanRemove Zynx outputs, bundled deps, and SDK builds.

Install locations can be changed with PREFIX:

bash
make install PREFIX=/tmp/zynx-install

Test 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:

text
requires loopback
requires filesystem-write
requires dynamic-linking
requires subprocess
requires network-external

Known 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:

bash
python3 tests/run.py

The 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:

bash
python3 tests/run.py --require-capability loopback --only-requiring-capability loopback

Platform-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

OptionDefaultMeaning
BUILDTYPEdebugdebug maps to CMake Debug; release maps to MinSizeRel.
PREFIX/usr/localInstall prefix used by make install and make uninstall.
CCclangC compiler for Zynx.
CXXderived from CCC++ linker/compiler used for the final executable link.
ARllvm-ar or arArchive tool for static libraries.
LLVM_PROVIDERsystemsystem uses installed LLVM; bundled uses build/deps/llvm/install.
LLVM_LINKsharedshared links shared LLVM/libclang; static links static LLVM/Clang/libclang.
LLVM_VERSION22.1.5Bundled LLVM release. A new version needs a matching pinned hash.
LLVM_TARGETSNativeLLVM targets built for the bundled dependency.
LLVM_BUNDLED_PREFIXbuild/deps/llvm/installInstall prefix for bundled LLVM.
LLVM_BUILDTYPEReleaseCMake build type used for bundled LLVM itself.
LLVM_CCACHEautoauto, on, or off for bundled LLVM compiler launcher use.
LLVM_JOBSemptyOptional parallel job count for the bundled LLVM install build.
LLVM_CONFIGllvm-configUsed to derive system LLVM package paths.
ZYNX_USE_LLDautoauto, on, or off for using lld on ELF links.
ZYNX_LINK_ICFsafeoff, safe, or all identical code folding with lld.
ZYNX_LINK_MAPOFFWrite a linker map into the build artifact directory.
EXTRA_CFLAGSemptyExtra C compile flags for Zynx targets.
EXTRA_LDFLAGSemptyExtra executable linker flags.
SDK_ROOTbuild/sdkInstall root for in-tree Zynx SDK targets.
SDK_TARGETSall default SDK targetsSDK targets built by make sdk.
TARGETemptyConvenience selector for make sdk TARGET=<triple>.
SDK_PACKAGEONCreate tar.gz archives for built SDK targets.
SDK_JOBSemptyOptional parallel job count for SDK dependency builds.
SDK_MUSL_VERSION1.2.6musl release used by default SDK targets.
DARWIN_SDK_PROVIDERautoDarwin SDK provider: auto, xcode, zig, or portable.
DARWIN_SDKROOTxcrun --sdk macosx --show-sdk-pathApple SDK root used by the xcode Darwin SDK provider.
DARWIN_ZIGzig on PATHZig executable used by the zig Darwin SDK provider.
DARWIN_SDK_SOURCEthird_party/zig-darwinPortable 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_VERSION11.0Minimum macOS deployment version for Darwin SDK targets.
DARWIN_SDK_VERSIONxcrun --sdk macosx --show-sdk-versionSDK version recorded in Darwin manifests.

Build Types

Use BUILDTYPE=debug or BUILDTYPE=release:

bash
make BUILDTYPE=debug
make BUILDTYPE=release

debug 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:

bash
make static                 # Release/MinSizeRel by default.
make static BUILDTYPE=debug # Debug static build.

LLVM Modes

The two main knobs are:

bash
LLVM_PROVIDER=system|bundled
LLVM_LINK=shared|static

The normal default is:

bash
LLVM_PROVIDER=system
LLVM_LINK=shared

In 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:

bash
make static

This target forces:

bash
LLVM_PROVIDER=bundled
LLVM_LINK=static

The 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:

bash
make LLVM_DIR=/path/to/lib/cmake/llvm Clang_DIR=/path/to/lib/cmake/clang

If only LLVM_CONFIG is set, the build derives LLVM_DIR from:

bash
llvm-config --cmakedir

and derives the nearby Clang package directory from that.

Useful overrides:

bash
make LLVM_CONFIG=/opt/homebrew/opt/llvm/bin/llvm-config
make LIBCLANG_LIBRARY=/path/to/libclang.so
make CLANG_C_INCLUDE_DIR=/path/to/include

On 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:

text
build/deps/llvm/install

The 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:

bash
make deps-llvm LLVM_TARGETS=Native

Useful dependency-build knobs:

bash
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-llvm

On macOS, use the system CPU count command instead:

bash
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:

bash
make sdk

Default 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:

text
build/deps
build/sdk

Use make distclean when dependency and SDK builds should be removed too:

bash
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:

bash
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=ON

ZYNX_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:

bash
otool -L ./zynx # macOS
ldd ./zynx      # Linux

Static mode should not show dynamic libLLVM, libclang, or libclang-cpp dependencies.

Extra Flags

The make wrapper preserves the usual compiler and linker overrides:

bash
make CC=clang CXX=clang++
make EXTRA_CFLAGS="-Wno-conversion"
make EXTRA_LDFLAGS="-fuse-ld=lld"

Sanitizer runtime targets also remain available:

bash
make test-runtime-sanitize
make test-runtime-tsan

Generate or refresh the compilation database with:

bash
make compile_commands.json
make refresh-compile-commands

Next Steps

Released under the MIT License.