pkg-build(1)
NAME
pkg-build - build a recipe directory into a signed local repository
SYNOPSIS
pkg build recipe-dir --out DIR [--with SPEC]... [--trust-key HEX]... [--work DIR] [--keep] [--replace]
DESCRIPTION
You've got a recipe. Maybe you wrote it, maybe you changed one line in an existing one. Now you want to see it build, install the result, and maybe hand it to somebody. You shouldn't need a build farm for that, and you don't.
pkg build builds one recipe on this machine, with the same build code the build farm runs, and publishes the result into a local repository you own. From there you can install it, compose with it, or roll it into a .pkgtar bundle for somebody else.
recipe-dir is a recipe directory the way the registry keeps it: recipe.toml, plus any patches/ and overrides.lua. Its [recipe] block names the package, version and release that come out.
What happens
- Load the build key. Without one,
pkg buildstops and points you at pkg-keygen(1). - Fetch the upstream sources the recipe names, check their SHA-256, and detect which build-system probe applies: autotools, cmake, meson, cargo, and so on.
- Resolve a buildroot from the repository chain; see THE BUILDROOT.
- Compose that buildroot into a scratch sysroot, verifying every manifest's signature. When the sysroot ships a sysusers.d, generate its
/etc/passwdand/etc/groupfrom it with the host'ssystemd-sysusers. - Run the recipe's configure, build and install phases once, sandboxed against that sysroot, and split the result into subpackages.
- Publish every subpackage into
--out, signed with the build key.
Is it the same package the distribution ships?
Usually, no. And it doesn't pretend to be.
pkg build applies the build policy to whatever the chain offers today and produces a new artifact. It makes no claim that the bytes match the published package of the same version. Usually they won't, because the buildroot is different. If what you want is to reproduce a published build from its pinned inputs, that's pkg provenance rebuild; see pkg-provenance(1).
THE BUILD KEY
Every build is signed with one key per user, $XDG_CONFIG_HOME/pkg/build.key, which pkg-keygen(1) creates. pkg build never makes one up on its own:
error: build: no build key at /home/u/.config/pkg/build.key; create one with pkg keygen
The key signs each manifest and the output repository's TUF metadata. That includes the self-signed metadata/1.root.json the first build writes. That file is the repository's trust anchor, and whoever consumes the repository pins it once. One key per contributor, rather than one per build, is what makes that pin last.
THE BUILDROOT
The sysroot a recipe builds in comes from the same repository chain pkg-install(1) uses: the drop-ins in /etc/pkg/repos.d, or --repo-url and --tuf-root when you give them. Never from the host's /usr. What goes into it:
+buildThe build floor: the toolchain, and the build systems every recipe compiles with. Some repository in the chain has to publish this set:
error: build: no repo in the chain publishes +build, the build floor every recipe is staged onto; configure one that does
+build-probe- The extra tools the winning probe needs on top of the floor, when the chain publishes such a set (
+build-rustcrate,+build-pep517). A probe with no set of its own needs nothing past the floor. [build_deps]- The recipe's
neededandhost_onlyentries. --with- Extra packages, for this run only.
elfutils- Always, for
eu-strip.
That whole set is closed over runtime requirements, so a tool's own libraries and interpreters come along. Everything the chain can't supply is reported in one go, and the build doesn't start:
error: build: the chain cannot supply the buildroot: zstd/staticdev: zstd has no subpackage staticdev ghost: no repo in the chain pins ghost
The buildroot also has to contain a /bin/sh, since every build phase starts with one. A floor missing the filesystem skeleton or a shell is refused by name, instead of failing later with a bare ENOENT.
pkgconfig: build dependencies (the -sys crate case) aren't supported yet, and are refused by name.
Who signed the buildroot?
Every buildroot manifest has to be signed by a key you trust. That's any key that signed a package already installed on this machine, your own build key, and any you give with --trust-key. So a package an earlier pkg build published into a local repository can be a dependency of the next build without being installed first. Any of these keys is trusted for any package name: a local build is meant to chain local builds, bundles and path repositories, so pkg build does not scope a key to the names it signed the way pkg offline upgrade does. On a machine with nothing installed from the chain you build against, pass its key with --trust-key.
Which subpackages does a name bring in?
Not the same ones pkg-install(1) would. A bare name here doesn't follow the runtime content rule of pkg-subpkgs(7). It follows the rules the build farm stages its buildroots by, so a recipe builds the same way in both places:
- +build and +build-<probe> members
- Host content:
bin,data,libandmain. Neverdev. The floor and the probe tools get run, not compiled against. - [build_deps] needed,
host_onlyand--with - Everything that matters to a build.
devandstaticdevare in. Onlydebug,debugsrc,source,mod,man,info,docandlangare left out. - name/subpkg
- Exactly that subpackage, anywhere in the list above. That's how the floor asks for
glibc/dev, while the rest of glibc stays at host content.
--with only takes those two forms. +set and name=version aren't accepted there.
THE OUTPUT REPOSITORY
--out is an ordinary signed repository; see pkg-repository-layout(7). The first build creates it, and every build after that grows it. Each build moves the repository's pointer for its package to the version it just built. So the repository's current set is the latest build of everything ever built into it. Older builds stay on disk as history, and nothing resolves to them.
Building the same N-V-R twice
A second build of an N-V-R the repository already holds is accepted when it reproduces the same bytes. When it doesn't, it's refused, just like a published repository would refuse it:
error: build: ...; bump [recipe].release, or pass --replace to retract the build already in /home/u/work/out
While you're iterating on a recipe, --replace retracts the old build and publishes the new one in its place. Bump release once the result goes to anybody else.
USING THE OUTPUT
Bundling the output repository takes one command; see Rolling a .pkgtar, below. Installing or composing from it means putting it in the chain, above the repositories it was built against. A drop-in in /etc/pkg/repos.d does that (see pkg-repos.d(5)):
name = "local" path = "/home/u/work/out" priority = 30 enabled = true consumes = ["Sandcastle-Common"]
A local path repository takes no trust anchor. From then on pkg-install(1), pkg-compose(1) and pkg-bundle(1) resolve through it like any other repository. Its own packages are read from the directory, and their dependencies are fetched from the rest of the chain.
Rolling a .pkgtar
pkg bundle create --from-repo-dir /home/u/work/out \ --output mywork.pkgtar
--from-repo-dir puts the output repository on top of the configured chain for that one command, and reads its metadata/ for the roots. So bundling needs no drop-in. The bundle's own TUF metadata is signed with the build key and lists everything the bundle carries. That's what lets the dependencies travel with it, visibly.
With no --closure, the bundle holds everything the output repository currently ships, every package built into it and every subpackage, and nothing else. The repository directory is the unit. If you want something left out of a bundle, build it into a different --out. Pass --full-closure to also carry what those packages need at runtime, for a machine that has none of the distribution to resolve against; see pkg-bundle(1).
To bundle only part of it, name the roots with --closure, in the spec grammar of pkg(1):
- name
- The package's runtime content: every subpackage except
debug,debugsrc,source,man,doc,info,lang,dev,staticdevandserver.--closure whichbundleswhich/bin. This is the install rule, not the buildroot rule above. - name/subpkg
- Exactly that subpackage, as in
--closure which/man. The name is split at the first/, socoreutils/lang/afis coreutils' subpackagelang/af. +set- Every member of a set published in the chain.
A name is resolved by chain position: the first repository in the chain that carries it supplies it. That's why the output repository has to sit above the repositories it was built against, and why --from-repo-dir puts it on top. name=version is refused, because a bundle is built from the chain's current set. No spec takes an @repo suffix either.
Without --closure, every package the output repository lists has to resolve through the chain to exactly the build it lists. The same bytes, not just the same version. If the repository isn't in the chain, or a higher-priority repository carries the same name, the bundle is refused and the packages are named. Silently carrying some other build would be worse.
OPTIONS
- recipe-dir
- Required. The recipe directory to build.
--outDIR- Required. The local repository to publish into. Created on first use.
--withSPEC- Can be repeated. An extra package for the buildroot, name or name/subpkg, staged the way a
[build_deps]entry would be. It's for trying out a build dependency before you declare it. A recipe that needs it should say so. --trust-keyHEX- Can be repeated. An Ed25519 public key trusted to sign buildroot manifests, on top of the keys that signed this machine's installed packages and your own build key. You need it on a machine that has nothing installed from the chain it builds against.
--workDIRScratch directory for the sysroot, the source cache and the build tree. Defaults to $XDG_CACHE_HOME/pkg/build/<name>-<version>-<release>, or ~/.cache/pkg/build/... without it. As root, it's /var/cache/pkg/build/..., whatever the environment says. A sysroot runs to hundreds of megabytes, which is why the default isn't
/tmp.The tree is removed after a successful build unless you pass
--keep. So a directory you name here has to be new, empty, or one pkg made itself (it carries a .pkg-work marker). Anything else is refused before it's touched.--keep- Keep the scratch directory after a successful build. A failed build always keeps it, and tells you where.
--replace- Retract a build of the same N-V-R from
--outbefore publishing, instead of refusing the new one.
All global options apply; see pkg(1). -v reports each phase with its counts. The build log itself always goes to stderr.
OUTPUT
published 5 subpackage(s) into /home/u/work/out which/bin 2.25-9.x86_64 which/debug 2.25-9.x86_64 which/debugsrc 2.25-9.noarch which/info 2.25-9.noarch which/man 2.25-9.noarch trust anchor /home/u/work/out/metadata/1.root.json (key 63d3...cdff)
JSON
{ "ok": true, "out": "/home/u/work/out", "root_json": "/home/u/work/out/metadata/1.root.json", "public_key": "63d3...cdff", "packages": [ { "name": "which", "subpkg": "bin", "version": "2.25", "release": 9, "arch": "x86_64", "payload_sha256": "d830...d6c5" } ] }
EXAMPLES
A first build
pkg keygen pkg build ./which --out ~/work/out
Iterating on a recipe
$EDITOR ./which/recipe.toml pkg build ./which --out ~/work/out --replace
Trying a build dependency before declaring it
pkg build ./foo --out ~/work/out --with zlib --with xz/dev
Looking inside a build
pkg -v build ./which --out ~/work/out --keep ls ~/.cache/pkg/build/which-2.25-9/sysroot
Installing what you built
With the output repository in the chain (see USING THE OUTPUT):
pkg install which pkg query which
Handing it to someone
pkg bundle create --from-repo-dir ~/work/out --output which.pkgtar
Just the binary and what it needs:
pkg bundle create --from-repo-dir ~/work/out \ --output which-bin.pkgtar --closure which
EXIT STATUS
0 when every subpackage was published. 1 on any failure: no key, a recipe that doesn't load, no probe that applies, a buildroot the chain can't supply, a failed build phase, or a refused publish.
ENVIRONMENT
XDG_CONFIG_HOME- Where the build key lives. Falls back to ~/.config.
XDG_CACHE_HOME- Base for the scratch directory. Falls back to ~/.cache. Ignored when running as root.
UR_SYSUSERS_BIN- The
systemd-sysusersbinary to run against the sysroot. The build host needs it, and unshare(1) from util-linux as well.
FILES
- $XDG_CONFIG_HOME/pkg/build.key
- The build key. See pkg-keygen(1).
- $XDG_CACHE_HOME/pkg/build/
- Default scratch space, one directory per N-V-R. /var/cache/pkg/build/ for root.
- <out>/metadata/1.root.json
- The output repository's trust anchor.
SEE ALSO
pkg(1), pkg-keygen(1), pkg-bundle(1), pkg-install(1), pkg-provenance(1), pkg-repo(1), pkg-repos.d(5), pkg-chain(7), pkg-sets(7), pkg-subpkgs(7), pkg-repository-layout(7)
docs/components/pkg-cli.md, docs/components/pkg-build.md, docs/design/buildroot.md, docs/design/recipes.md