On this page
- The three layers
- The [env] block
- Composition and override
- Shell expansion of [env] values
- Env files and --env
- [wrap] wrappers
- dependency_provider
- Keyrings
- --keyring=<value> (keyring files)
- --keyring.<dotted.path>=<value> (literals)
- Precedence
- Keyring file format
- A note on secrets in output
Environments and keyrings
Every target pekit runs — a build, test, install, or clean command —
executes inside an environment pekit assembles for it. That environment has three
distinct layers: the managed PEKIT_* variables pekit sets automatically, the
keyring values that carry secrets and keys, and the user variables you
declare in [env] and env files. This page describes how those layers are built,
how they compose and override, how [wrap] wrappers change the way the command is
launched, and how keyrings resolve and inject values.
For the exhaustive list of managed PEKIT_* variables and what each one means, see
Recipe anatomy. This page covers everything you add on
top of that contract.
The three layers
When a target runs, pekit composes three maps and overlays them in a fixed order:
| Layer | Source | Naming |
|---|---|---|
| Managed | Set by pekit (recipe/source roots, output paths, version parts, dependency outputs) | PEKIT_* |
| Keyring | Keyring files and --keyring.x.y= literals |
PEKIT_KEYRING_* |
| User | [env] in the workspace, recipe, and selected env file |
Any valid shell name |
The overlay order is managed, then keyring, then user — so in principle a later layer overwrites an earlier one on a key collision. In practice the three namespaces are kept disjoint by hard rules:
- User
[env]may not set any variable beginning withPEKIT_. Doing so is areserved_enverror. - A keyring export that collides with a user
[env]variable or with a managed variable is anenv_collisionerror, not a silent override.
So the layers never actually fight: managed owns PEKIT_*, keyrings own
PEKIT_KEYRING_*, and everything else is yours.
The [env] block
[env] declares plain environment variables as a TOML table. Each key is the
variable name and each value is a string:
[env]
CC = "clang"
CFLAGS = "-O2 -pipe"
PREFIX = "/usr"
Keys must be valid environment-variable names — they match
^[A-Za-z_][A-Za-z0-9_]*$. A non-string value, or a name outside that grammar, is
a parse error. Declaration order is preserved from the file.
[env] may appear in three places, each contributing to the user layer:
- the workspace file (applies to every member),
- the recipe (
pekit.toml), - the selected env file (see below).
Composition and override
Within the user layer the sources are applied in this order, and a later source overrides an earlier one on the same variable name:
- workspace
[env] - delegated source recipe
[env], then that source's env file (only when the recipe delegates env — see Recipe anatomy) - recipe
[env] - the selected env file's
[env]
So a variable set in the recipe overrides the same variable inherited from the workspace, and the selected env file has the final say.
Shell expansion of [env] values
For the common case — a shell target, or any target run under a [wrap] wrapper —
pekit emits the user variables as export lines in a shell script that runs before
your command, and it does not quote them. That means [env] values are
expanded by the shell and may reference other variables:
[env]
PATH = "$PEKIT_OUT/bin:$PATH"
LD_LIBRARY_PATH = "$PEKIT_OUT/lib"
Managed PEKIT_* and keyring PEKIT_KEYRING_* values are exported before the
user block, so [env] can build on them. (A bare argv command — an array-form
command with no wrapper — receives the composed environment directly from the
process, where these values are literal rather than shell-expanded. Keep [env]
values literal if your target uses the array form without a wrapper.)
Env files and --env
An env file is a supporting file that lives next to the recipe and carries an
[env] block, a [wrap] wrapper, a dependency_provider, or any combination of
the three. It lets you keep environment- or profile-specific settings out of the
recipe proper. An env file must declare at least one of those keys; the only
recognised top-level keys are env, wrap, and dependency_provider.
Which env file is loaded is controlled by --env <name>:
--env value |
File loaded | Must exist? |
|---|---|---|
(omitted) / main |
env.pekit.toml |
No — silently skipped if absent |
none |
(none) | — |
<name> |
<name>.env.pekit.toml |
Yes — missing file is an error |
The default is main, so env.pekit.toml is picked up automatically when present.
Passing --env none disables env-file loading entirely. Any other name selects a
named profile such as release.env.pekit.toml (--env release), which must
exist.
# release.env.pekit.toml
dependency_provider = "peipkg"
[env]
CFLAGS = "-O3 -DNDEBUG"
For the exact env-file schema, see Supporting files.
[wrap] wrappers
A wrapper changes how the target command is launched: instead of running your
command directly, pekit substitutes it into a wrapper command at a {{command}}
placeholder. This is how you run a build inside a sandbox, a container shim, a
nice/taskset prefix, or any other launcher.
[wrap] takes a single command, written either as a shell string or as an argv
array. Exactly one {{command}} placeholder is required:
# String form — {{command}} appears once in the shell string
[wrap]
command = "firejail --quiet -- {{command}}"
# Array form — {{command}} must be a complete argument (never argv[0])
[wrap]
command = ["nice", "-n", "10", "sh", "-euc", "{{command}}"]
At run time pekit assembles the export prelude plus your target command into a script, and:
- String wrapper: the script is shell-quoted and spliced in at
{{command}}, then executed withsh -euc. - Array wrapper:
{{command}}is replaced by the script; the wrapper's first element is the program that is executed.
Only one wrapper is active per run. Like [env], [wrap] may be declared in
several places, and the last non-empty one wins, in this order (lowest to
highest precedence):
- workspace
[wrap] - delegated source env file
[wrap](only when the recipe delegates wrap) - recipe
[wrap] - the selected env file's
[wrap]
So a recipe wrapper overrides the workspace's, and the selected env file's wrapper overrides the recipe's.
dependency_provider
Env files may also set dependency_provider, a single string naming which of a
target's declared [build.<target>.dependencies.<provider>] blocks is exported
to the build environment (PEKIT_DEPENDENCIES, PEKIT_DEPENDENCY_PROVIDER,
PEKIT_DEPENDENCIES_FILE). It is not an environment variable; it selects a
declared provider block by name. When both a delegated source env file and the
recipe's own env file set it, the recipe's env file wins. See
Dependencies and claims for what the
selection exports.
Keyrings
Keyrings supply secrets and keys to targets. Every keyring value is exported to the
target as a PEKIT_KEYRING_* environment variable. There are two ways to provide
them: keyring files and inline literals.
--keyring=<value> (keyring files)
--keyring names a keyring file and is repeatable. The value is resolved like
this:
- If the value is path-like, it is treated as a path immediately. A value is
path-like when it is absolute, begins with
., begins with~, or contains a/. The path is resolved relative to the current directory; if the file does not exist, that is amissing_keyringerror. - Otherwise the value is treated as a name: pekit looks for
<value>.keyring.pekit.tomlin each search root in turn. For a plain recipe the search root is the recipe directory; inside a workspace the workspace root is searched first, then the recipe directory. - If no named file is found in any search root, pekit falls back to treating the
value as a path relative to the current directory; if that does not exist
either, it is a
missing_keyringerror.
# Resolved by name → looks for prod.keyring.pekit.toml in the search roots
pekit build --keyring=prod
# Path-like → loaded directly
pekit build --keyring=./secrets/prod.keyring.pekit.toml
When several --keyring files are given, they are loaded in command-line order and
each overlays the previous — so a later keyring file overrides an earlier one on
the same key.
--keyring.<dotted.path>=<value> (literals)
To inject a single value without a file, use --keyring.<dotted.path>=<value>. The
dotted path is sanitised into an exported variable name: it is upper-cased, every
run of non-alphanumeric characters collapses to a single _, and the result is
prefixed with PEKIT_KEYRING_.
pekit build --keyring.tcb.priv=<value>
# exported to the target as PEKIT_KEYRING_TCB_PRIV
Literals are keyed by their dotted path, so repeating the same path replaces the earlier value with the later one.
Precedence
Keyring sources combine in this order, lowest to highest precedence:
earlier --keyring file < later --keyring file < --keyring.x.y= literal
That is: keyring files overlay in command-line order, and all inline literals are
applied last, so a --keyring.x.y= value always overrides whatever a keyring file
provided for the same exported name. Any resulting PEKIT_KEYRING_* name that
would collide with a managed PEKIT_* variable or with a user [env] variable is
an env_collision error.
Keyring file format
A *.keyring.pekit.toml file is a TOML document whose leaves are strings. Each
string leaf becomes one PEKIT_KEYRING_* variable, named from its full dotted key
path using the same sanitisation as inline literals. Nested tables are flattened:
# prod.keyring.pekit.toml
token = "<value>" # -> PEKIT_KEYRING_TOKEN
[tcb]
priv = "<value>" # -> PEKIT_KEYRING_TCB_PRIV
pub = "<value>" # -> PEKIT_KEYRING_TCB_PUB
A leaf that is not a string is rejected, and typed entries — a sub-table carrying a
path or content key — are reserved and currently rejected as
unsupported_keyring_entry. The full schema is documented in
Supporting files.
A note on secrets in output
Keyring values are exported to the target like any other environment variable.
pekit does not redact them from a target's stdout/stderr — if your command
prints a secret, pekit streams it verbatim. pekit's own diagnostics and its
--verbose environment summary log variable names only, never values, but you
are responsible for not echoing secrets from inside your commands. Prefer writing
secrets to files or consuming them directly rather than printing them.