Skip to content
CryoCryo home
CompilerToolchain

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_stdno_runtimeResult
falsefalseDefault hosted build: prelude + stdlib linked, crt0 + libc, the @panic runtime.
truefalseNo prelude/stdlib, but still hosted: crt0, libc, and the @panic runtime remain.
falsetrueFreestanding link (no crt0/libc/libcryo.a) but the stdlib source is still in scope — rarely useful.
truetrueFully 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:

ProfileOptimizationDebug info
release (default)O2off
debugO0DWARF (-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.

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
KeyRoleLinker form
systemA library the linker finds on its default search path.-l<name>
searchExtra directories to search - only needed for system libs that live off the default path.-L<dir>
staticA 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] static for a local archive). The old [compiler] args = ["--emit-llvm"] flag-smuggling is gone - set emit_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