On this page
Sources
A source tells pekit where a recipe's code comes from. Before any build
target runs, pekit resolves the source into a concrete source tree on
disk — the directory that targets execute in and that @source: package
references read from. This page covers the source
kinds, how each is materialised, and how a recipe can borrow behaviour from its
source tree.
Sources are declared under [source.*] in the recipe. A recipe may have:
- no external source (sourceless) — the recipe directory itself is the tree;
- one reproducible source — either
[source.git]or[source.url], never both; - optionally a local override —
[source.local], on its own or alongside a reproducible source.
In the code these map to SourceConfig.HasReproducible() (git or url set) and
SourceConfig.HasExternal() (reproducible or local set). Declaring two
reproducible tables is an error (mixed_source: "exactly one reproducible
source table is allowed").
The three source kinds
[source.git]
Clones a git repository and checks out a ref.
| Field | Required | Default | Meaning |
|---|---|---|---|
url |
yes | — | Repository URL passed to git clone --mirror. |
ref |
no | {{version}} |
Ref to check out. Templated with the selected version ({{version}}, {{major}}, …). Must render non-empty. |
versions |
no | — | Version cap: a constraint that filters enumerated or requested versions. See Versions. |
tag_regex |
no | — | Regex used when enumerating tags (see Enumeration). |
[source.git]
url = "https://github.com/example/widget.git"
ref = "v{{version}}"
versions = ">=2.0.0"
tag_regex = "^v([0-9]+\\.[0-9]+\\.[0-9]+)$"
If ref renders empty (for example, a bare {{version}} with no version
selected) pekit fails with missing_version — pass --version or set a
non-templated ref.
The code does not implement a submodule option.
[source.git]accepts only the four fields above; asubmoduleskey is anunknown_keyerror.
[source.url]
Downloads a file over HTTP(S), optionally extracting it as an archive.
| Field | Required | Default | Meaning |
|---|---|---|---|
url |
yes | — | Download URL. Templated with the selected version. |
extract |
no | false |
If true, treat the download as an archive and extract it. |
root |
no | "." |
Subdirectory inside the archive to promote as the source tree. Templated; only meaningful when extract = true. |
versions |
no | — | Version cap (as for git). |
file_regex |
no | — | Regex used when enumerating a URL listing. |
checksum |
no | — | Expected digest, or a per-version table (see below). |
[source.url]
url = "https://ftp.example.org/widget/widget-{{version}}.tar.gz"
extract = true
root = "widget-{{version}}"
checksum = "sha256:0f1e2d..."
checksum is a sha256:<hex> string, and it is the only algorithm the
verifier accepts — any other prefix fails with "unsupported checksum spec". It
may instead be a table keyed by version:
[source.url.checksum]
"1.2.0" = "sha256:aaaa..."
"1.3.0" = "sha256:bbbb..."
When the table form is used and the selected version has no entry, pekit fails
with missing_checksum. A URL source without a checksum is materialised
unanchored — pekit records that the tree is not pinned to a digest, which
matters for reproducible packaging.
[source.local]
Points at a directory already on disk.
| Field | Required | Meaning |
|---|---|---|
path |
yes | Directory to use as the source tree. Resolved relative to the recipe directory. |
[source.local]
path = "../widget-checkout"
[source.local] is an override, not a reproducible source: on its own a
recipe with only [source.local] is not reproducible (HasReproducible() is
false). The local tree is only used when a local flag selects it — see below.
Selecting the local override
Two invocation flags choose the local override; they resolve differently and are mutually exclusive (passing both is an error).
--local(strict): require a local source. If neither[source.local]nor--local=<path>supplies a directory, pekit fails withmissing_local_source. Use this to force building from a working checkout.--prefer-local: use the local source if it is usable, otherwise fall back to the reproducible source. If the local tree is missing and the recipe has no reproducible source, the fallback fails.
Both accept an inline path:
--local/--prefer-localwith no=<path>uses the directory from[source.local](resolved relative to the recipe directory).--local=<path>/--prefer-local=<path>uses<path>resolved relative to the invocation's working directory (cwd), ignoring[source.local].
The chosen directory must exist and be a readable directory, or pekit errors.
Local sources require an exact --version — --latest, --all-versions,
and constraint selectors are rejected (unsupported_version_mode); with no
version, a local build uses the placeholder 0.0.0-localdev.
For a sourceless recipe, passing a local flag is an unsupported_flag
error — unless --allow-unused is set, in which case the flag is ignored with a
warning and the recipe stays sourceless.
Materialisation
Resolving a source produces a SourceState: its Kind, the SourceRoot
(where targets run), a ProvenanceRef, and a timestamp. What pekit does to
produce the tree depends on the kind.
Sourceless (recipe). No work: the source root is the recipe
directory. Provenance is recipe:<recipe-root>.
Local (local). No copy or fetch: the source root is the local
directory itself; targets run in place. Provenance is local:<path>. Because
nothing is fetched, local sources are not cached and --refresh-source has no
effect on them.
Git (git). pekit keeps a bare mirror clone under
<out_dir>/_source_cache/git/<hash>/repo.git:
- If the mirror does not exist,
git clone --mirror <url>; otherwisegit fetch --prune --tagsto update it. - Resolve
refto an immutable commit (git rev-parse <ref>^{commit}). - Clone the mirror into
<out_dir>/git-<hash>/source, thengit reset --hard <commit>andgit clean -fdxto pin the checkout. - Write a
source.pekit.jsonmanifest recording the URL, ref, resolved commit, and provenance.
Provenance is git:<url>@<commit> and the timestamp is the commit's committer
date, so a git build is anchored to a specific commit even when ref was a
branch or tag.
URL (url). pekit caches the downloaded artifact under
<out_dir>/_source_cache/url/<hash>/:
- If the artifact is not already cached, download it (with a
pekit/2user-agent). - If a
checksumis set, verify it; on mismatch, re-download once and verify again before failing. - Materialise the tree at
<out_dir>/url-<hash>/source: ifextractis set, extract the archive to a temporary directory, then promote therootsubdirectory (which must exist, elsemissing_source_root); otherwise copy the raw artifact into the source directory. - Write a
source.pekit.jsonmanifest.
The downloaded artifact is cached regardless. The extracted tree is re-materialised from that cached artifact on each run when a checksum is set (the pinned content is deterministic anyway); without a checksum the tree is reused as long as the recorded manifest still matches.
Caching and --refresh-source
Pekit does not expose configurable cache policies. Caching is exactly the
behaviour above — a persistent raw cache (mirror repo / downloaded artifact)
plus a materialised tree — and the one knob is --refresh-source, which
discards the cache and rebuilds from scratch:
- git: removes the mirror repo and the checkout scope, forcing a fresh
clone --mirrorand checkout. - url: removes the cached artifact and the materialised scope, forcing a re-download and re-extract.
- local / sourceless: nothing to refresh.
Archive extraction
Extraction is chosen by the artifact's filename suffix. Only these formats
are handled; anything else is an unsupported_archive error:
| Suffix | Handler |
|---|---|
.zip |
native zip reader |
.tar |
uncompressed tar |
.tar.gz, .tgz |
gzip + tar |
.tar.bz2, .tbz2 |
bzip2 + tar |
.tar.zst |
zstd + tar |
.tar.xz, .txz |
tar piped through the external xz -dc (requires xz on PATH) |
Extraction is hardened: entries that escape the extraction root (absolute
paths, .., or traversal through a symlinked parent), unsafe symlink targets,
NUL bytes, unsupported entry types, and colliding entries are all rejected as
unsafe_archive.
Enumeration
Reproducible sources can list the versions they offer upstream; this feeds
--latest, --all-versions, and constraint selectors (see
Versions).
- git:
git ls-remote --tags <url>, with tags optionally filtered bytag_regex. If the regex has a capture group, group 1 is the version; otherwise an embeddedMAJOR.MINOR.PATCHis extracted. - url: fetch the directory listing derived from the
urltemplate (the part before the first{{…}}, up to the last/), filter entries byfile_regex, and extract versions from the matches.
Local and sourceless recipes cannot enumerate
(version_enumeration_unavailable) — they require an exact version. The
versions field on a git or url source acts as a cap applied to the
enumerated (or requested) set; version selection itself is documented on the
Versions page.
Source roots and provenance
The source root is the directory targets execute in and the base that
@source: file references resolve against. Every
resolution records a provenance ref identifying exactly what was
materialised:
| Kind | Provenance ref |
|---|---|
| sourceless | recipe:<recipe-root> |
| local | local:<path> |
| git | git:<url>@<commit> |
| url (checksummed) | url:<url>#<checksum> |
| url (unchecksummed) | url:<url> (marked unanchored) |
For git and url sources the provenance is also written to a source.pekit.json
manifest beside the materialised tree, and pekit emits it as a source event
under --verbose.
Delegation
A recipe can borrow behaviour from a pekit.toml that lives inside its
materialised source tree, using the [delegate] table (or the shorthand
delegate = true, which enables everything):
delegate = true
# or, selectively:
[delegate]
build = true
env = true
wrap = true
packages = true
[delegate] has four surfaces plus all:
| Key | Borrows |
|---|---|
all |
all of the below |
build |
build/test/install/clean targets from the source's recipe |
env |
environment variables from the source's env file |
wrap |
the command wrapper from the source's env file |
packages |
package definitions discovered in the source tree |
Delegation only happens when the source tree is a different directory from
the recipe (so a sourceless or in-place local recipe never delegates), and, for
build targets, only when a pekit.toml exists at the source root. The merge
(mergeDelegatedRecipe) is additive with the base recipe winning: a
delegated target is adopted only if the base recipe does not already define a
target of the same kind and name, and adopted targets are tagged as owned by the
source. Env, wrap, and package delegation are likewise gated on the
corresponding Allows… flag and only apply when the source tree differs from
the recipe root.
This lets a thin Peios recipe wrap upstream software that already ships its own
pekit recipe: point [source.git] at the upstream repo, set delegate = true,
and reuse its build and packaging while overriding only what you need.
Where to go next
For how enumerated versions are selected and rendered, read Versions.
For turning the materialised tree into package payloads, read Packaging files.
For the exhaustive [source] schema, read Recipe format reference.