24Project Configuration (cryoconfig)
A project is a directory containing a cryoconfig file. cryo build, cryo run, and cryo test search upward from the working directory for it, then build the project it describes. Scaffold a starter file with cryo init.
cryoconfig is an INI-like file: [section] headers, key = value lines, # comments. List values use TOML-style arrays (["a", "b"]). Unknown keys are ignored with a warning; keys removed in 1.0 are a hard error that names the replacement.
24.1 [project]
Project identity and source layout.
[project]
project_name = "my-app" # display name
target_type = "executable" # executable | library | stdlib
entry_point = "src/main.cryo" # main file (executables only)
source_dir = "src" # source root (libraries / stdlib)
output_dir = "build" # where build artifacts are written
source_paths = ["../shared/src"] # extra source roots to scan for modules
target_triple = "" # cross-compile triple; empty => build for host
stdlib_root = "" # project-pinned stdlib root (see section 24.3)
target_triple (e.g. "x86_64-pc-windows-gnu") cross-compiles: it is threaded into LLVM and selects the target ABI and toolchain. For x86_64-pc-windows-gnu the build drives a full mingw-w64 link to a .exe; for triples without a known toolchain (aarch64, riscv, windows-msvc, ...) the host link step is skipped and the object files are left for a manual link. The CLI --target=TRIPLE overrides it.
24.2 [compiler]
Code-generation knobs.
[compiler]
debug = false # verbose compiler logging
optimize = "O2" # O0 | O1 | O2 | O3 (default O2)
emit_llvm = false # also write LLVM IR (.ll) beside the object
no_std = false # build without linking the standard library
no_runtime = false # freestanding: no crt0 / libc / panic runtime
no_std vs no_runtime. These are orthogonal knobs, each also settable
per-build with --no-std / --no-runtime (the flag can force the option on,
never off):
no_std | no_runtime | Result |
|---|---|---|
false | false | Default hosted build: prelude + stdlib linked, crt0 + libc, the @panic runtime. |
true | false | No prelude/stdlib, but still hosted: crt0, libc, and the @panic runtime remain. |
false | true | Freestanding link (no crt0/libc/libcryo.a) but the stdlib source is still in scope — rarely useful. |
true | true | Fully freestanding: no stdlib, no crt0/libc, main is not widened, and check failures diverge through llvm.trap instead of the libc-backed @panic. This is how a runtime/core tier is built. |
no_runtime = true normally implies no_std = true: a freestanding link drops
libcryo.a, so any stdlib symbol the program references would fail to resolve.
A freestanding program supplies its own entry point (an ![naked] _start);
without one the linker warns that _start is missing.
![config(no_runtime)] / ![config(not(no_runtime))] gate a declaration on the
freestanding build flag, exactly like the OS atoms (![config(linux)], …).
Build profiles. A build runs under a named profile that supplies a default optimization level and debug-info setting and names the per-profile cache subtree. Two are built in:
| Profile | Optimization | Debug info |
|---|---|---|
release (default) | O2 | off |
debug | O0 | DWARF (-g) |
Set the default in cryoconfig, or pick one per build with --release / --dev
(--dev = the debug profile) / --profile=NAME:
[profile]
default = "release" # release | debug
An explicit [compiler] optimize overrides the profile's level; --opt-level=N
overrides everything; -g forces debug info on regardless of profile.
Build directory layout. The final artifact is hoisted to the root of
output_dir so it runs as build/<name> regardless of profile; everything
else lives under a visible, per-profile cache (target/<profile>/) grouped by
package origin - the standard library (std/), the local project
(local/), and one subtree per third-party dependency (<depname>/):
build/
+-- <name> # hoisted final executable (cryo run / [[bin]])
+-- lib<name>.a # hoisted final library ([lib] target)
+-- target/
+-- <profile>/ # release | debug
+-- <name> # per-profile build (the hoist source)
+-- <name>.ll # combined LLVM IR (when emit_llvm)
+-- build-manifest.json # per-profile metadata + fingerprint
+-- std/ # standard library package
| +-- deps/ *.o # per-module objects
| +-- ir/ *.ll # per-module IR (when emit_llvm)
+-- local/ # the local project package
| +-- deps/ *.o
| +-- ir/ *.ll
| +-- incremental/ # rebuild fingerprint
+-- <depname>/ # one subtree per dependency
+-- deps/ *.o
+-- ir/ *.ll
cryo build is incremental: if no input changed (sources, the resolved knobs,
the compiler binary, or the linked stdlib) and the artifact still exists, the
build is skipped (<name> is up to date). Pass --no-incremental to force a
full rebuild and refresh the manifest.
24.3 [link]
Native libraries to link, named by intent rather than by raw linker flag - so a project never has to juggle two similar lists.
[link]
system = ["ssl", "crypto"] # system libraries -> -l<name>
search = ["/usr/lib/llvm-20/lib"] # extra -L dirs to resolve `system` libs
static = ["vendor/libhelpers.a"] # local archives, passed to the linker by path
| Key | Role | Linker form |
|---|---|---|
system | A library the linker finds on its default search path. | -l<name> |
search | Extra directories to search - only needed for system libs that live off the default path. | -L<dir> |
static | A local archive in your project; linked by its path. | the path, verbatim |
Per-OS overlays [link.unix] and [link.windows] carry the same three keys. Bare [link] is common (applied to every target); the overlay whose OS matches the effective target triple is appended on top, so a project can name a library differently per platform - or link a platform-specific archive - without a cross-compile dragging in the other OS's libs:
[link]
system = ["ssl", "crypto"] # linked everywhere
[link.unix]
static = ["helpers/libhelpers-unix.a"]
[link.windows]
static = ["helpers/libhelpers-windows.a"]
system = ["ws2_32"] # winsock, windows targets only
Migrating from pre-1.0:
link_libs->[link] system,link_paths->[link] search(or[link] staticfor a local archive). The old[compiler] args = ["--emit-llvm"]flag-smuggling is gone - setemit_llvm = true.[compiler] include_paths->[project] source_paths.[project] target->[project] target_triple.
24.4 Multi-target: [lib] and [[bin]]
A project can build a library and one or more executables from one source tree. [lib] declares the library; each [[bin]] (array-of-tables) declares an executable. When present these take precedence over the single-target [project] target_type / entry_point.
[lib]
name = "mylib"
source_dir = "src"
[[bin]]
name = "mytool"
entry_point = "src/main.cryo"
24.5 [dependencies]
Each entry is an inline table - a local path dependency or a git dependency. cryo fetch resolves them and writes cryoconfig.lock.
[dependencies]
mylib = { path = "../mylib", alias = "MyLib" }
remote = { git = "https://example.com/lib.git", tag = "v0.1.0", alias = "Remote" }
A git dependency requires exactly one of version, tag, branch, or rev, and may set subdir (the path to the dependency's cryoconfig inside the repo). alias is the top-level namespace the consumer imports.
24.6 [test]
Defaults for the test runner; forwarded to the spawned test binary unless overridden on the CLI.
[test]
format = "pretty" # plain | pretty | compact
color = "auto" # auto | always | never