radlang — Project config (cfg)
cfg is a compiler-injected namespace that exposes your project’s config.yaml to the program. It needs no import — the compiler fills it
in from the config at the project root and bakes the values into the binary.
Its purpose is to keep two different notions of “environment” cleanly apart:
cfg.env(key)— project configuration: theconfig.yamlenv:block (baked into the binary at compile time) overlaid at runtime by a.radenvfile. The.radenvlayer is not baked — it is read when the program runs, so it is the right home for secrets.sys.env(key)— the real OS environment at runtime, nothing else (seesys.md).
The two are fully separate: cfg.env reads only your project config (never the
OS), and sys.env reads only the OS (never your config). The baked config.yaml defaults travel inside the binary, so a standalone build binary still runs with
its configured defaults even if no .radenv is present. To let the OS override a
config value, combine them explicitly: sys.env("PORT") or cfg.env("PORT").
Members
Project dependency roots are configured under paths.include in config.yaml:
paths:
include:
- "../shared-radlang"
- "../common" Each included project contributes its lib/*.rlc files and its src/namespace/*.ns.rad sources. The compiler, rtest, and the LSP use the
same ordered search path. Paths are resolved relative to config.yaml; earlier
entries take precedence, and duplicate roots are canonicalized.
All members read as strings.
cfg.name // project.name from config.yaml ("" if unset)
cfg.version // project.version from config.yaml ("" if unset)
cfg.profile // "release" for `radlang build`, "debug" for `radlang run`
cfg.buildDir // paths.build (default "output")
cfg.srcDir // paths.src (default "src")
cfg.staticDir // paths.static (default "public"; see server.useStatic)
cfg.libDir // paths.lib (resolved path, "" if disabled)
cfg.testsDir // paths.tests ("" if unset)
cfg.namespaceDir // paths.namespace (default "namespace") cfg.env
cfg.env(key: string) : string Returns the configured value for key, or the empty string if there is no such
key. Resolution is project-config only:
- The runtime
.radenvvalue, if a.radenvprovides that key (see below). - Otherwise the value baked from
config.yaml’senv:block. - Otherwise
"".
It does not read the OS environment — that’s sys.env’s job. When you want
a deploy-time override, ask for it explicitly:
fn main() {
string port = sys.env("PORT") // OS override, if set
if port == "" { port = cfg.env("PORT") } // else the config default
HttpServer server = httpserver.serve(Int(port))
sys.output("{cfg.name} v{cfg.version} on port {port}")
server.listen()
} Because the config keys are known at compile time, a literal key that isn’t
in your config env is caught as a compiler warning (not an error, so config
and code can be edited in either order) — cfg.env("PROT") when only PORT exists warns rather than silently returning "". Keys computed at runtime (a
variable) are not checked.
config.yaml
project:
name: "radlang_api"
version: "1.0.0"
cores: 4 # optional: HTTP prefork worker count (omit = CPU count)
paths:
build: "output"
lib: "lib"
tests: "src/test"
namespace: "src/namespace"
env:
PORT: 8085
MODE: "dev" project.cores sets how many worker processes an HttpServer.listen() preforks across CPU cores. It is baked into the compiled binary. If omitted (or
0) the server uses the machine’s online CPU count; cores: 1 disables prefork
(classic single process). The RAD_HTTP_WORKERS environment variable overrides
it at runtime for a given launch. Values are capped at 256.
Per-profile values
An env: key may hold per-build-profile values instead of a single one, so cfg.env returns something different for radlang build (release) vs radlang run (debug) — resolved at compile time, still baked into the binary:
env:
PORT: 8085 # plain: same for every profile
DB:
debug: "sqlite://dev.db"
release: "mysql://prod-host/app"
API_BASE:
release: "https://api.example.com"
default: "http://localhost:9000" # used by any profile without its own entry
LOG:
debug: "verbose" # release has no LOG → cfg.env("LOG") is "" Resolution for the active profile: its own variant → default: if present →
otherwise the key is omitted and cfg.env(key) returns "". A key defined
this way still counts as declared, so cfg.env("LOG") never triggers the
unknown-key warning even when it resolves to "". .radenv keys override the
whole config key regardless of profile.
.radenv — runtime secrets
A .radenv file supplies environment values at run time — its contents are not baked into the binary, so it is the right place for secrets (passwords,
API keys, tokens). Its keys override the baked config.yaml env: values.
# .radenv — runtime secrets, gitignored
DB_PASSWORD=s3cr3t
API_TOKEN=abc123
MODE=local Because .radenv is read at run time, secrets never enter the compiled artifact
— strings on the binary cannot recover them. The baked config.yaml env: block stays inside the binary as the default; .radenv overlays it. So the
split is:
| Put it in… | When | Baked into binary? |
|---|---|---|
config.yaml env: | Non-secret defaults (ports, modes, base URLs) | Yes (compile time) |
.radenv | Secrets and per-deploy overrides | No (read at run time) |
Keep .radenv out of version control (the api template’s .gitignore already
ignores it).
Discovery. At the first cfg.env call the program loads one .radenv, in
this order: the path in $RADLANG_ENV_FILE if set, else ./.radenv in the
working directory, else .radenv next to the executable. Format: KEY=value per line, # comments, blank lines ignored; one pair of surrounding double
quotes on a value is stripped (K="a b" → a b).
The OS environment is never consulted by cfg.env — read it separately via sys.env. (sys.env remains the option for secrets you’d rather not place in a
file at all, e.g. injected by a container orchestrator.)
cfg vs sys.env
| Need | Use |
|---|---|
A value declared in config.yaml / .radenv | cfg.env("KEY") |
| A project path or name/version | cfg.buildDir, cfg.name, … |
| A value from the real OS environment only | sys.env("KEY") |
cfg is a reserved name — you cannot declare a variable, function, or type
called cfg.