A package manager for C++. Inspired by Cargo.
| Platform | Architecture |
|---|---|
| macOS | ARM64 (Apple Silicon) |
| Linux | x86_64 |
| Linux | aarch64 |
| Windows | x86_64 (MSVC) |
curl -fsSL https://raw.githubusercontent.com/misut/exon/main/install.sh | shmise install "vfox:misut/mise-exon@latest"
mise use "vfox:misut/mise-exon@latest"Build from source
mise installs the pinned released exon seed and intron provisions the same toolchain that CI uses (LLVM, CMake, Ninja pinned in .intron.toml). On Windows, intron install uses this repo's .intron.toml to provision MSVC, CMake, and Ninja.
# install pinned seed tools from mise.toml
mise install
# install pinned toolchain into ~/.intron/toolchains
mise exec -- intron install
# inspect the toolchain plan used by exon
mise exec -- intron status --output human
# build current source with the released seed exon
mkdir -p .exon/seed/bin
cp "$(mise which exon)" .exon/seed/bin/exon
touch .exon/seed/bin/exon
./.exon/seed/bin/exon build
# optional: prove the newly built current-source exon can self-host
eval "$(mise exec -- intron env)"
./.exon/debug/exon buildThe released seed exon reads the current checkout and generates .exon/debug/exon, so normal source builds no longer require a separate CMake bootstrap script. Copying the seed into .exon/seed/bin keeps the build local to the checkout and lets older seed releases bootstrap this source tree on macOS without tripping their previous timestamp-based freshness guard. Self-hosted exon build, exon check, and exon test serialize the build automatically on macOS when import std; is enabled. If you change src/build.cppm, src/toolchain.cppm, or exon.toml in the exon repo, rebuild the current-source binary with the released seed before reusing ./.exon/debug/exon.
On Windows, a regular PowerShell session is enough:
mise install
mise exec -- intron install
mise exec -- intron status --output human
New-Item -ItemType Directory -Force -Path .\.exon\seed\bin | Out-Null
Copy-Item (mise which exon) .\.exon\seed\bin\exon.exe -Force
(Get-Item .\.exon\seed\bin\exon.exe).LastWriteTime = Get-Date
.\.exon\seed\bin\exon.exe build
$INTRON = mise which intron
Invoke-Expression (& $INTRON env | Out-String)
.\.exon\debug\exon.exe buildA Visual Studio Developer Command Prompt also works, but it is no longer required when intron is available.
exon init
# edit exon.toml and src/main.cpp
exon runcreated exon.toml (bin)
configuring...
building...
build succeeded: .exon/debug/hello
running hello...
hello, world!
| Command | Description |
|---|---|
exon init [--lib|--workspace] [name] |
Create a new package or workspace |
exon new --lib|--bin <name> |
Create a new workspace member from the workspace root |
exon info |
Show package information |
exon build [--release] [--target <t>] [--member a,b] [--exclude x,y] [--output human|json|wrapped|raw] [--color auto|always|never] [--progress auto|always|never] [--unicode auto|always|never] [--hyperlinks auto|always|never] |
Build the project or selected workspace members |
exon dist [--release] [--target <t>] [--output-dir <dir>] [--version <v>] [--output human|json|wrapped|raw] |
Build and package a release-compatible archive |
exon status [--output human|json] |
Inspect project, toolchain, and terminal status |
exon doctor [--output human|json] |
Alias for exon status |
exon check [--release] [--target <t>] [--member a,b] [--exclude x,y] |
Check syntax without linking |
exon run [--release] [--target <t>] [--member <name>] [args] |
Build and run |
exon debug [--release] [--debugger auto|lldb|gdb|devenv|cdb|<path>] [--member <name>] [--exclude x,y] [-- <args...>] |
Build and open the selected native executable in a native debugger |
exon test [--release] [--target <t>] [--member a,b] [--exclude x,y] [--timeout <sec>] [--output human|json|wrapped|raw] [--show-output failed|all|none] |
Build and run tests |
exon clean [--member a,b] [--exclude x,y] |
Remove build artifacts |
exon add [--dev] <pkg> <ver> [--features a,b] [--no-default-features] |
Add a git dependency |
exon add [--dev] --path <name> <path> |
Add a local path dependency |
exon add [--dev] --workspace <name> |
Add a workspace member dependency |
exon add [--dev] --vcpkg <name> <ver> [--features a,b] |
Add a vcpkg dependency |
exon add [--dev] --cmake <name> --repo <url> --tag <tag> --targets <targets> [--install-targets <targets>] [--install-package <name>] [--option K=V] [--install-option K=V] [--shallow false] |
Add a raw CMake dependency |
exon add [--dev] --git <repo> --version <v> --subdir <dir> [--name <n>] |
Add a git dep pointing to a subdirectory |
exon remove <pkg> |
Remove a dependency |
exon outdated [pkg...] [--member a,b] [--exclude x,y] [--output human|json] |
Check git dependencies for newer semver tags |
exon update [pkg...] [--dry-run] [--precise <version>] [--member a,b] [--exclude x,y] |
Update lockfile entries to latest compatible versions |
exon tree [--member a,b] [--exclude x,y] [--dev] [--features] [--output human|json] |
Show the resolved dependency graph |
exon why <pkg> [--member a,b] [--exclude x,y] [--dev] [--output human|json] |
Show why a package is in the dependency graph |
exon sync [--member a,b] [--exclude x,y] |
Sync CMakeLists.txt with exon.toml |
exon fmt |
Format source files with clang-format |
exon version |
Show exon version |
exon commands [--output human|json] |
Describe command metadata |
exon complete [--output human|json|raw] -- [words...] |
Print completion candidates |
exon completion bash|zsh|fish |
Generate a shell completion script |
exon debug is a native-only convenience launcher for host debuggers. It supports host executables only and does not support --target wasm32-wasi yet.
exon debug [--release] [--debugger auto|lldb|gdb|devenv|cdb|<path>] [--member <name>] [--exclude x,y] [-- <args...>]exon debug
exon debug -- --port 8080
exon debug --debugger gdb -- input.txt
exon debug --debugger devenv.com
exon debug --debugger C:\Debuggers\cdb.exe -- input.txt--debugger accepts auto, a built-in debugger name, or a path/command whose basename classifies as one of these supported debugger families:
lldb*gdb*devenv,devenv.exe,devenv.comcdb,cdb.exe
Examples of accepted custom values include lldb-18, gdb-14, devenv.com, and C:\Debuggers\cdb.exe. Values outside those families, including vsjitdebugger, are rejected.
auto picks a debugger by host platform:
- macOS:
lldb, thengdb - Linux:
gdb, thenlldb - Windows:
devenv, thencdb, thenlldb, thengdb
Debugger invocation follows the debugger's native CLI syntax:
- Visual Studio:
devenv /debugexe <executable> <args...> - CDB:
cdb <executable> <args...> - LLDB:
lldb -- <executable> <args...> - GDB:
gdb --args <executable> <args...>
Windows-specific notes:
devenvlaunches through the full Visual Studio IDE.cdbis the command-line debugger from the Windows debugger toolchain, which fits Build Tools / Debugging Tools-style environments better.devenvandcdbare only supported on Windows hosts.devenv /debugexecan misinterpret program arguments that start with/as Visual Studio switches.exon debug --debugger devenv -- /flagfails early with a targeted error.exon debug --debugger auto -- /flagskipsdevenvand continues withcdb, thenlldb, thengdb.exon debug --helpis not a separate command-specific help entry today; use the top-levelexonusage text instead.
[package]
name = "my-app"
version = "0.1.0"
description = "A sample project"
authors = ["misut"]
license = "MIT"
type = "bin" # "bin" or "lib"
standard = 23
build-system = "exon" # "exon" (default) or "cmake"
platforms = [ # supported platforms (optional)
{ os = "linux" },
{ os = "macos", arch = "aarch64" },
{ os = "windows", arch = "x86_64" },
]
[dependencies]
"github.com/user/repo" = "0.1.0" # git
member = { git = "github.com/user/monorepo", version = "0.1.0", subdir = "member" }
[dependencies.find]
Threads = "Threads::Threads" # find_package()
[dependencies.path]
shared = "../shared" # local path
[dependencies.workspace]
core = true # workspace sibling
[dependencies.vcpkg]
fmt = "11.0.0" # vcpkg manifest
[dev-dependencies]
"github.com/user/testlib" = "0.1.0" # test-only
[defines]
MY_FLAG = "value"
[defines.debug]
DEBUG_MODE = "1"
[build] # raw compile/link flags
cxxflags = ["-Wall", "-Wextra"]
compiler-launcher = "ccache"
[build.debug]
cxxflags = ["-g", "-fsanitize=address"]
ldflags = ["-fsanitize=address"]
[sync]
cmake-in-root = true # generate portable CMakeLists.txtstandard accepts 11, 14, 17, 20, 23, or 26. The default remains
23, so existing import std; projects keep the current behavior. Projects
that set 11, 14, 17, or 20 build without enabling CMake's experimental
standard-library module support.
The build-system field in [package] declares who manages the package's build definition.
"exon"(default): exon scanssrc/for.cpp/.cppmfiles and generates cmake targets."cmake": the package provides its ownCMakeLists.txt. Exon skips source scanning and usesadd_subdirectory()directly. Useful for header-only wrappers or packages with custom cmake logic.
# header-only library with hand-written CMakeLists.txt
[package]
name = "metal-cpp"
type = "lib"
build-system = "cmake"The [sync] section controls what exon sync outputs.
cmake-in-root(defaulttrue): generate a portableCMakeLists.txtat the project root for IDE and raw cmake consumers. Set tofalsefor exon-only projects. The internal.exon/CMakeLists.txtis always generated regardless of this setting.
[sync]
cmake-in-root = false # exon-only project, no root CMakeLists.txt neededA workspace root declares members in [workspace] and may provide shared defaults for members:
[workspace]
members = ["core", "util", "app"]
[workspace.package]
version = "0.1.0"
authors = ["misut"]
license = "MIT"
standard = 23
build-system = "exon"
[workspace.build]
cxxflags = ["-Wall"]
[workspace.build.debug]
cxxflags = ["-fsanitize=address"]
ldflags = ["-fsanitize=address"][workspace.package] only fills missing member package fields; an explicit member value wins. [workspace.build], [workspace.build.debug], and [workspace.build.release] prepend shared flags to each selected member's own build flags. compiler-launcher is inherited only when the member/profile does not set its own value; a member-level launcher also blocks workspace profile launcher defaults.
Workspace roots are not runnable packages themselves. From the root:
exon build,exon check,exon test,exon sync,exon clean,exon outdated,exon update,exon tree, andexon whyaccept--member a,band--exclude x,yexon run --member <name>runs a member package- root builds use a unified graph under
.exon/workspace/<profile>(or.exon/workspace/<target>/<profile>for cross-target builds) - member execution order follows workspace dependency order, not the declaration order in
members = [...]
Use exon init --workspace to create the root, then exon new --lib <name> or exon new --bin <name> from the root to add members.
The [build] section forwards raw flags to the compiler and linker. Profile-specific subsections ([build.debug], [build.release]) merge on top of the base. Two environment variables append at the end so CI can inject flags without editing exon.toml:
EXON_CXXFLAGS="-fsanitize=address,undefined" exon test
EXON_LDFLAGS="-fsanitize=address,undefined" exon testCommon uses: sanitizers (-fsanitize=address), coverage (--coverage), warnings, profile-guided optimization. For [defines]-style preprocessor macros, prefer [defines] instead — exon escapes them per-target.
Set compiler-launcher to pass a CMake compiler launcher to configure:
[build]
compiler-launcher = "ccache"
[build.release]
compiler-launcher = "sccache"Exon forwards the active value as CMAKE_C_COMPILER_LAUNCHER,
CMAKE_CXX_COMPILER_LAUNCHER, CMAKE_OBJC_COMPILER_LAUNCHER, and
CMAKE_OBJCXX_COMPILER_LAUNCHER. This is intended for tools such as ccache
or sccache, including raw CMake dependencies fetched into the same CMake
configure. ccache does not currently cache standard C++20 module builds, so
the main win is for non-module C/C++/Objective-C++ sources such as large CMake
dependencies.
On Windows, declare ASan in [build] or [target.'cfg(os = "windows")'.build]:
[target.'cfg(os = "windows")'.build]
cxxflags = ["/fsanitize=address"]
ldflags = ["/fsanitize=address"]When these flags are present, exon now copies clang_rt.asan_dynamic-x86_64.dll next to each built executable and test binary. This makes direct execution from .exon/debug/ work without manually editing PATH.
exon build and exon test default to human output. In an interactive terminal, human shows small accented section headers, indexed stages such as [1/5] resolve, fixed-width status cells such as OK, FAIL, and TIMEOUT, and a live progress renderer whose active phase label shimmers from left to right with a bright-white highlight during long build or test phases. Determinate build/test work also gets a capability-aware progress bar: Unicode terminals use a smoother spinner and shaded bar, while non-Unicode sessions keep the ASCII fallback. During configure and build, the renderer refreshes the latest CMake/Ninja output lines in place with dim styling so long builds do not look idle without making preview output compete with the active phase. Long progress and output lines are clipped to the terminal width and include elapsed time, ETA, and rate when enough progress data is available. The same path uses restrained ANSI styling for status and error: diagnostics. When stdout is not a TTY, human falls back to the same plain ASCII, stage-oriented summaries. wrapped adds the same command framing while still showing the underlying CMake/Ninja/test output, and raw keeps exon wrapping to a minimum.
Use --output json for JSON Lines events (stage, diagnostic, artifact, test-result, and summary) that are stable for tools and CI log processors.
Terminal capabilities can be controlled with --color auto|always|never, --progress auto|always|never, --unicode auto|always|never, and --hyperlinks auto|always|never. The matching environment variables are EXON_COLOR, EXON_PROGRESS, EXON_UNICODE, and EXON_HYPERLINKS. Set NO_COLOR=1 to force plain color-free output, or FORCE_COLOR=1 to enable color in auto mode unless NO_COLOR is also set. Use EXON_PROGRESS=never exon build to disable animation without changing human summaries.
On GitHub Actions, exon disables animation and emits workflow annotations/groups for failures so the CI log stays readable.
Workspace builds keep the active member inline in each indexed stage header, for example [4/5] [hello (apps/hello)] build. Workspace tests use the same status-cell style for member dividers, for example RUN member hello (apps/hello).
exon test --show-output failed (default) only shows captured stdout/stderr for failing or timed-out test binaries. Use --show-output all to always print captured output, or --show-output none to suppress it entirely.
exon test --timeout <sec> is also available for long-running or hung tests. On Windows, timeout uses a native process runner that terminates the full child process tree instead of leaving stale cmake, ninja, or test processes behind.
exon status (or exon doctor) gives a TUI-lite snapshot of the current package/workspace, lockfile, build cache, intron status toolchain diagnostics, exon's detected tools, and terminal capability policy. Human output starts with a compact readiness summary, then uses capability-aware section accents to make intron, toolchain, diagnostics, and terminal details easier to scan. It calls out missing tools, untrusted mise.toml files, environment/PATH issues, and stale current-source binaries with concrete rerun commands. Use exon status --output json when another tool needs the same information; the status event keeps the older flattened fields and adds structured intron and toolchain objects.
exon dist --release builds the current binary package and creates the same archive shape used by exon releases: <name>-v<version>-<platform>.tar.gz on macOS/Linux and <name>-v<version>-<platform>.zip on Windows. The archive payload is the executable at the archive root (exon or exon.exe), so consumers that already unpack release assets can consume exon dist output unchanged.
Use --version <v> when packaging from a Git tag or CI variable, and --output-dir <dir> to place artifacts outside the project root:
exon dist --release --version v0.31.0 --output-dir distexon commands --output json emits the command registry used by the CLI completion path. Each command includes category, summary, positional metadata, examples, and option metadata such as value names and known value hints.
exon complete prints candidates for a partial invocation. Pass words after --; the final word is treated as the current prefix:
exon complete -- build --ou
exon complete --output raw -- build --output jShell completion scripts can be generated directly:
exon completion bash
exon completion zsh
exon completion fishIf a fresh Windows native configure fails with a CMake modules error such as:
compiler does not provide a way to discover the import graph dependencies
check which compiler your current shell exported before assuming the repository is misconfigured.
Invoke-Expression ((intron env) -join "`n")
Write-Host "CC=$env:CC"
Write-Host "CXX=$env:CXX"
where.exe cl.exe
where.exe clang-cl.exeThis often explains why local Windows and GitHub Actions Windows CI differ:
- the repository or CI may expect the MSVC
cl.exepath - your local
intron envmay have selectedclang-cl.exeinstead - that mixed setup can surface CMake C++ modules or
import stderrors that do not appear in acl.exe-based CI job
If the project expects MSVC, rerun with cl selected explicitly:
$env:CC = "cl"
$env:CXX = "cl"
exon buildIf clang-cl is intentional, verify that your current CMake and toolchain combination supports Windows C++ modules for the project you are building. With intron 0.19.1, Windows shells can still end up with an MSVC developer environment plus CC / CXX set to clang-cl.exe; newer intron releases prefer cl.exe when Windows MSVC is configured.
Exon supports five kinds of dependencies, all with [dev-dependencies.*] variants that are only pulled in for exon test.
Fetched from GitHub (or any git remote) and built from source.
[dependencies]
"github.com/misut/tomlcpp" = "0.2.0"The version is a Cargo-style requirement. A bare version such as 0.2.0 means ^0.2.0; use =0.2.0 for an exact pin. Exon selects the newest compatible stable semver tag from either v0.2.0 or 0.2.0, then records the selected version and commit in exon.lock. The short name (tomlcpp) becomes the CMake target.
exon add github.com/misut/tomlcpp 0.2.0
exon add --dev github.com/user/testlib 0.1.0
exon add github.com/user/codec 1.0.0 --features json --no-default-featuresGit dependencies may declare [features] in their own exon.toml. Each
feature expands to module basenames or to other feature names. When a provider
declares [features], exon includes only default plus the consumer-selected
features; default-features = false disables the provider default. If a
provider has no [features] table, all dependency modules are included. The
selected feature set is recorded in exon.lock.
[dependencies]
"github.com/user/codec" = { version = "1.0.0", default-features = false, features = ["json"] }Use exon outdated to compare locked git dependencies with remote semver tags. Path, workspace, find, vcpkg, and raw CMake dependencies are reported as skipped because they do not resolve through git tags.
exon outdated
exon outdated github.com/misut/tomlcpp --output jsonUse exon update to refresh exon.lock without rebuilding. With no package argument it updates every git dependency to the newest compatible tag. With a package argument it performs a conservative update for that dependency and only changes transitive dependencies when the resolved graph requires it. --dry-run prints the lockfile changes without writing exon.lock; --precise <version> locks one package to a specific version that still satisfies the manifest requirement.
exon update
exon update github.com/misut/tomlcpp --dry-run
exon update github.com/misut/tomlcpp --precise 0.3.1Use exon tree to inspect the resolved dependency graph. Repeated packages are
deduplicated by default and marked with (*); pass --features to include the
selected git dependency features in the human output. exon why <pkg> prints
the root-to-package path that explains why a dependency is present.
exon tree --features
exon why tomlcpp
exon tree --member hello --output jsonDepending on a package inside a remote monorepo: use inline-table syntax with subdir. The TOML key becomes the CMake target name.
[dependencies]
refl = { git = "github.com/misut/txn", version = "0.1.0", subdir = "refl" }
txn = { git = "github.com/misut/txn", version = "0.2.0", subdir = "txn" }exon add --git github.com/misut/txn --version 0.1.0 --subdir reflExon clones the repo at tag v{version}, then uses <repo>/<subdir>/CMakeLists.txt (typically generated by exon sync on the upstream side) as the dep's build definition. Multiple subdirs sharing a (repo, version) share one clone. The TOML key must match the CMake target produced by the subdir's CMakeLists (for sync-managed repos, this is the member's package.name).
Link against system-installed packages or anything find_package() can locate (conan/vcpkg-installed packages work too). The map key is the find_package name; the value is the imported target(s) to link. Space-separated values link multiple targets.
[dependencies.find]
Threads = "Threads::Threads"
ZLIB = "ZLIB::ZLIB"
fmt = "fmt::fmt fmt::fmt-header-only"
[dev-dependencies.find]
GTest = "GTest::gtest_main"Generates:
find_package(Threads REQUIRED)
find_package(ZLIB REQUIRED)
find_package(fmt REQUIRED)
target_link_libraries(my-app-modules PUBLIC
Threads::Threads
ZLIB::ZLIB
fmt::fmt
fmt::fmt-header-only
)Use [dependencies.cmake] for upstream projects that already publish CMake
targets but do not have an exon.toml. The default mode is fetch, which
keeps the historical behavior: CMake fetches the project with FetchContent
and builds it inside the main build tree.
[dependencies.cmake.glfw]
git = "https://github.com/glfw/glfw.git"
tag = "3.4"
targets = "glfw"
shallow = false
[dependencies.cmake.glfw.options]
GLFW_BUILD_DOCS = "OFF"
GLFW_BUILD_TESTS = "OFF"
GLFW_BUILD_EXAMPLES = "OFF"exon add --cmake glfw \
--repo https://github.com/glfw/glfw.git \
--tag 3.4 \
--targets glfw \
--option GLFW_BUILD_DOCS=OFF \
--option GLFW_BUILD_TESTS=OFF \
--option GLFW_BUILD_EXAMPLES=OFF \
--shallow falsetargets is the whitespace-separated list of CMake targets to link. tag may
be a tag, branch, or commit; a commit hash is the most reproducible choice for
clean builds because branch and tag names can move in remote repositories.
Large CMake dependencies can declare install-cache metadata. Exon can then
materialize the dependency once into an install prefix under
EXON_CACHE_DIR/cmake-install (or ~/.exon/cache/cmake-install) and consume it
with config-mode find_package(). This avoids rebuilding the dependency every
time another project configures against the same dependency declaration,
profile, target, and toolchain.
[dependencies.cmake.dawn]
git = "https://github.com/google/dawn.git"
tag = "v20260423.175430"
targets = "webgpu_dawn"
[dependencies.cmake.dawn.install]
package = "Dawn"
targets = "dawn::webgpu_dawn"
[dependencies.cmake.dawn.options]
DAWN_BUILD_SAMPLES = "OFF"
DAWN_BUILD_TESTS = "OFF"
DAWN_FETCH_DEPENDENCIES = "ON"
[dependencies.cmake.dawn.install.options]
DAWN_ENABLE_INSTALL = "ON"exon add --cmake dawn \
--repo https://github.com/google/dawn.git \
--tag v20260423.175430 \
--targets webgpu_dawn \
--install-package Dawn \
--install-targets dawn::webgpu_dawn \
--option DAWN_BUILD_SAMPLES=OFF \
--option DAWN_BUILD_TESTS=OFF \
--option DAWN_FETCH_DEPENDENCIES=ON \
--install-option DAWN_ENABLE_INSTALL=ON[dependencies.cmake.<name>.install] describes how the dependency is consumed
after installation. package is the find_package() name and defaults to the
dependency key when omitted; targets defaults to the fetch-mode targets.
Install-cache options overlay the base options only for the materialization
build.
mode = "install" is still supported as a manifest-level default for existing
projects. New projects can keep the source-controlled manifest as capability
metadata and choose the cache policy locally:
# exon.local.toml (ignored by default)
[cmake-install-cache]
mode = "prefer" # auto | off | prefer | require
[cmake-install-cache.dependencies]
dawn = "require"The same global policy can be set with EXON_CMAKE_INSTALL_CACHE=prefer.
auto honors legacy mode = "install" only, off forces FetchContent,
prefer uses install metadata when present, and require fails if a raw CMake
dependency has no install metadata. Install mode requires the upstream project
to provide install rules and a CMake package config file, such as
DawnConfig.cmake, under the install prefix. Delete the corresponding
directory under EXON_CACHE_DIR/cmake-install to force a rebuild. The root
CMakeLists.txt generated by exon sync remains portable and does not embed
machine-local install cache paths.
Depend on any local directory containing an exon.toml. Paths are relative to the declaring manifest.
[dependencies.path]
shared = "../shared"
helpers = "../../vendor/helpers"exon add --path shared ../shared
exon add --dev --path testlib ../testlibTransitive path deps are resolved relative to the dep's own directory, so nested monorepos compose naturally. Path deps are not written to exon.lock (no version to pin).
Reference a sibling package in the same workspace by its package.name. Exon walks up from the current directory to find a workspace root (an exon.toml with [workspace]) and resolves the name against member manifests.
[dependencies.workspace]
core = true
utils = trueexon add --workspace coreSee docs/workspace.md for a full monorepo walkthrough, or open
examples/workspace/ for a checked-in runnable workspace that
demonstrates shared defaults, workspace member dependencies, and root
exon run --member <name>.
Install packages through vcpkg in manifest mode. Exon generates .exon/vcpkg.json and passes CMAKE_TOOLCHAIN_FILE to CMake, which installs packages automatically at configure time.
[dependencies.vcpkg]
fmt = "11.0.0" # pinned via version>=
zlib = "*" # baseline version
boost-asio = { version = "*", features = ["ssl"] } # select vcpkg features
opencv = { features = ["contrib", "cuda"] } # version omitted = "*"
[dependencies.find]
fmt = "fmt::fmt" # link target (required)
ZLIB = "ZLIB::ZLIB"
[dev-dependencies.vcpkg]
gtest = "*"
[dev-dependencies.find]
GTest = "GTest::gtest_main"exon add --vcpkg fmt 11.0.0
exon add --vcpkg boost-asio '*' --features ssl
exon add --dev --vcpkg gtest '*'Install ([dependencies.vcpkg]) and link ([dependencies.find]) are separate because vcpkg package names and CMake find_package names often differ (vcpkg zlib ↔ find_package(ZLIB)). Use inline table form ({ version = "...", features = [...] }) to enable optional vcpkg features. Exon requires VCPKG_ROOT or a standard install path (e.g. /opt/vcpkg, ~/vcpkg, GitHub Actions VCPKG_INSTALLATION_ROOT); if vcpkg cannot be located, the build fails with a clear error.
Declare which platforms your package supports with platforms in [package]. Each entry is an inline table with os and/or arch fields; omitting a field acts as a wildcard.
[package]
name = "mylib"
version = "1.0.0"
platforms = [
{ os = "linux" }, # all Linux architectures
{ os = "macos", arch = "aarch64" }, # macOS ARM64 only
{ os = "windows", arch = "x86_64" }, # Windows x64 only
]Known values: os = linux, macos, windows, wasi, android; arch =
x86_64, aarch64, wasm32.
If the host platform does not match any entry, exon build/run/test/check fail early with a clear error. Omitting platforms entirely means the package supports all platforms (backward compatible with existing manifests).
Use [target.'cfg(...)'] sections to declare dependencies or defines that only apply on certain platforms. The predicate syntax supports os, arch, comma-separated AND, and not().
# Linux-only: link io_uring
[target.'cfg(os = "linux")'.dependencies.find]
LibUring = "LibUring::LibUring"
# Windows-only: vcpkg package
[target.'cfg(os = "windows")'.dependencies.vcpkg]
wil = "*"
# All except Windows
[target.'cfg(not(os = "windows"))'.dependencies.vcpkg]
libuv = "*"
# Platform-specific defines
[target.'cfg(os = "linux")'.defines]
IO_BACKEND = "io_uring"
[target.'cfg(os = "macos")'.defines]
IO_BACKEND = "kqueue"All dependency subsections (find, vcpkg, path, workspace, inline-table git), defines / defines.debug / defines.release, and build / build.debug / build.release are supported inside [target.'cfg(...)']. Non-matching sections are skipped entirely — their dependencies are not fetched.
Build for WebAssembly using --target wasm32-wasi. Requires wasi-sdk via intron or WASI_SDK_PATH.
# install wasi-sdk
intron install wasi-sdk 32
intron default wasi-sdk 32
# build
exon build --target wasm32-wasi
# run (requires wasmtime on PATH)
exon run --target wasm32-wasiOutput is placed in .exon/wasm32-wasi/{debug,release}/.
Limitations:
import std;is not available (#includeindividual headers instead). User-defined.cppmmodules work normally.- C++ exceptions are disabled (
-fno-exceptions). vcpkgandfind_packagedependencies are not supported for WASM targets.
// WASM-compatible source (use #include, not import std)
#include <print>
int main() {
std::println("hello, wasm!");
return 0;
}Build Android arm64 artifacts with --target aarch64-linux-android. Exon uses
the Android NDK CMake toolchain, keeps the default API level at 33, and writes
output under .exon/aarch64-linux-android/{debug,release}/.
# install Android NDK
intron install android-ndk <version>
# build
exon build --target aarch64-linux-android
# tests are compiled only; run the artifact on a device or emulator
exon test --target aarch64-linux-androidexon run --target aarch64-linux-android fails early because host execution is
not meaningful for Android binaries. Use exon build or exon test to verify
the build, then deploy the produced artifact with Android tooling.
- C++11-C++26 projects —
standard = 11,14,17,20,23, or26, with the default kept at C++23 - C++23
import std;— automatically detected whenstandard >= 23and clang with libc++ modules is available - C++20 modules —
.cppmfiles insrc/are handled as module sources - Transitive dependencies — recursive resolution with cycle detection
- Lock file —
exon.lockfor reproducible builds (git deps only) - Incremental builds — CMake configuration is cached and skipped when unchanged; large raw CMake dependencies can also use install-prefix caching
- Build profiles — debug (default) and release (
--release, statically links libc++ for portable binaries) - Distribution archives —
exon dist --releasecreates release-compatible tar/zip artifacts - Dev dependencies —
[dev-dependencies.*]for test-only packages, excluded from builds - Five dependency kinds — git, find_package, local path, workspace sibling, vcpkg; raw CMake dependencies support fetch and install-cache modes
- Workspaces — monorepos with
[workspace] members = [...], shared[workspace.package]/[workspace.build.*]defaults, dependency-ordered root commands, and unified workspace builds - Compile definitions — built-in (
EXON_PKG_NAME,EXON_PKG_VERSION) and user-defined via[defines] - CMakeLists.txt sync —
exon syncgenerates a portable CMakeLists.txt for plain cmake builds (opt-out with[sync] cmake-in-root = false) - Custom cmake packages —
build-system = "cmake"delegates to a hand-written CMakeLists.txt instead of scanningsrc/ - Syntax check —
exon checkcompiles modules without linking for fast feedback - Self-hosting — exon builds itself with
exon build - Cross-platform — macOS (ARM64), Linux (x86_64, aarch64), and Windows (x86_64, MSVC)
- WebAssembly —
--target wasm32-wasicross-compiles to WASM via wasi-sdk - Android —
--target aarch64-linux-androidcross-compiles arm64 Android artifacts via Android NDK - Platform targeting —
platforms = [{ os = "linux" }, ...]declares supported platforms; build fails early on unsupported hosts
MIT