Building candela¶
How to get the repository building, what is in it, and which gates to run before you open a pull request.
What you need¶
- A stable Rust toolchain. The crates are on edition 2024, so Rust 1.85 or newer. Nothing is pinned; CI builds on current stable.
- A C toolchain. The FFI dependencies build C as part of their own build, and the standard library's native modules are C.
clang, if you want to build those native modules yourself.
Two more are needed only for specific jobs: cargo-pgo and the LLVM tools for a
profile-guided release build, and WiX for the Windows installer.
Layout¶
The repository is a Cargo workspace of three crates:
- The root package,
candela-lang. The lexer, parser, type checker, code generator, REPL, theEngine/Programembedding API, and thecandelabinary. The package name carries the-langsuffix only because crates.io gavecandelato an unrelated project years ago; the library it builds iscandela, so everyuse candela::...reads the same as it always did. vm/, thecandela-vmcrate. The self-contained runtime: the executor, the bytecode and value representation, the garbage collector, host and C value marshalling, and the.cdlbartifact format. It builds both a library and thecandela-vmbinary.lsp/, thecandela-lspcrate. The language server, which reuses this repository's lexer, parser and type checker rather than reimplementing them.
Dependencies run one way only: candela depends on candela-vm, and
candela-lsp depends on candela. Nothing goes the other way, which is what
keeps the runtime free of the compiler. Both binaries link the same VM, so the
executor exists once. A reverse edge would be a dependency cycle and would fail
to resolve, so the rule enforces itself; keep it in mind when deciding where new
code belongs.
The rest of the tree:
libs/std/holds the standard library, written in candela.libs/std/tests/holds one.cdltest program per module.libs/std_src/holds the C sources behind themath,randomandtimemodules.examples/holds demo and benchmark programs, most of them alongside Python and Lua versions of the same thing. They double as the training corpus for profile-guided builds.examples/package/is a project rather than a loose file, and it is what the package-workflow self-test packs.tests/holds the Rust integration suites.pgo/holds the workloads and the small C library used to train a profile-guided release build.msi/holds the WiX package definition and the script that builds the Windows installer.scripts/holds the release tooling:bump-version.py, which sets the version the tree calls itself, and the checklist for cutting a release.editors/vscode/holds the VS Code extension: the language server client, the TextMate grammar, and snippets.editors/jetbrains/holds the plugin for the IntelliJ-based IDEs. It is a Gradle build rather than a Cargo one, and it reads its grammar out ofeditors/vscode/so there is one TextMate grammar to fix.editors/tree-sitter/holds the tree-sitter grammar, its highlighting queries, and the Neovim and Helix configuration that installs them. The generated parser is committed, so an editor needs no tree-sitter CLI.editors/zed/holds the Zed extension, which builds the tree-sitter grammar from this repository and carries its own copy of the queries.docs/holds this documentation site.
Building¶
cargo build at the root builds the candela-lang package only. That is deliberate;
the workspace sets its default member to the root so the common case stays
quick. Name the others explicitly:
Two feature flags matter:
compileris on by default and gates everything the front end needs. It exists so the language server and the binary opt into their allocator and lexer dependencies rather than every consumer getting them.embedmakes a fatal error unwind instead of ending the process, for building candela into a host program. See embedding.
There is also a WebAssembly target, which drops the FFI dependencies:
Profiles¶
devis a lightly optimised debug profile with debug information off. Debug builds here are not stock Cargo debug builds.releaseis the shipping profile: full optimisation, fat link-time optimisation, one code generation unit, stripped, and unwinding on panic. The unwinding is required. The compiler turns an error into a diagnostic by catching an unwind,candela buildandcandela-lspboth depend on that, and a profile that aborts instead kills the process on the first error.debugreleaseisreleasewith debug information kept and nothing stripped. Use it to profile or debug optimised code.embedisreleaseunder a name of its own, so the library artifact built with theembedfeature keeps its own output directory.
Tests¶
runs the root package's suites:
- The in-crate tests, which compile a snippet and run it through the VM, then
assert on the resulting state or on what it printed. Run them alone with
cargo test --lib. tests/cdlb_roundtrip.rs, covering the artifact format: the header, the instruction and constant tables, the type tables, the dynamic-library recipe round trip, the unbound host function, and a full comparison ofcandelaoutput againstcandela-vmrunning the artifact.tests/embedding.rs, covering theEngine/ProgramAPI: registering host functions, calling script functions, marshalling values both ways, state persistence, and the diagnostics returned on failure.tests/vm_embedding.rs, covering the same ground with the compiler absent: binding an artifact's host functions to aHostRegistryat load, and calling its exports by name.tests/imports.rs, covering module binding rules, by writing multi-file programs to a scratch directory and running the built binary against them.tests/std_library.rs, which runs each.cdlprogram inlibs/std/tests/through the binary and checks it exits cleanly. This is the standard library's test suite.
The other two crates are not reached by a root cargo test:
The language server's suite spawns the built binary and drives it over its protocol, so build it first.
Two tests skip rather than fail when their prerequisite is absent: the dynamic
library round trip, which needs a system zlib it can open, and the artifact
comparison, which needs the candela-vm binary built. Build
-p candela-vm --bin candela-vm before cargo test if you want that one to
run, and pass -- --nocapture to see whether anything skipped.
CANDELA_LIB_PATH points the compiler at a standard library directory. The
suites set it to this checkout's libs, because a test binary is not laid out
like an install, and deliberately clear it in the tests that prove resolution
works with no configuration.
Coverage comes from the same suites, measured with cargo llvm-cov --workspace
on every push to main and every pull request and reported to Codecov. It is a
report on the whole tree and a gate on the change: the project-wide Codecov
status is informational, while the patch status is a required check that fails
when the diff drops meaningfully below the baseline coverage. Doctests are outside the measurement, since collecting coverage
from them needs a nightly toolchain.
There is no fuzzing setup. Benchmarking is manual; BENCHMARKS.md describes it.
Before you open a pull request¶
cargo fmt --all
cargo clippy --workspace --all-targets
cargo test --workspace
cargo test --features embed
cargo build --target wasm32-unknown-unknown
Clippy's pedantic and nursery groups are on as warnings for the candela
package. Do not add new ones. If a lint is wrong for your case, allow it at the
narrowest scope with a comment saying why.
Continuous integration builds and tests the whole workspace on Linux, macOS and
Windows, adds a Linux run with the embed feature and a WebAssembly build, and
gates on cargo fmt --all --check and on clippy with warnings denied. A
separate job exercises the Windows installer end to end when the installer or
the update logic changes.
The crate documentation is built from the sources with
and the same build is published on every push to main, serving the API
documentation for the current tree at
api.candela.lumenfx.dev. That is the Rust
API, separate from this site.
Two policies from CONTRIBUTING.md are worth repeating. A change to the
language, the standard library or the command line updates the matching
documentation page in the same pull request. A change in behaviour comes with a
test: in tests/ for the compiler, artifacts, imports or embedding, and in
libs/std/tests/ for the standard library. Run a program through both candela
and candela-vm and confirm they agree.