Supporting files and reference tables

This page documents the TOML files that sit alongside a recipe — the workspace file, env files, and keyring files — and gives exhaustive reference tables for the three substitution systems used throughout pekit: template variables, source-ref roots, and publish targets.

All files are TOML. Every loader rejects unknown top-level keys with an unknown_key diagnostic, so the key tables below are complete: any key not listed is an error.

For the recipe file itself (pekit.toml / *.pekit.toml), see recipe-format.


workspace.pekit.toml #

A workspace file marks a directory as a pekit workspace and declares distro-wide defaults.

KeyTypeRequiredMeaning
includearray of stringsyesMember patterns the workspace builds. Must be present and non-empty, or the load fails (missing_key).
excludearray of stringsnoPatterns removed from the include set.
[env]tablenoWorkspace-level environment variables. See env table.
[wrap]tablenoWorkspace-level command wrapper. See wrap table.
[policy]tablenoDistro-wide derivation policy. See below.

[policy] #

The [policy] table currently carries exactly one sub-table. Any other key under [policy] is rejected.

KeyTypeRequiredMeaning
[policy.symbol_versions]table (string → string)noMaps a shared-library soname to the symbol-version token prefix whose tokens are commensurable with the providing package's version. Governs which sonames get a symbol-version floor during dependency derivation.

Each entry in [policy.symbol_versions] is soname = "PREFIX_":

[policy.symbol_versions]
"libc.so.6"     = "GLIBC_"
"libstdc++.so.6" = "GLIBCXX_"

env.pekit.toml #

An env file provides a named, selectable layer of environment variables, command wrapper, and dependency-provider override.

An env file must declare at least one of [env], [wrap], or dependency_provider; a file with none of them is rejected (missing_key).

KeyTypeRequiredMeaning
[env]tableone of the threeEnvironment variables. See env table.
[wrap]tableone of the threeCommand wrapper. See wrap table.
dependency_providerstringone of the threeSelector naming which of a build target's declared dependency-provider blocks ([build.<target>.dependencies.<provider>]) is exported as the PEKIT_DEPENDENCIES* variables. Validated as a selector.

--env <name> selection #

The active env file is chosen from the --env <name> flag (default main), resolved relative to the recipe root:

--env valueFile loadedMust exist?
(unset) or mainenv.pekit.tomlno (silently absent)
none(no env file loaded)n/a
any other <name><name>.env.pekit.tomlyes — a missing file is an error

So --env prod loads prod.env.pekit.toml, and it must exist. The default env.pekit.toml is optional.

[env] table #

Shared by workspace.pekit.toml, env.pekit.toml, and the recipe's own [env]. It is a flat table of NAME = "value" string pairs.

RuleDetail
Value typeEvery value must be a string.
Name grammarNames must match ^[A-Za-z_][A-Za-z0-9_]*$.
Reserved prefixUser env may not set any PEKIT_* name — those are pekit-managed. A PEKIT_-prefixed name is rejected at build time (reserved_env).
LayeringApplied workspace → (delegated source) → recipe → env file, later layers overriding earlier ones.
ExpansionValues are shell expressions, not literals. $NAME expands when the target runs, so a value may reference a managed PEKIT_* variable or an env value declared before it. Values are word-split-safe: spaces are preserved, so a flag list needs no quoting of its own.
[env]
CC = "clang"
CFLAGS = "-O2 -pipe"
CXXFLAGS = "$CFLAGS -Wp,-D_GLIBCXX_ASSERTIONS"
TOOL_PATH = "$PEKIT_ROOT/tools"

$ always expands — a literal $ is not expressible here, and \$ does not escape it. A value needing one (an $ORIGIN rpath, say) belongs in the target's command, where the recipe has the full shell.

[wrap] table #

Shared by workspace.pekit.toml, env.pekit.toml, and the recipe. Wraps every target command in another command (sandbox, nice, a toolchain shim, etc.).

KeyTypeRequiredMeaning
commandstring or array of stringsnoThe wrapper. Must contain exactly one {{command}} placeholder.

{{command}} rules:

  • String form — the string must contain {{command}} exactly once.
  • Array form — exactly one element must be the literal {{command}} (as a complete argument, not a substring), and it may not be element 0 (the program). An element that merely contains {{command}} inside other text is rejected.
[wrap]
command = ["bwrap", "--ro-bind", "/usr", "/usr", "{{command}}"]

Note: {{command}} here is a wrap-only placeholder handled by the wrap machinery. It is not one of the template variables below and is not available in ordinary recipe strings.


*.keyring.pekit.toml #

A keyring file supplies secrets to the build environment as PEKIT_KEYRING_* variables. Keyrings are opt-in per invocation (--keyring); a keyring named <name> resolves to <name>.keyring.pekit.toml, searched in the workspace root then the recipe root (a path-like value is used verbatim).

Schema #

The file is a tree of nested TOML tables whose leaves are strings. Each leaf's dotted path is converted into an environment-variable name:

  • Prefix PEKIT_KEYRING_, then the dotted path uppercased.
  • Every run of non-alphanumeric characters (including the . separators) collapses to a single _.
  • Trailing _ is trimmed.
Leaf pathExported variable
tokenPEKIT_KEYRING_TOKEN
github.tokenPEKIT_KEYRING_GITHUB_TOKEN
registry.api-keyPEKIT_KEYRING_REGISTRY_API_KEY
# placeholders only — never commit real secrets
token = "REPLACE_ME"

[github]
token = "REPLACE_ME"

[registry]
api-key = "REPLACE_ME"

Restrictions #

RuleDetail
Leaf typeEvery leaf must be a string. A non-string leaf is an error.
NestingSub-tables nest arbitrarily; each nesting level adds a segment to the exported name.
Typed entriesA table containing a path or content key is a typed entry and is not supported yet (unsupported_keyring_entry).
CollisionsA keyring export that collides with a normal or managed env variable is an error (env_collision).

Well-known entries #

Most keyring values are opaque to pekit — they exist to be exported. The entries below are additionally read by pekit itself:

Leaf pathMeaning
signing.package_keyPath to an Ed25519 private key — a raw 32-byte seed, or PKCS#8 PEM as produced by openssl genpkey -algorithm ed25519. When present, every peipkg-format artifact the run produces — package members and -source packages alike — is packed with an embedded signature naming the key's fingerprint, and each emits a sign event. A relative path resolves against the invocation's working directory. A configured key that cannot be loaded is an error (signing_key); pekit never silently falls back to unsigned output.

Without a configured signing key, package writes unsigned artifacts, and publish refuses peipkg-format packages unless --allow-unsigned is passed (unsigned_publish).

A build target's sign table reads further entries, but which ones is the recipe's choice: each sign.pip value is the dotted path of a leaf holding the path to an ML-DSA-65 private key. Those entries are still exported to the target like any other.


pekit.lock #

The machine-written source lockfile, created and updated by pekit in the recipe directory beside pekit.toml. It pins fetched source inputs trust-on-first-use: the first resolve of a version records what was fetched, and every later resolve verifies against the record — see Sources for the behaviour. Commit it, and do not edit it by hand: pekit lock --repin --version <v> is the one supported way to change an existing entry. Loading is strict — an unknown key is unknown_key and an unsupported schema is lock_schema.

Schema #

KeyTypeMeaning
schemaintegerLockfile schema version. Currently 1.
[[source]]array of tablesOne entry per locked version, kept sorted by version.

Each [[source]] entry carries the keys for its source kind:

KeySet forMeaning
versionallThe locked version. Empty for a versionless url fetch.
urlurl sourcesThe rendered URL fetched at lock time. Provenance only — the hash, not the address, is the assertion.
sha256url sourcesSHA-256 of the fetched artifact.
refgit sourcesThe rendered ref the version resolved through.
commitgit sourcesThe commit the ref resolved to — the assertion.
signature_keyurl sources with [source.url.signature]Hex fingerprint of the pinned upstream key that verified the artifact at lock time.
locked_atallUTC timestamp of the pinning run (RFC 3339).

Worked example #

schema = 1

[[source]]
  version = "0.5.12"
  url = "https://example.org/dash-0.5.12.tar.gz"
  sha256 = "6a474ac46e8b0b32916c4c60df694c82058d3297d8b385b74508030ca4a8f28a"
  signature_key = "9f0b7ddc1325a3e2c40e5f0a88b1c62f3d94ab07"
  locked_at = "2026-08-10T11:00:00Z"

[[source]]
  version = "1.2.0"
  ref = "v1.2.0"
  commit = "8f3a1bc99e2f6d41c07b21ac1886d63b7f2e5d10"
  locked_at = "2026-08-10T11:00:00Z"

The patches directory #

The directory named by [source] patches = "<dir>" in pekit.toml (conventionally patches, and always a single path segment). pekit applies its series to the materialised source tree before any target runs — see Sources for the behaviour — and the whole directory ships in the recipe's source package under patches/.

The series file #

<dir>/series lists the patches in apply order: one slash-separated relative path per line. # starts a comment, whole-line or trailing; blank lines are ignored; entries must not contain whitespace. Subdirectories are allowed (misc/fix.patch).

Loading is strict:

ConditionError
series missing or unreadablepatch_series
An entry that repeats, contains whitespace, or escapes the directorypatch_series
A series entry with no file behind itpatch_missing
A *.patch file in the directory the series does not listunused_patch (permitted by --allow-unused)

Application is git apply per entry, in series order, with no fuzz: a failed hunk is patch_apply and a patch that matches nothing is patch_skipped.

Worked example #

patches/
  series
  misc/
    mke2fs-root-sd.patch

with a series of:

# Applied in dependency order:
misc/mke2fs-root-sd.patch   # stamp the root inode's SD at format time

Give each patch file a short header — what it changes and its upstream status (submitted, backport, Peios-specific). The series ships with the corresponding source, where that header is a third party's only context.


Template variables #

Recipe and package value strings (source refs, ref, package metadata, file/symlink paths, excludes, claims, multipack enum.files.path, publish path) are run through the template engine before use. The syntax is {{name}}, where name matches [A-Za-z0-9_]+. An unknown name is an error.

VariableSourceAvailability
{{version}}the selected version, verbatim (Raw)Errors when no version is selected.
{{major}}major componentAvailable once a version is selected.
{{minor}}minor componentErrors if the selected version has no minor component.
{{patch}}patch componentErrors if the selected version has no patch component.
{{prerelease}}pre-release component (after -)Empty string when absent.
{{buildmeta}}build-metadata component (after +)Empty string when absent.
{{multipack}}the current multipack instance valueOnly available while expanding a multipack package's per-instance values; errors in any non-multipack context.

A version string parses as major[.minor[.patch]][-prerelease][+buildmeta], so {{minor}} / {{patch}} are only present when the version actually carries them.

Shell targets use $PEKIT_* instead #

The {{...}} engine applies to recipe configuration strings, not to the shell bodies of build/test/install/clean commands. Target commands instead read the pekit-managed environment. The version equivalents are:

Template variableEnvironment variable
{{version}}$PEKIT_VERSION
{{major}}$PEKIT_VERSION_MAJOR
{{minor}}$PEKIT_VERSION_MINOR
{{patch}}$PEKIT_VERSION_PATCH
{{prerelease}}$PEKIT_VERSION_PRERELEASE
{{buildmeta}}$PEKIT_VERSION_BUILDMETA

Alongside these, the managed environment also exports PEKIT_RECIPE_ROOT, PEKIT_SOURCE_ROOT, PEKIT_LITERAL_ROOT, PEKIT_ROOT, PEKIT_OUT_BASE, PEKIT_OUT, PEKIT_COMMAND, PEKIT_TARGET, PEKIT_WORKSPACE_ROOT, PEKIT_BUILD_TIMESTAMP, PEKIT_SOURCE_TIMESTAMP, a PEKIT_<DEP>_OUT per declared needs dependency, and any PEKIT_KEYRING_* from active keyrings. The full environment contract is documented in the command-line reference.


Source-ref roots #

Package files sources, excludes, and multipack.enum.files.path are references whose leading token selects which root the path is resolved against. A reference is ROOT:path (or a bare path, which normalises to the owner's default root).

Reference formRootMeaning
target:paththe build target's output stageReads from the output of build target target.
:pathbuild target main's output stageSame as above with the target name defaulted to main.
@source:paththe materialised source tree (LiteralRoot)Reads from the checked-out / extracted source.
@recipe:paththe recipe directoryReads a file shipped next to the recipe.
@workspace:paththe workspace rootReads a file from the workspace. Errors if used outside a workspace.
bare pathowner defaultA path with no : normalises to the owning layer's root: @recipe: for recipe packages, @source: for source packages, @workspace: for workspace packages.

Any reference containing a : that is not one of the @-prefixed roots is a build reference; the token before the : is the target name. See packages for how these are used in a package's [files] table.


Publish targets #

The publish command copies a package's built artifact to declared destinations. There is exactly one publish kind: localdir. Any other [publish.*] key is rejected (unknown_key). A package selected for publishing that declares no target is an error.

Targets are an array of tables:

[[publish.localdir]]
path = "dist/{{version}}"
overwrite = false
KeyTypeRequiredMeaning
pathstringyesDestination directory, relative to the base root. Templated with {{version}} and {{multipack}}. The artifact keeps its own filename.
overwriteboolno (default true)Whether an existing destination file may be replaced. When false, an existing destination is an error (publish_exists).

Resolution details:

AspectBehaviour
Base rootThe workspace root when building in a workspace, otherwise the recipe root. path is joined onto it.
Final destination<base>/<path>/<artifact-filename>.
CollisionsTwo instances resolving to the same destination is an error (publish_collision) unless they are the same artifact.

Selectors #

Several names above are validated as selectors: target names, package selectors, multipack values, and dependency_provider. A selector must match ^[A-Za-z0-9_.+-]+$, must not be empty, must not start with -, and must not contain / or :.

See also #

Edit this page