This chapter is for anyone whose KDOS fetch, build or push has failed: a contributor adding or
bumping a port, or someone building the system from source for the first time. Most failures in
this repository are one of a few dozen recurring problems, and many of them report something
other than what is wrong: a missing type that is an empty generated header, a compiler that “cannot
create executables” when the compiler is fine. The chapter catalogues those problems by the message
you will see, and for each gives the cause and the fix that works in this repository. It assumes you
know how a build is run (Developing) and what a recipe looks like
(Writing ports); The build system explains the phases, the
chroot and the orchestrator that the entries below refer to, and
How KDOS is built tells the whole build as one story for a first read.
Start with Reading a failure, then find your message in the
symptom index and follow its link. The entries are grouped by where the failure
arises: fetching and publishing sources, the orchestrator and the build tree, toolchains and
language runtimes, the base userland, and upstream build systems. If nothing matches, work
through When the failure is not here at the end.
Reading a failure
A build is a sequence of steps, and every step writes its whole output to its own log:
build/logs/<phase directory>/<NNNN>_<name>.log
For a script step, <name> is the script’s file name without its numeric prefix
(build/logs/10_bootstrap/0000_file_system.sh.log); for a port built from a phase’s package list it
is the port name followed by .install (build/logs/42_graphics/0123_mesa.install.log). The
orchestrator names the failing step, and in its failure panel O opens the log and C copies the
path. The message at the very end of a build’s output is usually not the error: read upward from the
end of the log. Three other files sit under build/logs/: snapshots.log (every notice the build
showed), chroot.log (warnings from script/chroot/exec.sh) and <phase>/expansion.log (why
a phase’s package list could not be resolved). Step logs has the
details.
A few terms recur throughout. kpkg is KDOS’s package manager: it builds each port from its
recipe and installs it, and kpkgdepends is its dependency solver. KDOS uses musl as its C library
in place of glibc, and the GNU tools (coreutils, grep, sed, gawk, findutils, diffutils, patch, tar)
as its base userland. Preflight is testing/preflight.sh, a static check of the
repository’s wiring. The target tree is build/fs, the root filesystem the build assembles and
every phase from 10_bootstrap onward installs into. A shelf is the subject directory an upstream
port is filed in, ports/core/<shelf>/<port>/; a port is always named by its bare name, and
ls -d ports/core/*/<port> shows where it is filed. The Glossary
defines the rest.
When a port’s own build fails, kpkg leaves its work directory in place: the unpacked source is
at /var/cache/kpkg/work/<name>/<name>-<version>/ inside the target tree, which is
build/fs/var/cache/kpkg/work/… on the host. A configure script’s config.log is there. It stays
until that port is built again, which empties it first, or until 70_image removes the whole work
directory from the image; a later phase does not clear it. The
target tree is owned by root, as the shipped system is, so reading it from the host needs a
container or sudo.
Two checks run without a build and catch whole classes of failure before one starts:
testing/preflight.sh # the wiring: layout, phase lists, dependencies, hashes, meson options, recipe syntax
make fetch-check # offline: every archived source present and matching its hash
testing/preflight.sh takes two to three minutes. Run it after make fetch, because several of
its checks read the fetched tarballs and skip a port whose tarball is absent. make fetch-check
hashes every archived source, which takes about a minute. preflight.sh
lists everything preflight checks.
Symptom index
What you see
Section
Source not found: <file> (checked <port directory> and /var/cache/kpkg/sources)
kdosbuild: <phase> has both packages.txt and packages.d/, holds no *.txt, has no packages.txt, no packages.d/ and no *.sh, or no phase under script/phases/
'sem_clockwait' was not declared, or invalid operands of types 'int' and 'std::nullptr_t' at a strerror_r call, execinfo.h: No such file or directory, 'canonicalize_file_name' was not declared, or 'stat64' ... has incomplete type
no matching function for call to 'std::basic_string<char>::basic_string(const char8_t [N])', or invalid conversion from 'const char8_t*' to 'const char*'
binary file matches where grep should print a line, or a version a configure script extracted with grep comes back empty; or, in 00_cross or 10_bootstrap, invalid or incomplete multibyte or wide character from a tool reading a file
Upstream sources are not in git. A recipe names each source by its sha256 = line, and
make fetch (which runs ports/fetch) resolves every hash from the first place that has a copy
that verifies: the port directory, the local cache ports/.srccache, the kunaldawn/kdos source
archive (numbered pre-releases, sources-<N>, located through the committed index
ports/sources.idx), and finally the URL in the
recipe’s source = line. make fetch is the only networked step. make build runs its container
with --network none and mounts ports/ read-only, so the build can only read what the fetch has
already put beside each recipe. Where sources come from
and Publishing sources describe the whole arrangement.
A source that was never fetched
Source not found: <file> (checked <port directory> and /var/cache/kpkg/sources)
kpkg reads sources only from the port directory and its own source directory,
/var/cache/kpkg/sources (SOURCE_DIR in /etc/kpkg.conf). A fresh clone, a recipe edited since
the last fetch, or a branch switch that changed a version all leave a port without its archive.
make fetch-check lists every archived source that is missing or fails its hash, without touching
the network, and names the fix:
make fetch-check
make fetch # or, for one port: ports/fetch <port>
If the file is on disk under another name, the recipe names it differently from how it was saved.
A file::url entry is saved as file. The first source of a recipe, when its URL ends in a
recognised archive suffix (.tar.gz, .tgz, .tar.bz2, .tbz2, .tar.xz, .txz, .tar.zst,
.zip), is saved as <name>-<version>.<ext> whatever the URL’s own file name is, with .tgz,
.tbz2 and .txz written out as .tar.gz, .tar.bz2 and .tar.xz; every other entry keeps the
URL’s file name. See
Sources, and what each one becomes. Preflight
reports a stray file as one which no 'source =' line resolves to.
A downloaded source that fails its hash
✗ <port>: sha256 MISMATCH for <file> from <url>
expected <recipe hash>
got <hash of what arrived>
ports/fetch deletes a download that does not match the recipe and tries the next location, so
this line on its own is not a failure. A resumed download that fails its hash is first asked for
once more from zero, from the same URL. The port fails only when every location has given the
wrong bytes.
kpkg prints sha256 MISMATCH for <file> at build time. It checks every file a sha256 = line
names that is present in the port directory, not only the ones a source = line reaches, so the
usual cause is a recipe whose hash was changed after the file beside it was fetched. Run
ports/fetch <port> again: it replaces a file that does not match.
When every location disagrees with the recipe, upstream has changed the file under the same name;
forges regenerate archives, and some projects re-roll a release. If the source was ever published,
the archive still holds the bytes the recipe names, so check that the archive is reachable (see
Fetch cannot reach the archive). If it never was, compare the
new file with upstream’s announcement or signature before you trust it, then replace the
sha256 = line and publish the file.
A source with no hash
No sha256 for <file> in the recipe — refusing to extract an unverified source (KDOS_ALLOW_UNVERIFIED=1 to override)
or, from preflight, declares <file>, which is neither in the port directory nor hashed, or
ships <file> with no sha256 line.
Every source a recipe names needs a sha256 = <hash> <file> line, matched by file name. kpkg
refuses to extract a file with none, and ports/fetch can fetch such a file only from its upstream
URL, never from the cache or the archive, and warns no sha256 for <file> in the recipe. The usual
cause is a version bump whose sha256 = lines still name the old file: a bump made by hand, with
ports/update --no-fetch, or as a group bump, which rewrites the versions of every member but not
their hashes. A single-port bump through ports/update rewrites the hash itself. Fetch the new
file and record its hash:
KDOS_ALLOW_UNVERIFIED=1 lets a build extract an unhashed source while you bring a port up.
Preflight fails if any kpkgbuild mentions the variable, so it cannot stay in a recipe.
An error page saved as a tarball
Preflight reports ships <file>, whose first bytes are none of gzip, bzip2, xz, zstd, lzip, zip or
tar, or ships <file> as an empty file; without preflight, the build fails at
tar: Error is not recoverable.
A mirror that answers a download with an error page writes HTML under the tarball’s name, and an
interrupted download can leave an empty file. A hash recorded from either verifies,
since it is the hash of those bytes. Delete the file, fetch it again from a working URL, and
replace the sha256 = line with the hash of the real archive. ports/fetch itself refuses an
empty file as a download, so this arises from a hash recorded by hand.
Sources still in the git index
Preflight fails with <N> recipe-hashed archives are tracked by git — git rm --cached them, and
make fetch-check counts fewer archived sources than the recipes name, down to
All 0 archived sources present and verified when none are left.
ports/fetch treats every file in the git index as carried by git and skips it: it neither
fetches nor checks it. A source tarball still in the index, for example as a Git LFS pointer, is
therefore invisible to make fetch and make fetch-check, while ports/publish and the pre-push
hook treat an LFS pointer as a file to archive. Take the tarballs out of the index (the files on
disk stay where they are) and commit that change:
git rm--cached ports/core/*/<port>/<file>
make fetch-check then counts and verifies them, and .gitignore keeps them out of the index.
Preflight also fails if .gitignore stops covering any source suffix or ports/.srccache/.
A vendor bundle that cannot be generated
A vendor bundle, <name>-vendor-<version>.tar.xz, holds the crates, modules or packages a port’s
language would otherwise download during the build (see Vendoring).
ports/fetch generates one only when no copy that verifies exists in the port directory, the
cache or the archive. Generating needs the language toolchains at the versions the recipes name, so
by default it runs in the kdos-fetch container, built from ports/Containerfile.fetch. These
messages mean it could not:
Message
Cause and fix
cannot generate <bundle> without its source
Another of the port’s files, usually its main tarball, also failed to download, and the bundle is generated from it. Fix that download first
ports/fetch: this needs docker or podman
Neither container engine is on PATH. Install one, or set KDOS_FETCH_HOST=1 to generate with this machine’s own cargo, go, npm, pip or cabal, which then have to be the versions the recipes name
failed to build the recipe reader
ports/fetch compiles kpkg on the host to read recipes (ports/.kpkgbin/kpkg), and there is no C compiler ($CC, default cc). A plain fetch hands the whole run to the container instead; --check, --tree and KDOS_FETCH_HOST=1 need the host compiler
--tree never generates
ports/fetch --tree <dir> fetches for another checkout and does not generate bundles. Run that checkout’s own ports/fetch <port>
A sha256 mismatch on a vendor bundle
✗ <port>: sha256 MISMATCH for the generated <name>-vendor-<version>.tar.xz
expected <recipe hash>
got <hash of what was generated>
A generated bundle is held to the recipe’s hash like a download. ports/fetch packs it with
sorted names, a fixed time stamp and fixed ownership, so the same resolution produces the same
bytes twice; a mismatch means the resolution itself differed from the one the hash records: a
registry that moved, a yanked crate, a toolchain version that changed the lock file. The generated
file is deleted and the port fails, because keeping it would put a bundle nothing verifies in
front of the build.
If the bundle was ever published, it is in the archive and the fetch would not have generated it;
check that KDOS_SOURCES_BASE is not empty and the archive is reachable. If it never was, the
recipe’s hash names bytes that exist nowhere. Replace the sha256 = line with the hash printed on
the got line, build the port, and run ports/publish <port> so every other clone fetches that
bundle instead of generating its own.
Pre-push refused: the ports layout
pre-push: the ports layout is broken at <commit>:
ports/core/<name>/kpkgbuild: not at ports/core/<shelf>/<name>/kpkgbuild
port <name> is in two places: <path> and <path>
shelf <shelf> is not listed in ports/shelves
pre-push: (KDOS_SKIP_LAYOUT_CHECK=1 git push … skips this check)
Before it checks the archive, the pre-push hook reads the tree at the tip of each pushed ref that no
remote already holds, with no network, and refuses the push when the ports tree breaks the layout every
tool assumes. Each line names one breach:
Line
Fix
…/kpkgbuild: not at ports/core/<shelf>/<name>/kpkgbuild
A recipe sits directly under ports/core, or a level too deep. Move the port with git mv to ports/core/<shelf>/<name>/; the placement rules are in Writing ports
port <name> is in two places: <path> and <path>
Two directories hold the same bare name, across ports/core and the src/ areas. A port is found by name, so neither resolves; remove or rename one
shelf <shelf> is not listed in ports/shelves
Add the shelf to ports/shelves, with a one-line description, or move its ports to a listed shelf
shelf <shelf>: the name is reserved, an id is lowercase letters, digits and -, or shares its name with the port at <path>
A shelf is never libs or core, never named after a port, and uses only [a-z0-9-]. Rename it
ports/shelves is missing or lists no shelf
The commit lost or emptied ports/shelves
testing/preflight.sh checks the same rules and more, so run it first; the hook holds when
preflight was not run. KDOS_SKIP_LAYOUT_CHECK=1 skips this check only, and
KDOS_SKIP_PUBLISH_CHECK=1 skips only the archive check that follows.
Pre-push refused: an unpublished source
pre-push: these sources are named by a recipe but not in the archive at that commit:
<port>/<file> — indexed in sources-<N> as <asset> but absent (<hash>)
pre-push: publish them and commit the index first: ports/publish <port> && git commit ports/sources.idx
The pre-push hook (script/hooks/pre-push, enabled with git config core.hooksPath script/hooks)
checks every source hash the pushed commits add. Each must have a line in ports/sources.idx as of
the pushed commit, and the release that line names must hold the asset, or every one of its parts
for a split file; the line names the first asset found absent. A line reading
<port>/<file> — not in ports/sources.idx means the index line is missing: the upload may have
happened, but the index change was not committed, and without it ports/fetch cannot find the
file. An index that is not format 2 at the pushed commit counts as naming nothing, and the hook
prints ports/sources.idx is not format 2; the archive is skipped first. Run the command the hook prints, which uploads from the port directory or the cache and
writes the index lines, commit the index, and push again.
Uploading needs a token with write access to the archive; the token, pacing and flags are in
ports/publish, and what the hook checks is in
The pre-push hook. Without that access, push with the check
bypassed and name the ports in your pull request, so a maintainer publishes them:
KDOS_SKIP_PUBLISH_CHECK=1 git push …
A shelf with no volume
ports/shelves FAIL
shelf <shelf> has no volume (field 2 is a positive integer; make publish-plan assigns one)
The second field of every ports/shelves line is the number of the source-archive volume,
sources-<N>, that keeps the shelf’s files, and ports/publish cannot place a file of a shelf
without one. A new shelf is written with - there, or with nothing. Run make publish-plan, which
gives a volume to each shelf without one and changes no other line, and commit ports/shelves
with the shelf. make publish-plan PUBLISH_ARGS=--dry-run shows the plan first. See
Publishing sources.
Fetch cannot reach the archive
ports/fetch asks the source archive before upstream, so an unreachable archive shows as every
archived source falling through to its upstream URL: each file prints a download line for the
archive and then one for upstream. That is slower, and fails outright with
Failed to download <file> for any file whose upstream has gone. An archive that answers 404 for a
file behaves the same way for that file: either it was never published, or the archive repository
is not publicly readable. With the archive switched off and no upstream URL for a file, the message
is <file> is in no cache, and the archive is off and no source names it.
When every archived file goes upstream and fetch printed
ports/sources.idx is not format 2; the archive is skipped first, the index is of another format
or was edited by hand; testing/preflight.sh names the lines it objects to. A file in parts
prints one download line per part. A part that fails its own hash sends the whole file upstream,
and a download that stops keeps the parts already verified in ports/.srccache/.parts-<hash>/
for the next run to resume. Joining needs about twice the file’s size free on the cache’s and the
port directory’s filesystem.
The pre-push hook refuses rather than passing, since absence cannot be ruled out offline:
cannot reach the source archive at <base> (curl exit N), or
source archive unreachable at <base> (HTTP N) when the archive answers anything but 200 or 404.
ports/publish stops the same way.
Check that the machine can reach https://github.com at all. Then look at the variables
ports/fetch, ports/publish and the hook all read (from ports/srclib.sh):
The download base. A mirror or a directory laid out as <tag>/<asset>, parts as <asset>.partNN, works unchanged. Empty skips the archive and fetches from upstream alone; ports/publish and the pre-push hook then refuse to run
KDOS_SRCCACHE
ports/.srccache
The local cache, one file per hash. Files already there need no network
KDOS_SOURCES_INDEX
ports/sources.idx
Which release and asset hold each hash, read only in format 2. A hash it does not name is fetched from upstream
The orchestrator and the build tree
These failures come from how the build is run rather than from any one port: the package lists,
kpkg’s record of what is installed, the chroot’s environment, and the order in which phases have
touched the build tree.
A package list that does not resolve
<phase>: package resolution failed - see build/logs/.../expansion.log
Before a package phase runs, the orchestrator asks kpkgdepends to turn the phase’s list into a
build order. When that fails, nothing in the phase is built, and
build/logs/<phase>/expansion.log holds the solver’s output: usually a port name that does not
exist, or a depends entry naming one. Preflight checks both (every package named in a phase list
has a port, every depends entry names a port, every list resolves to a dependency order), so run
it before re-running the build.
A port filed twice
kpkg: port <name> is filed twice: /ports/core/<shelf>/<name> and /ports/core/<other>/<name>
ERROR: port <name> is in two places: <path> and <path>
The first is kpkg, kdos update or kdos cve; the second is script/lib/port.sh, in
00_cross or 10_bootstrap. A port is known by its bare name and looked up at
<repository>/<name>/ and <repository>/<shelf>/<name>/, so two directories holding the same name
inside one repository leave no right answer, and every tool refuses rather than pick one. The usual
cause is a git mv to a new shelf that left a copy behind, or a new port given a name that is
already taken. Remove one of the two. A recipe below its shelf (port <name> is nested below its
shelf) is refused the same way. Preflight and the pre-push hook both report either before a
build does.
A phase that installs a port it does not name
phase closure FAIL
<N> line(s), first: FAIL <port>: installed by 41_system (pulled in: <port> <- <port>) but not named in its list
Every package phase from 30_foundation on names exactly the ports it installs (see
Every phase installs exactly its list).
A new depends = entry, or a port removed from a list, can make a phase pull in a port its list
does not name, and testing/phaseclosure.py then names the port, the phase that installs it, the
chain of dependencies that pulled it in, and the later phase that names it, if one does. Run
python3 testing/phaseclosure.py for the whole report.
but not named in its list. Add the port to that phase’s list: in a packages.d/, the
file of its shelf, <shelf>.txt, or for a port of KDOS’s own src-<area>.txt; otherwise its
packages.txt.
but named by <later phase>. A dependency now reaches forward. Either move the port to the
earlier phase’s list, which also moves everything that needs it into order, or remove the new
dependency. The port cannot stay in the later phase: the earlier one would build it anyway.
The same run also refuses a name that is not a port on the phase’s PORT_REPO, a name listed twice
in one phase, a dependency that resolves only on a later phase’s PORT_REPO, such as a port of
src/desktop needed before 50_desktop, and a name an earlier phase installs (`is installed by
; a later phase may re-name it only in its order run`): a later phase names such a port
again only in its order run, which is `00-order.txt` in a `packages.d/` and the names a
`packages.txt` gives ahead of its first shelf banner, so that it is rebuilt there, in a pinned
order, when its recipe changes. Preflight adds that a file in `packages.d/` names only ports filed on its shelf or `src/`
area.
### A phase the orchestrator will not run
```text
kdosbuild: 41_system has both packages.txt and packages.d/: a phase reads one list, and which one would be a guess
kdosbuild: 41_system/packages.d/ holds no *.txt: the phase would install nothing and report success
kdosbuild: 41_system has no packages.txt, no packages.d/ and no *.sh: the phase would run nothing and report success
kdosbuild: no phase under script/phases/
```
`kdosbuild` names such a phase on every run and refuses to start a build, with exit status 2.
Keep one kind of list per phase, give a `packages.d/` at least one `.txt` file, or remove a phase
directory that holds nothing. The last message means `--script-dir` does not name the directory
holding `phases/`; it defaults to `script`.
### The ISO is in use
```text
ERROR: build/iso-build/kdos.iso is open by another process — a running VM?
```
`make build` refuses to start while another process holds the ISO open, because rewriting it would
give that guest I/O errors on anything it has not already cached. Shut the virtual machine down.
`make build ALLOW_ISO_IN_USE=1` overrides the check.
### A recipe change that did not rebuild its port
The build marks a port you have just changed `installed` and runs no step for it, or `kpkg` prints
`Skipping (already installed)` for it.
`kpkg`, and the orchestrator before it with the same function, decides whether an installed port
is current by comparing a *recipe hash* with the one `kpkg` recorded for the build that installed
it. That hash covers every file in the port directory, subdirectories included, except the
tarballs a `sha256 =` line names, which that line covers; a port with no `source =`, which builds
out of its own directory as the ports under `src/` do, also counts all of `src/libs`. So:
- An edit that lives outside the port directory, such as a file under `fs/` that the port
installs, changes nothing `kpkg` sees. Bump `release =` in the same change, or rebuild the port
by name.
- The hash is taken from the port directory when the build *starts*, and recorded only after the
install succeeds. An edit made while the port's rebuild is running is therefore not recorded as
built: the next build under `KPKG_STRICT_RECIPE=1` sees a mismatch and rebuilds the port.
To force a rebuild:
```sh
make build BUILD_ARGS="--phases ,70_image --rebuild "
```
### A variable that does not reach the chroot
A variable passed to `make build` changes the steps that run in the build container and has no
effect on any step that runs inside the chroot, including every packaging step.
`script/chroot/exec.sh` enters the chroot with `env -i`, which clears the environment, and names
the few variables that pass through (`HOME`, `TERM`, `PATH`, `KDOS_REPLAY`, `KDOS_JOBS`,
`KDOS_ISO_SOURCES`, `KDOS_PACK_KDOS`, `KDOS_ISO_COMP`, `KDOS_CCACHE`, `KDOS_MAKE_BINHOST`,
`KPKG_KEEP_CACHE`, `KDOS_PKG_STORE`, `KDOS_PKG_STORE_MAX`, `KPKG_STORE`, `KPKG_STORE_CHECK` and
`KPKG_STORE_SALT`). A build variable
therefore needs two edits: the `Makefile` passes it into the container with `-e`, and
`script/chroot/exec.sh` names it on the `env -i` line. See
[`env -i` means every variable must be named](/docs/kdos/05-developer/build-system.html#env--i-means-every-variable-must-be-named).
### A compiler cache under suspicion
Every CMake port in the chroot compiles through ccache into `build/ccache` (see [The compiler
cache](how-kdos-is-built.md#the-compiler-cache)). A hit is byte-identical to a compile, and a
change to gcc, binutils or an LLVM recipe misses the whole cache, so a cached object is rarely the
cause. To rule it out, rebuild the port with the cache off:
```sh
make build KDOS_CCACHE=0 BUILD_ARGS="--phases --rebuild "
```
To empty it, remove the directory from a container, since the build writes it as root:
`docker run --rm -v $PWD/build:/b alpine rm -rf /b/ccache`.
### A port that came from the package store
With `KDOS_PKG_STORE=1` the port's log says `Reusing from the store ()` and no build
ran: every input the store key covers matched a package built before (see [The package
store](../03-architecture/packaging.md#the-package-store)). A package that behaves like an older
build of itself, after a change the key cannot see (a file under `fs/`, a library the port finds
without declaring it and does not link, a `dlopen`ed plugin), is the usual reason to suspect it. To
build it regardless:
```sh
make build BUILD_ARGS="--phases --rebuild " # forced: -f never reads the store
make build KDOS_PKG_STORE=check BUILD_ARGS="--phases " # build every hit, log differences
```
`check` writes each port whose rebuild differed from its stored package to
`build/logs/pkgstore-check.log`; see [Testing](/docs/kdos/05-developer/testing.html#reading-pkgstore-checklog). `: store
key unknown ( is installed with no .pkgsha)` means a dependency was installed before the store
recorded package hashes; the port builds and is not stored until that dependency is rebuilt. To
empty the store, remove `build/pkgstore` from a container, as for the compiler cache.
### A package manager older than its source
`kpkg` is not a port. `script/phases/10_bootstrap/120_kpkg.sh` compiles it straight into the target
tree and records a hash of its sources in `build/mark/bootstrap/kpkg`. A run that includes
`10_bootstrap` recompiles it when that hash changes; a run that starts later, such as
`--continue-from 41_system`, never does,
so every later install and upgrade uses the older binary in the target tree. The current `kpkg`
keeps a file another installed package still lists (`Keeping : claims it`); an
older one can remove it as an orphan instead. The tool is then missing for every port built after
it: a removed `cmp`, for example, makes bzip2's test fail with
`make: cmp: No such file or directory`.
Recompile `kpkg` in the target tree in place before continuing. A plan that names a step suppresses
snapshots and sets `KDOS_REPLAY=1`, so the step's "already built" guard stands down and the
`10_bootstrap` snapshot is not overwritten:
```sh
make build BUILD_ARGS="--phases 10_bootstrap --steps 10_bootstrap:120_kpkg.sh"
```
Files an older `kpkg` has already removed come back only when their owning port reinstalls. That
needs a recipe change to the port, or a `--rebuild` of it.
### Helpers compiled against the other C library
`ports/fetch`, `ports/publish` and `ports/update` compile helpers on first use and keep them in
`ports/.kpkgbin/`, `ports/.portup` and `ports/.portup-tools/`. `ports/.kpkgbin/kpkg` is recompiled
when any of its sources is newer than the binary, and `ports/.portup` when a `.c` or `.h` file
under `src/devtools/kdos-portup` or the libraries it links is; `ports/.portup-tools/kpkg` is recompiled only when it is missing or
fails to execute. Run one of those tools inside a container that mounts the repository read-write,
such as an Alpine-based development container, and the helpers are left compiled against that
container's C library; a binary built against musl does not run under glibc, or the reverse, and the
failure does not say why. `ports/.portup-tools/kpkg` then fails to execute and is rebuilt on its
next use, but `ports/.kpkgbin/` and `ports/.portup` are kept as long as their sources are older.
Delete those two (from a container, if a container running as root left them owned by root) and let
the next run recompile them. `ports/.srccache` is plain data and does not need clearing.
[Where the build puts things](/docs/kdos/05-developer/developing.html#where-the-build-puts-things) has the details.
### A snapshot the orchestrator refuses
After each phase the orchestrator archives the build tree to `build/snapshots//` (see
[Snapshots](/docs/kdos/05-developer/build-system.html#snapshots)). A snapshot it cannot take is reported as a notice, shown
in the build and kept in `build/logs/snapshots.log`, and that phase has no snapshot to restore or
continue from:
| Message | Cause and fix |
|---|---|
| `snapshot FAILED: only free, need ~` | A snapshot is staged beside the one it replaces, so the free space on `build/snapshots` must hold its estimate plus a fifth: the allocated bytes of the files it will archive, the whole tree for a full snapshot or only the changes for a layer, times the base's compression ratio (a half with no base). Free space, delete old snapshots from the startup picker (`D`) or with `--delete ` (the only way to remove a snapshot `--list` marks as a leftover or unusable), or run `make cleanbuild` (keeps `build/snapshots`) or `make clean` (removes them); both keep `build/keys`. A snapshot other snapshots layer on is held, not freed, until they go; `make snapshots` shows what is held and why |
| `snapshot FAILED: build/ is mid-restore of ; refusing to snapshot it` | `build/.restore-in-progress` exists: a restore was interrupted and the tree is part-extracted. A new build refuses to start in the same state with `a restore of never finished - build/ is inconsistent.` Restore a snapshot again, or run `make cleanbuild` |
| `snapshot FAILED: mounts still active under build/fs: ` | Something is still mounted under `build/fs`, and it is not the chroot wrapper's: `script/chroot/exec.sh` and `script/chroot/enter.sh` mount in a private namespace of their own and leave nothing behind, even when killed. Look for a bind made by hand or by a wrapper from another tree. The orchestrator first unmounts what it finds itself, lazily if it has to (`released leftover mount(s)`), and names at most three it could not release. Find what holds them, stop it, and unmount the paths |
| `refusing to remove : a directory above it is a symlink` | A restore was deleting what a layer's `.gone` list names, and a directory above that path is a symlink in the tree the chain has extracted so far; deleting through it would delete outside `build/`. A chain `kdosbuild` wrote never produces this, so the layers do not belong together. The restore stops with `build/.restore-in-progress` in place: restore an earlier phase, and delete the snapshot that failed with `--delete ` |
| `refusing removal of '' listed by ` | A `.gone` list names a path outside the snapshot path, or with a `..`, `.` or empty component. It is refused before anything is deleted. The snapshot was not written by `kdosbuild`, or was edited; delete it with `--delete ` |
| `the snapshot of is unusable: a snapshot its layers are built on is missing` | The phase's snapshot is a layer, and a snapshot in its chain is gone or damaged, so it cannot be restored and is not offered. `make snapshots` lists it under `unusable` with the base it misses. Restore another phase, or delete it with `--delete `; its next snapshot is taken again by the build |
| `deleted ; kept as the base of ` | Not a failure. The deleted snapshot is a base of the snapshots named, so it moved to `build/snapshots/.held/` and they still restore. It is deleted with the last of them |
### Snapshots the orchestrator does not see
The startup picker offers only *start fresh*, `--restore` answers `no snapshot for `, and
`10_bootstrap` re-runs every step, although `build/snapshots/` is full.
Snapshots, logs and timing history are filed by phase directory name, and the orchestrator looks
only under the names of the directories in `script/phases/`. State filed under a name no phase
directory has, such as a `build/` shared with a checkout whose phases are named differently, is
invisible to it. For each phase, file its state under the name of the phase's directory: the
snapshot directory under `build/snapshots/` and the `phase` and `phase_dir` fields of its
`manifest.json`, the log directory under `build/logs/`, the phase and step keys of
`build/snapshots/timings.json`, and the names in `build/.devplan.json`. The two host phases'
markers are filed by the `MARK=` directory their `phase.env` names, `build/mark/cross` and
`build/mark/bootstrap`, and a marker under any other name makes `10_bootstrap` re-run its steps.
The same symptom has a second cause: a `kdosbuild` older than the snapshots. Snapshots are written
with manifest schema 4, whose per-path array is `paths`; a binary that knows only `entries` reads
every such snapshot as absent. That is the case when `build/.kdosbuild` was compiled from an older
checkout, as a host copy kept with `KDOSBUILD_BIN` can be. `make snapshots` recompiles it from the
current sources whenever they changed, and `make build` does the same in the build image.
### Re-running an early phase on a later tree
`10_bootstrap` run on a target tree that has already been through the userland phases works on
files later phases have replaced. Its scripts skip on their markers (see
[Guards in the early phases](/docs/kdos/05-developer/build-system.html#guards-in-the-early-phases)), but one whose guard
stands down installs its early build into `build/fs` over the newer versions, and a failure that
follows looks like an ordinary build error. A full run would also overwrite that phase's snapshot
with the later tree filed under its name. Use `--continue-from` or a
narrowed plan instead; a plan that narrows the run suppresses snapshots unless `--snapshot` is
given. See [Snapshots](/docs/kdos/05-developer/build-system.html#snapshots) and [Build plans](/docs/kdos/05-developer/build-system.html#build-plans).
### A port that needs `--port-jobs 1`
A port that builds in a serial run and fails under `--port-jobs`, or builds there without a feature
it has in a serial run, is missing a `depends` entry for a port of its own phase. Under
`--port-jobs` a port builds against the earlier phases and the lower levels only, and its level
comes from its `depends` line; in a serial run the list order may have installed the undeclared
dependency first. Compare the port's step log with the serial run's for a configure check that
found a library in one and not the other, name that port in `depends`, and the level follows. This
is the same defect as [A dependency the list happened to satisfy](#a-dependency-the-list-happened-to-satisfy),
found sooner.
A failed level is not committed: the ports that built are held in the package cache as
`.pending` records, and a resumed build (`--continue-from `) reuses those whose recipe
hash has not changed. The failing port's own log is the step log named in the failure, as in a
serial run.
The notice ` took paths from ; the serial order had last — pin before
in 00-order.txt` means a recipe's `replaces =` took paths from a port of the same phase that
a level had committed first, while the serial run would install that port after the taker and
refuse it as a conflict. Pin both in the phase's order run, `00-order.txt` or the names ahead of a
`packages.txt`'s first shelf heading, the replaced port first; the order run builds one port at a
time.
`its kpkg has no --build-only; building one port at a time` means the `kpkg` in the tree predates the
two-part install, as one restored from an early snapshot can. The phase runs serially and is
correct; the next build of `kdos-kpkg` brings the option.
---
## Toolchains and language runtimes
KDOS is built on musl, with GCC as the system compiler (`CC=gcc` from `30_foundation` onward), LLVM
and Clang as ports, and Rust linked against the system LLVM. Most of the failures in this group
come from an upstream that assumes glibc, or a toolchain configured by guesswork.
### Rust with a binding generator
A Rust crate fails with `Dynamic loading not supported`, or its final link fails with undefined
references into a library's `.a` (`libcurl.a` and its `nghttp2_*`).
The musl target links statically unless told otherwise. A binding generator (bindgen) then cannot
load `libclang` at build time, and a crate that links a system library takes that library's
static archive without the libraries it depends on. Where the link succeeds, the binary carries a
private copy that no update to the library's port reaches. Every recipe under `ports/core` that
runs `cargo build`, `cargo install`, `cargo cbuild` or `cargo cinstall` sets the flag, and
preflight fails one that does not:
```sh
export RUSTFLAGS="-C target-feature=-crt-static"
```
`RUSTFLAGS` reaches build scripts only when cargo builds for the host. Given `--target`, as
Firefox's mach always gives it, cargo passes `RUSTFLAGS` to the target's crates alone, and a build
script that runs bindgen is linked statically again. Such a recipe also sets `RUSTC_WRAPPER` to a
script that runs rustc with the same flag appended: cargo runs every rustc call, host and target,
through it. `firefox-esr`, `librewolf` and `thunderbird` are the recipes that do.
A recipe whose crates run bindgen also points it at `libclang`, which saves it a search:
```sh
export LIBCLANG_PATH=/usr/lib
```
### An LLVM that guessed its triple
`fatal error: 'cstddef' file not found` from clang, or from bindgen through libclang, while gcc
compiles the same header.
`clang -print-target-triple` answers with the triple compiled into LLVM. When the `llvm` port does
not set one, LLVM guesses, and on this system the guess is `x86_64-unknown-linux-gnu`. Clang then
looks for a gcc installation under that triple, finds none beside gcc's `x86_64-pc-linux-musl`,
and searches no C++ header directory at all; it would also link against glibc's loader. The `llvm`
and `llvm21` ports pass gcc's own triple, `$(cc -dumpmachine)`, as `LLVM_HOST_TRIPLE` and
`LLVM_DEFAULT_TARGET_TRIPLE`. Clang and libclang compile the default in from LLVM's
`llvm-config.h`, so a changed LLVM triple needs `clang` rebuilt as well. A stale clang shows as
the wrong answer from `clang -print-target-triple` after `llvm` is fixed.
The same error comes from a clang given another triple explicitly. The `cc` crate passes Rust's
`--target=x86_64-unknown-linux-musl` to clang, and clang looks for gcc under that triple instead.
`--gcc-triple=$(gcc -dumpmachine)` in `CFLAGS`, `CXXFLAGS` and `LDFLAGS` names gcc's own;
`firefox-esr`, `librewolf` and `thunderbird`, whose Rust crates compile C++ with clang, set it. A
gecko build without it fails on `'new' file not found` under `dist/system_wrappers`.
### A Rust release older than the system LLVM
`'+amx-tf32' is not a recognized feature for this target` from rustc, at the first
standard-library build.
The `rust` port links rustc against the system LLVM (`llvm-config = "/usr/bin/llvm-config"` in
the configuration its `build.sh` writes), not the copy in its own tarball. When the system LLVM is
a major version ahead of the one a Rust release was cut against, rustc can name something that
LLVM does not have: a target feature, a pass, an intrinsic. Upstream's fixes for the newer LLVM
land on rustc's development branch first, and the port carries those commits as patches beside
the recipe until a release contains them. The port carries two for LLVM 23:
| Patch | What it fixes |
|---|---|
| `rust-llvm23-amx-tf32.patch` | Drops the AMX-TF32 target feature and its intrinsics, which LLVM 23 does not have |
| `rust-llvm23-assign-guid.patch` | Adds the pass that assigns the global identifiers ThinLTO's summaries are keyed on, where rustc writes ThinLTO bitcode; LLVM 23 does not run it unless the pipeline asks |
Remove a patch from the recipe when the Rust version it targets includes the change; the patch
then fails to apply, and the build stops at that line.
### A crate newer than the toolchain
`rustc is not supported by the following packages`.
Cargo refuses a crate whose declared minimum Rust version is higher than the toolchain, rather
than degrading. The repository pins the toolchain (the `rust` port), and the `kdos-fetch` container
installs the same version, read from the `rust` recipe, because a Cargo newer than the one that
will compile the port can write a lock file the target's Cargo refuses. The container reads its Go,
Node.js, GHC and cabal versions from their recipes for the same reason.
Pin the port to the newest release that builds. Bumping the toolchain instead means bumping the
`rust` port and regenerating every Rust vendor bundle against it, which touches every Rust port
rather than one. The declared minimum is not an oracle: it gates the refusal and says
nothing about what the code uses, so a release declaring an older minimum can still fail on a
language feature stabilised later. The only reliable test is compiling. See
[A Rust port's version is pinned by this tree's compiler](/docs/kdos/05-developer/writing-ports.html#a-rust-ports-version-is-pinned-by-this-trees-compiler).
### A vendor bundle in the wrong place
`no matching package named ''` under the offline build, with the crate sitting in the vendor
directory the whole time.
Cargo finds its configuration by walking up from the current directory. A build that invokes it
from a subdirectory, or with an explicit manifest path from a parent, never reads a configuration
placed next to that manifest. Unpack the bundle where the tool will be standing. The recipe's
`vendordir` key says where the *vendoring* must run, which is beside the manifest; the two
directories are not always the same place.
The same message names a crate that is missing from the bundle when the build runs a second Cargo
manifest outside the workspace, such as an `xtask` crate that writes a manual page: it resolves
against its own lock file. List every such manifest in `vendorsync`, relative to `vendordir`, and
`ports/fetch` passes each to `cargo vendor --sync`. See
[Where the bundle goes](/docs/kdos/05-developer/writing-ports.html#where-the-bundle-goes).
### A Python backend resolving a system tool
A build system is compiled from source inside a step that is supposed to be downloading.
`pip download --no-binary :all:` builds each source distribution's metadata while it downloads,
and a build backend that cannot find a system build tool on `PATH` resolves it as a package of the
same name from the index. The index's `cmake` package is CMake's entire source tree, compiled from
scratch; its `patchelf` is patchelf's source tree with an autotools build inside it.
Two fixes, and which applies depends on the tool:
- Give the fetch image what a metadata build needs, so the installer never reaches for the index's
copy of a system tool. That is why `ports/Containerfile.fetch` carries `cmake`, `ninja-build`,
`patchelf`, a compiler and the `python3-dev`, `libffi-dev` and `libssl-dev` headers.
- Name the backends as ports and disable build isolation, when they are packages KDOS should
have anyway. Most Python recipes install with `--no-build-isolation`.
Where a recipe names an explicit closure with `pypackages`, `ports/fetch` vendors it with
`--no-deps`: letting pip resolve would drag in every dependency that is already a port and build
each one's metadata to find that out.
### A language standard reaching the compiler's own runtime
`unknown type name 'bool'` in a header under `gcc/config/`, while `libgcc` is being compiled.
Every phase's `CFLAGS` pins a C standard older than C23 (`-std=gnu99` in `00_cross` and
`10_bootstrap`, from `script/env/host.env`, and `-std=gnu11` from `20_selfhost` onward, from
`script/env/chroot.env`). GCC's configure copies `CFLAGS` into
`CFLAGS_FOR_TARGET`, the flags for the runtime libraries it builds with the compiler it has just
built. That runtime includes the target's own headers, which are written for the new compiler's
default standard and use `bool` without ``.
`script/phases/10_bootstrap/100_gcc.sh` and the `gcc` port each pass `CFLAGS_FOR_TARGET` with the
`-std=` flag removed:
```sh
CFLAGS_FOR_TARGET="${CFLAGS/-std=gnu[0-9][0-9]/}"
```
The bare-metal compilers take nothing from the host's flags. `gcc-arm-none-eabi`, `gcc-avr`,
`gcc-riscv64-unknown-elf`, `libstdcxx-arm-none-eabi` and `libstdcxx-riscv64-unknown-elf` pass
`-Os -pipe -ffunction-sections -fdata-sections` as `CFLAGS_FOR_TARGET` (and, for libstdc++,
`CXXFLAGS_FOR_TARGET`), as Alpine's `gcc-cross-embedded` does. `-Os` because every byte of
`libgcc` and `libstdc++` a firmware links lands in the part's flash, which is kilobytes on an AVR
and rarely more than a megabyte on a Cortex-M; the host tree's `-O2` would grow each image for
speed a microcontroller seldom needs. `-fPIC` and
`-fstack-clash-protection` would make every microcontroller's `libgcc` position-independent and
probed, and the per-function sections let `--gc-sections` drop what a firmware image does not
call. The picolibc ports `unset CFLAGS CXXFLAGS LDFLAGS` before `meson setup` for the same reason:
meson adds the exported flags to the cross compiler's command line as well as the native one's.
### The installed C++ headers shadowing the ones being built
`'fenv_t' has not been declared in '::'`, followed by `Cannot compile std module`, in the
`10_bootstrap` GCC log. Make carries on past it, so the step succeeds and the compiler ships with no
`import std;`.
`10_bootstrap` builds the system compiler with the cross-compiler, whose own C++ headers are on its
default search path. A libstdc++ wrapper such as `fenv.h` reaches the C library's header with
`#include_next`, and the next directory holding that name is the cross-compiler's copy of the same
wrapper. Its include guard is already set, so the C library's declarations never arrive.
`script/phases/10_bootstrap/100_gcc.sh` passes `CXXFLAGS_FOR_TARGET="$CXXFLAGS -nostdinc++"`, which
leaves only the headers of the libstdc++ being built. GCC's own configure adds `-nostdinc++` when it
builds the target libraries with the compiler in its own build directory; `10_bootstrap` builds them
with the cross-compiler instead, so the flag has to be passed.
### Gettext on musl
`undefined reference to libintl_gettext` (or `libintl_ngettext`) at link, from a program that
compiled without complaint.
musl has a `gettext()` in libc, but the GNU `libintl.h` the `gettext` port installs renames every
call to `libintl_gettext`, which only GNU's libintl defines; glibc provides those names and musl
does not. Upstream build systems routinely test `uname -s` and take Linux to mean glibc, so the one
platform that needs `-lintl` is the one their test excludes.
No message catalogue ships, so first look for the build's own switch: autotools' `--disable-nls`,
or a named option such as neovim's `-DENABLE_LIBINTL=OFF`. With it the program needs no `libintl`
at all; shadow and cryptsetup are built that way. Where the build has no switch (gdk-pixbuf's meson
links `intl` whenever it finds it, fcitx5 requires it), depend on `libintl` and put `-lintl` where
that project's link line will keep it. Check which variable survives before choosing one: a
`LIBS`-style variable set with `=` is clobbered by the Makefile, while `LDFLAGS` is usually appended to with `+=` and sits last on the link line, which is
where a library reference belongs. Do not add `-liconv` alongside it out of habit: musl carries
iconv in libc and there is no library of that name.
`libintl` exists because the `gettext` port is built `--disable-nls` and installs the header
without the library, which is right for a system that ships no message catalogues. The `libintl`
port builds only the runtime library from the same tarball and version, so a caller's header and
library cannot describe two different gettexts.
### A kernel ioctl request missing on musl
A request such as `TCGETS2` or `TCSETS2` is undeclared, although `configure` found the structure it
reads (`struct termios2`, from `<asm/termbits.h>`). glibc's `<sys/ioctl.h>` includes the kernel's
`<asm/ioctls.h>`; musl's defines its own list of requests and leaves out the ones glibc gets from
the kernel. Add the kernel header rather than turning the feature off:
`CPPFLAGS="$CPPFLAGS -include asm/ioctls.h"` on the `configure` line. The `libmodbus` port does
this, and keeps the serial rates only `termios2` can set.
### A glibc-only call on musl
Code written against glibc alone fails on musl in a few recurring ways. All but the last need a
`.patch`:
- `sem_clockwait` and `pthread_cond_clockwait` are glibc's. Wait with `sem_timedwait` (or
`pthread_cond_timedwait`) and take the deadline from `CLOCK_REALTIME` instead of
`CLOCK_MONOTONIC`, everywhere the same deadline is computed.
- glibc's `strerror_r` returns the message as a `char *`; the POSIX one musl has returns an `int`
and fills the buffer. C++ always defines `_GNU_SOURCE`, so a test on that macro picks the glibc
form on musl too. Add `!defined(__GLIBC__)` to the test that selects the POSIX form, or use
`strerror`.
- A header that names `siginfo_t` (or another type) without including its header compiles on glibc,
whose headers include `` on the way to others. Add the include.
- `` (`backtrace`, `backtrace_symbols`) is glibc's, and musl has no such header. Where
it serves only a debugging aid, put the include and its function under `#ifdef __GLIBC__`. A
crash handler that exits after the backtrace still has to exit without it, or it returns into
the fault.
- `canonicalize_file_name(path)` is glibc's; `realpath(path, NULL)` returns the same allocated
string.
- `stat64`, `fstat64`, `lseek64` and `off64_t` exist on musl only as macros for the plain names, and
only under `_LARGEFILE64_SOURCE`, which `_GNU_SOURCE` does not imply. `aggregate '...::stat64 buf'
has incomplete type` is the sign. Define it rather than patch: `libreoffice` exports
`ENVCFLAGS=-D_LARGEFILE64_SOURCE` and `ENVCFLAGSCXX=-D_LARGEFILE64_SOURCE`, which gbuild adds to
every library it compiles, for the CLucene it bundles.
`freedv-gui`'s `musl.patch` and `backend-musl.patch` carry the first three; `labplot`'s
`musl-execinfo.patch` the fourth; `avidemux`'s `musl-glibc-calls.patch` the fourth and fifth.
### `NULL` used as an integer
`cannot convert 'std::nullptr_t' to 'uint32_t'` (or another integer type) at a `NULL`, often
the last element of an array of structures whose first member is an integer. musl's headers
define `NULL` as `nullptr` in C++, which converts only to pointers; on glibc, GCC's own ``
defines it as `__null`, which also converts to an integer. Ship a `.patch` that writes what was meant: `0`, or `{}` for
an empty terminating element. `avidemux`'s `mp4-menu-sentinel.patch` ends a menu list with `{}`.
### A private library loaded by full path, then not found
The build succeeds, and the program fails at start with `Error loading shared library libfoo.so:
No such file or directory (needed by …)`. The missing library sits beside the one that names it, in
a private directory such as `/usr/lib/firefox-esr`. The program's launcher loads the libraries
there by full path, and glibc then takes the copy already loaded for a bare-name `DT_NEEDED`. musl
looks the name up again on its search path and does not find it. Link with a run path into the
directory, `LDFLAGS="$LDFLAGS -Wl,-rpath,"`, and check `readelf -d` shows `RUNPATH` on
the libraries. The `firefox-esr`, `librewolf` and `thunderbird` ports do this.
### The wide curses API
`wget_wch`, `mvwaddnwstr` or another wide-character curses function reported as an implicit
declaration, against an ncurses that has it.
KDOS builds one ncurses, with wide-character support (`--enable-widec`), with its headers flat
in `/usr/include` and no `ncursesw/` directory. Two things follow. An include of
`<ncursesw/ncurses.h>` resolves nowhere. And `curses.h` declares the wide functions only when
`NCURSES_WIDECHAR` is 1, which it derives from `_XOPEN_SOURCE_EXTENDED`: without that define you
get the narrow API from the wide library.
Pass `-D_XOPEN_SOURCE_EXTENDED`, and for the include path build a directory of one symlink, added
with `-I`, as the `stfl` port does:
```sh
mkdir -p compat/ncursesw && ln -sf /usr/include/ncurses.h compat/ncursesw/ncurses.h
export CFLAGS="$CFLAGS -I$PWD/compat -D_XOPEN_SOURCE_EXTENDED"
```
Both are build flags; neither edits the source. Passed through the environment, they are extended
by a Makefile that appends its own with `+=`; passed on the command line, they replace that
variable, so the assignment has to carry whatever the Makefile's line added that the build needs,
such as `-fPIC`. `stfl` takes the command line, with `-I.`, `-D_GNU_SOURCE` and `-fPIC` spelled
out, so that its Makefile's `-Os -ggdb` never reaches the compiler.
### An ICU component not propagated
Break-iterator symbols (`ubrk_*`) missing at link.
The break iterator lives in ICU's core library, `libicuuc`. In a shared build, ICU's
`icu-i18n.pc` lists `icu-uc` under `Requires.private`, so `pkg-config --libs icu-i18n` does not
emit `-licuuc`, and a program that asks only for the i18n component and calls the core directly
fails to link. Add the core library:
```sh
export LDFLAGS="$LDFLAGS -licuuc"
```
---
## The base userland
KDOS's base userland is GNU at every name: coreutils as separate programs, grep, sed, gawk,
findutils, diffutils, patch and tar, cross-built in `10_bootstrap` and rebuilt as ports in
`30_foundation`. A build system that assumes GNU behaviour (`sed -i`, `expr length`,
`ln --relative`, `date --date`, `\|` in a basic `grep` pattern, a `mkdir` that configure accepts
as thread-safe) gets it from the first configure script on. `which` is GNU's; `getconf`, `getent`
and `iconv` are musl's own (the `musl-utils` port), so they answer for musl's limits, databases
and converters, not glibc's.
### A link at a copy's destination
`cp` writes through a link at the destination, and GNU `cp` refuses one that dangles with
`not writing through dangling symlink`. An absolute link inside `$PKG` names a path on the build
root, not in `$PKG`: it dangles there, or, when the package is already installed, it leads to the
installed file and the copy overwrites that instead. To replace a link with the file it names,
read it, `rm` it, then copy. The `libreoffice` recipe does this for
its `.desktop` entries, which upstream installs as absolute links into
`/usr/lib/libreoffice/share/xdg`; a copy is what preflight's name, icon and command checks can
read.
### A file installed unreadable
A package with a regular file that users cannot read, or a file in `usr/bin` with no `x` bit, is
refused by the [package lint](/docs/kdos/03-architecture/packaging.html#the-package-lint). An unreadable
file in `/etc` or `/usr` breaks every non-root user of it, and silently: `git` stops on an
unreadable `/etc/gitconfig`, `/etc/profile` skips an unreadable `profile.d` script, and
`pkgconf` fails every module that `Requires` an unreadable `.pc`. Root reads the file, so nothing
in the build notices.
GNU `install` writes the mode `-m` names, whatever the source, `/dev/stdin` included, so
`install -Dm644` in a recipe is enough. A file a build writes with a generator, a heredoc or
`cp` takes the generator's mode or the umask's: neverball's `mapc` writes its compiled levels
0600, and its recipe follows the copy with `chmod -R a+rX`. A file that must stay private, such
as a credential, belongs on the lint's allowlist, with the reason, in `src/system/kdos-kpkg/lint.c`
and in the table on that page.
### A package the lint refuses
The build ran and the staged tree broke a packaging rule: a path outside the layout, a
world-writable file, a patch leftover, a pkg-config module that does not resolve, or an ELF object
with an executable stack, a text relocation or a build-directory `RPATH`. Each `ERROR: lint:` line
names the path and the rule, and no package file is written. [What the package lint
refuses](writing-ports.md#what-the-package-lint-refuses) gives the fix for each rule; fix the
recipe and build again.
---
## Upstream build systems
The remaining failures come from the port's own build system meeting KDOS's constraints: no
network, musl, a newer compiler than upstream tested, and a parallel `make`.
### A build that reaches the network
`is the internet available?`, `cannot compute hash on failed download`, or `Could not resolve host`,
hours in.
The build container runs with `--network none`. Four shapes cause this:
| Shape | Looks like |
|---|---|
| A meson subproject fallback | `script/bin/meson` gives every setup `--wrap-mode=nodownload`, so the setup stops with the subproject's name instead of fetching its wrap |
| A CMake download call | A hash computed on a failed download |
| A dependency fetched from version control at configure time | A clone attempt |
| A Python build backend resolving a package | A package index request |
Which fix is right depends on what the thing being fetched actually is:
- A real library becomes a port. Look for the project's own escape hatch first; many have a "use
the system copy" switch. A meson subproject is never fetched: the setup fails on it, and the
library's port goes in `depends`, or `--wrap-mode=nofallback` makes the system copy required.
- A header-only submodule nobody else uses becomes a second `source`, extracted where the probe
already looks.
- A build-time generator becomes a host dependency.
CMake's `FetchContent` has a generic override: `-DFETCHCONTENT_FULLY_DISCONNECTED=ON` forbids
downloads, and `-DFETCHCONTENT_SOURCE_DIR_=` points one dependency at a directory you
supply. The `libavif` port does this for `libargparse`, which it carries as a second source.
`minizip-ng` does it for PPMd, which it clones from 7-Zip whatever its own `MZ_FETCH_LIBS` says:
its second source is the 7zip port's tarball under the same name and hash, so the archive holds
the file once and both ports read it.
The `qemu` port is the worked example of the fourth shape. Its configuration builds a Python
environment and, with downloads enabled (its default), drops the offline flag, so the installer
goes to the network for packages vendored in the tarball itself and fails naming whichever it
asked for first. `--disable-download` is the flag. It also stops a device-tree submodule being
fetched, so a target that needs one then fails configuration; the port supplies the library
instead with `--enable-fdt=system` and a dependency on the `dtc` port.
### An unknown meson option
`Unknown options` at setup, before a line is compiled.
There is no universal spelling; one project's test-disable option is fatal in the next. Read the
option file (`meson_options.txt` or `meson.options`) out of the tarball, and use meson's built-in
options when you want a meson-level setting, because those are always valid. Preflight checks every
`-D` a recipe passes against the port's own option file, so the mistake is caught in the few
minutes preflight takes rather than when the port's turn comes in the build.
### A meson feature given a boolean
A configure-time error saying the value is not one of the choices, on a line that looks correct.
meson's *feature* type takes `enabled`, `disabled` or `auto`, and refuses a boolean. The *boolean*
type takes `true` and `false`. Both look alike in a recipe. Match the type in the option file.
Preflight validates these two types, the ones with a closed value set.
### A missing meson prefix or library directory
The build and install succeed, and at run time a shared library cannot be loaded
(`Error loading shared library`).
meson's default library directory is not on the runtime linker's search path. Pass
`--prefix=/usr --libdir=lib` on every `meson setup`.
A stale package-config file under the old prefix can also shadow the fixed one. After correcting a
prefix, delete the old files and the old package-config file, then rebuild the consumers.
### Two introspection runtimes in one process
`cannot mix girepository 1.0 (via libpeas) and 2.0 (via pygobject)`, or a program whose Python
plugins crash on load.
PyGObject from 3.52 on loads typelibs through glib's `girepository-2.0`, and a library built on
the older `gobject-introspection-1.0` runtime cannot share a process with it. Every library that
loads Python into a program must be on the same runtime as PyGObject: `libpeas` 1.38 is, and
`rhythmbox` needs it for its Python plugins.
### An old CMake policy floor
`Compatibility with CMake < 3.5 has been removed`, from a project whose `cmake_minimum_required`
predates CMake 3.5. The `cmake` port (4.x) refuses such a floor unless told otherwise:
```sh
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
```
A `-D` reaches only the CMake run it is given to. When the floor is in a library the project
builds from source through `ExternalProject`, export the variable instead, which every nested
CMake run reads from the environment: `kodi` does this for its internal PCRE.
### A policy set to its removed behaviour
`Policy CMP0026 may not be set to OLD behavior`, from a `cmake_policy(SET OLD)` in the
project. CMake 4 has only the new behaviour of the policies CMake 3.0 introduced, and no flag
selects the old one. Ship a `.patch` that drops the `cmake_policy` line, or sets it to `NEW` and
rewrites the code that depended on the old behaviour: `lmms`'s `fix-cmake-policy.patch` reads a
target's path through `$<TARGET_FILE:...>` instead of its `LOCATION`. A library the project builds from
source is patched through the project's own patch list for it: `kodi`'s `pcre-cmp0026.patch`
adds a PCRE patch to the list in `FindPCRE.cmake`, which drops the line: the `LOCATION` reads it
covered are in parts Kodi does not build.
### A JVM tool older than the JDK
`BUG! exception in phase 'semantic analysis' ... Unsupported class file major version 69`, from a
Groovy script (or another tool built on the ASM bytecode library) that the build runs on the JDK.
The tool reads the JDK's own class files, and its bundled ASM rejects a class file format newer
than it knows. Major version 69 is Java 25, the `openjdk` port. Move the tool to a release whose
ASM reads it, a new source that needs `ports/publish`: `kodi`'s binding generator needs Groovy
4.0.33 or later.
### A version test that excludes a newer major release
`too few arguments to function 'GifFileType* DGifOpen(void*, InputFunc, int*)'`, with
`'GifLastError' was not declared in this scope`, in code that supports two giflib interfaces.
The code tests `GIFLIB_MAJOR == 5` for the current interface and takes the giflib 4 calls for any
other version. The `giflib` port is 6, which keeps the 5 interface, so the test picks calls giflib 6
does not have. Ship a `.patch` that tests `>= 5` (and `< 5` for the old branch). `kodi`'s
`texturepacker-giflib6.patch` does this for its TexturePacker build tool.
### A requirement a find module misreads
`find_package called with invalid argument "1"`, or `Could not find a configuration file for
package "" that is compatible with requested version "1"`, from a find module the project
ships, with `CMAKE_REQUIRE_FIND_PACKAGE_=ON` on the command line.
The module passes `${_FIND_REQUIRED}` to its own config-package search as a bare argument,
expecting it empty or `REQUIRED`. A requirement sets it to `1`, which the search reads as a version
when none comes before it, and rejects as an unknown argument when one does. Leave that package
unrequired and, after the configure, check the generated build for the definition it enables, as
`labplot` does for Cantor and NetCDF:
```sh
grep -q -- -DHAVE_NETCDF build/build.ninja
```
### A Qt private module not loaded
`Missing Qt6::GuiPrivate`, or a link to a `Qt6::Private` target CMake does not know, from a
project that asks for Qt's private API through `find_package(Qt6 ... Private)` or only
tests `TARGET Qt6::Private`.
Qt keeps each private module in its own package, `Qt6Private`, and finding the public one
does not load it. `-DQT_FIND_PRIVATE_MODULES=ON` makes every Qt module found load its private
counterpart too (`labplot`).
### Eigen without its legacy variables
`Eigen3 library NOT FOUND`, or Eigen's headers not found, with the `eigen` port installed.
The Eigen 5 config file defines the `Eigen3::Eigen` target only. A project that tests
`EIGEN3_FOUND` or reads `EIGEN3_INCLUDE_DIR` or `EIGEN3_VERSION_STRING` gets none of them. Keep
`CMAKE_REQUIRE_FIND_PACKAGE_Eigen3=ON`, so a missing library still stops the configure, and give the
three from `eigen3.pc`, as `labplot` does:
```sh
-D EIGEN3_FOUND=ON \
-D EIGEN3_INCLUDE_DIR="$(pkg-config --variable=prefix eigen3)/include/eigen3" \
-D EIGEN3_VERSION_STRING="$(pkg-config --modversion eigen3)" \
```
A project that falls back to its bundled Eigen when the variables are missing builds with no
error and ships its own copy; `solvespace` names `EIGEN3_INCLUDE_DIRS` for that reason. Eigen 5
also needs C++14, so a project that sets `CMAKE_CXX_STANDARD` to 11 stops with
`#error Eigen requires at least c++14 support`. A normal `set()` in the project overrides a `-D`
cache value, so the standard is raised by a patch to that line (`solvespace`'s
`eigen5-cxx14.patch`).
### A plugin built with no compiler flags
`fatal error: glib.h: No such file or directory` (or another dependency's header) in one plugin
of a project whose plugin list the recipe names, where the rest of the build found that header.
The plugin's configure fragment declined it for this configuration and set no flags, but the
explicit list still puts it in the build. AbiWord's `rsvg` plugin is declined under the GTK front
end, which draws SVG itself, and `abiword`'s list leaves it out. Read the plugin's `plugin.m4` (or
equivalent) for the condition, and drop the plugin from the list.
### A Qt 5 program built against Qt 6
`'class QTextStream' has no member named 'setCodec'`, `'QRegExp' was not declared in this scope`,
`no matching function for call to 'QString::indexOf(QRegExp)'`, across many files of a program
whose project file adds `core5compat` under Qt 6.
The project file accepts Qt 6, but the code does not: Qt 6 removed these interfaces, and
`core5compat` restores only some of them, and only where a file includes their header. Read the
packaging files upstream ships (`*.spec`, `debian/control`) for the Qt it builds with. When that
is Qt 5, depend on the `qt5-*` ports and run `qmake-qt5` with Qt 5's `lrelease` from
`/usr/lib/qt5/bin`. `smplayer` is built this way.
### A source file in a moc list
`qt6_wrap_cpp: TARGET parameter is empty. Since the file .../X.cpp is a source file, the TARGET
option must be specified.` Qt 6 runs moc on a `.cpp` file only for a named target, so it can add
the target's build directory to the include path for the `X.moc` the file includes. A `.cpp`
with no `Q_OBJECT` class gives moc nothing to generate: ship a `.patch` that drops it from the
list. One with a `Q_OBJECT` class keeps it and gains `TARGET `, after that target is
declared. `avidemux`'s `qt6-moc-headers-only.patch` drops two of the first kind.
### An older standard after CMake's
`#error "Qt requires a C++17 compiler"`, then `'is_integral_v' is not a member of 'std'` from Qt
6's headers, with `-std=c++17 -std=c++11` on the compile line. The project asks CMake for C++17
through `CMAKE_CXX_STANDARD`, but also passes `-std=c++11` through `add_definitions()` or
`add_compile_options()`. CMake puts compile options after the standard flag it derives, so the
older one wins, and no `CXXFLAGS` value can come later. Ship a `.patch` that drops the option:
`avidemux`'s `qt6-no-cxx11.patch`.
### FLTK's headers needing Cairo's
`/usr/include/FL/Fl_Cairo.H:40:11: fatal error: cairo.h: No such file or directory`, compiling an
FLTK interface.
The `fltk` port is built with Cairo, so `FLTK_HAVE_CAIRO` is set in `FL/fl_config.h` and its
headers include ``, which sits in `/usr/include/cairo`. `fltk-config --cxxflags` names that
directory, but a project that finds FLTK through CMake's `FindFLTK` gets FLTK's include directory
alone. Add Cairo's flags to the C++ flags and `cairo` to `depends`: `lmms` appends
`$(pkg-config --cflags cairo)` to `CMAKE_CXX_FLAGS` for its ZynAddSubFX editor.
### A bundled header shadowed by the system's
`unsupported Lua version (i.e. not Lua 5.1, 5.2, 5.3, or 5.4)` from sol2, with `LUA_ERRGCMM` and
`lua_newstate` errors after it, in a project that bundles its own Lua.
The `lua` port is Lua 5.5, whose headers sit in `/usr/include`. A project that compiles a bundled
Lua puts that copy's directory first with `-I`, but a header the bundle lacks is still found in
`/usr/include`, and it then includes the system's `lua.h` from beside itself. sol2 includes
`` whenever one exists, and the bundled Lua usually ships none. `-DSOL_NO_LUA_HPP=1` has
sol2 include `lua.h`, `lualib.h` and `lauxlib.h` directly, which the bundle has (`satdump`).
### A misspelt CMake option
The option had no effect, and the build did the thing you disabled.
CMake prints a warning about manually-specified variables that were not used and carries on, the
opposite of meson, which fails at setup. Read that warning in the log rather than trusting the exit
status, and take option names from the project's own `option()` declarations.
### A CMake file with no project declaration
The library builds and installs, and then every static link against it fails on unwinder symbols.
A `CMakeLists.txt` with no `project()` gets an implicit one covering only C and C++, so assembly
sources are silently dropped and the archive is missing the routines written in assembly. The fix
is to configure from a directory whose `project()` enables the `ASM` language. LLVM's standalone
`libunwind/` directory has no `project()` of its own, so the `libunwind` port configures LLVM's
`runtimes/` directory (`project(Runtimes C CXX ASM)`) with `-D LLVM_ENABLE_RUNTIMES="libunwind"`.
### A library installed into the source tree
The install step reports `Installing: …/pkg/var/cache/kpkg/work///lib/lib….so`, and
the check that the library is under `/usr/lib` fails.
The project declares an install directory itself, with `set(CMAKE_INSTALL_LIBDIR lib CACHE PATH
…)` rather than through `GNUInstallDirs`, and the recipe passes `-DCMAKE_INSTALL_LIBDIR=lib`. A
relative value given on the command line without a type is made absolute when the project then
declares the entry a `PATH`, and it is made absolute against the build's working directory. Leave
the variable out when the project's default is already the one wanted, or give it a type,
`-DCMAKE_INSTALL_LIBDIR:PATH=lib`. `shapelib` leaves it out.
### Go building into its own directory
The install step reports `cp: -r not specified; omitting directory` on a path that looks exactly
like the binary it wanted.
Building with an output name equal to an existing directory of the same name writes the binary
inside that directory. Build into a directory of your own and install from there.
### `C compiler cannot create executables`
Exactly that, from an old configuration script.
The message blames the toolchain and is almost never about the toolchain. Read the port's own
`config.log` (see [Reading a failure](#reading-a-failure)): the real error is next to the failing
test program.
For a configuration script generated by an old autoconf, the cause is the test program's old-style
C (implicit `int`, implicit declarations, unprototyped definitions), which GCC 14 and later treat as
errors; the `gcc` port builds GCC 16. Suppress the whole family at once, because each one otherwise
costs another round trip:
```sh
export CFLAGS="$CFLAGS -Wno-implicit-function-declaration -Wno-implicit-int \
-Wno-int-conversion -Wno-incompatible-pointer-types -Wno-return-mismatch \
-Wno-declaration-missing-parameter-type"
```
### A configuration script preferring another compiler
`C compiler cannot create executables` from a configuration script with a working compiler on the
search path the whole time.
The standard compiler-detection macro walks a preference list, and some projects put an
alternative compiler first. The moment that alternative becomes a port, every such recipe silently
changes toolchain. The environment of `30_foundation` and every later phase therefore sets `CC=gcc`
by name: a distribution that builds itself cannot have its toolchain depend on which ports happen to
be installed. A configuration script that ignores `CC` needs its own switch, passed in the recipe.
### An optional tool found in the tree
The build runs a program the recipe never named, such as `inkscape` or ImageMagick's `magick`, and
fails inside it. `configure` looks the tool up with `AC_PATH_PROG`, and the build uses it when it is
found; it is found because another port installed it into the tree before this one. Whether the
step runs then depends on the build order, not on the recipe. Pass the cache variable empty on the
`configure` line, so the lookup is recorded as having found nothing:
```sh
./configure --prefix=/usr ac_cv_path_INKSCAPE=
```
`mkvtoolnix` does this: its Rakefile renders the Windows icon set with Inkscape and ImageMagick
whenever both are found, and Inkscape crashes in the chroot.
### A build calling `python`
The `python3` port installs `python` as a link to `python3`, so a makefile or a script that runs the
unversioned program gets Python 3, and there is no Python 2 on the system. A script written for
Python 2 fails at the first construct Python 3 dropped: `SyntaxError: Missing parentheses in call
to 'print'`, `invalid syntax` on `except E, e:`, or `ModuleNotFoundError` for a renamed module such
as `urllib2` or `ConfigParser`. One the build runs needs a `.patch` porting it; one the package
would install with a `python2` shebang is deleted from `$PKG` instead of shipped unable to start, as
`john` and `tuxpaint` do.
A makefile that names `python2`, or an interpreter by absolute path, usually reads it from a
variable; pass the variable on the `make` line, which also reaches the makefiles it calls:
```bash
make -C roms bios PYTHON=python3
```
The `qemu` port does this for SeaBIOS, iPXE and the option ROMs.
### Autoconf lowering the C++ standard
Every compile line reads `g++ -std=gnu++11`, and the code fails on a C++17 feature: a
`static_assert` demanding C++17, or `'exchange' is not a member of 'std'`. `configure` shows
`checking for g++ option to enable C++11 features... -std=gnu++11`. A `configure` generated by
Autoconf 2.72 probes for C++11 with a test program that assigns a `u8""` literal to a `const char *`.
Under GCC's default of C++20 that literal is `char8_t`, the probe fails, and `configure` adds the
first option that passes, `-std=gnu++11`, to `CXX` itself. Setting the cache variable empty
records that no option is needed and keeps the compiler's default, rather than naming a standard
in `CXXFLAGS`, which pins one the code may outgrow. `script/env/chroot.env` exports it empty for
every package phase from `30_foundation` to `60_kernel`, so a port built in them does not meet this;
`20_selfhost` unsets it, because it builds the compiler itself. A recipe that must not depend on
the phase environment, such as one also built by hand or on an installed system with `kpkg`, passes
it on its own `configure` line, as `rdfind` does:
```bash
./configure --prefix=/usr ac_cv_prog_cxx_cxx11=
```
### `u8` literals under C++20
`error: no matching function for call to 'std::__cxx11::basic_string::basic_string(const
char8_t [4])'`, or `invalid conversion from 'const char8_t*' to 'const char*'`, at a `u8"..."`
literal. GCC 16 compiles C++ as C++20 unless told otherwise, and C++20 gives a `u8` literal the type
`const char8_t[]`, which converts to neither `std::string` nor `const char *`. Code written for
C++17 assumed plain `char`. Build the port as C++17, through the project's own switch where it has
one, so its configure checks see the same standard: `ardour` passes waf's `--cxx17`. A project
with no switch takes `-std=gnu++17` in `CXXFLAGS`.
### Newer-compiler diagnostics as errors
`error: incompatible pointer types`, or a similar diagnostic that is a warning with older
compilers. GCC 14 made this one an error; disable it for the port:
```sh
export CFLAGS="$CFLAGS -Wno-incompatible-pointer-types"
```
### A shift count out of range in inline assembly
`Error: operand type mismatch for 'shr'` (or `sar`, `shrd`) at an `asm` line, reported through
`lto-wrapper` while a library links. The source hands x86 inline assembly a shift count under
the `"ci"` constraint, an immediate or `%cl`; once link-time optimisation propagates a count as a
constant outside 0 to 31, the assembler gets an immediate it cannot encode. FFmpeg 4.4.2
does this in `libavcodec/x86/mathops.h`; FFmpeg's own fix, in its later releases, masks the
constant and takes `%cl` otherwise. A program that builds a private FFmpeg 4 from a tarball it carries takes that fix
through its own patch list: `avidemux`'s `ffmpeg-mathops-shift.patch` adds it to
`avidemux_core/ffmpeg_package/patches/upstream/`, which Avidemux applies to that tree.
### A POSIX function hidden by a strict C standard
`error: implicit declaration of function 'strdup'` (or `fileno`, `popen`) in a C file of a KDE
project, whose command line carries `-std=c99`. KDE's compiler settings in
`extra-cmake-modules` set strict C99 unless `CMAKE_C_STANDARD` is already set, and musl's headers
declare no POSIX function in a strict ISO mode. Flex scanners and bison parsers are where it
shows. Give the standard on the configure line with GNU extensions on, which the settings then
leave alone:
```sh
-D CMAKE_C_STANDARD=99 -D CMAKE_C_EXTENSIONS=ON
```
`digikam` takes GNU C99; `kdevelop` takes GNU C23, because its lexer also includes a Qt header
that uses `static_assert`.
A GNU extension (`exp10`, `pow10`) stays hidden in a GNU mode too: musl declares it only under
`_GNU_SOURCE`, which no `-std` defines. `labplot` adds `-D_GNU_SOURCE` to `CFLAGS`, which also
lifts the strict mode's hiding of POSIX.
### C flags handed to the C++ compiler
A configure script reports that its C++ compiler works and then that it supports no C++ standard
(`Checking for availability of switch -std=c++11 in g++ ... no`), and a feature that needs C++
stops it. The script tests C++ switches with `CFLAGS` and `-Werror`; g++ warns that the tree's
`-std=gnu11` is valid only for C, and `-Werror` makes the warning the failure. Remove the standard
from `CFLAGS` where the project sets its own C standard:
```sh
export CFLAGS="${CFLAGS/-std=gnu11/}"
```
`retroarch` is the example; it compiles its C as `-std=gnu99` either way. `handbrake` takes the
same line for a different reason: its make system adds `CFLAGS` to every C and C++ compile, its
own and each bundled library's, and each of those names its own C standard.
A build that adds `CFLAGS` to its C++ compile line by design (`CXXFLAGS += $(CFLAGS)`, or
`AM_CXXFLAGS` built from `@CFLAGS@`) compiles, but `cc1plus` prints `command-line option
'-std=gnu11' is valid for C/ObjC but not for C++` for every C++ file. Move the standard into `CC`,
which only the C compiler is run as, so the C files keep GNU C11:
```sh
CC="gcc -std=gnu11" CFLAGS="${CFLAGS/-std=gnu11/}" ./configure ...
```
`ghostscript` is the example. A makefile that assigns `CC` itself takes it on the `make` line
instead, with `CFLAGS` exported rather than given there, since a `CFLAGS` on the `make` line
replaces every `CFLAGS +=` in the makefile; `mupen64plus` and `mupdf` are the examples. Where the
compiler is run from a list rather than a shell (calibre's `setup.py`), `CC` cannot carry a flag,
and the standard is removed from `CFLAGS` for that one command. A project that takes one
optimisation string for both languages (`cups`' `--with-optim`) gets `CFLAGS` without the
standard there. A bundled library that a makefile builds with `CFLAGS` alone (nmap's
`liblinear`) is built first by the recipe, with its own `make` line, so the project's make finds
it up to date. A helper that a `configure` script builds through `make` (hplip's `Dat2drv`) takes
a `CFLAGS=` in `GNUMAKEFLAGS` on the `configure` line; `MAKEFLAGS` does not reach it, because
every Autoconf `configure` empties `MAKEFLAGS` first.
The same warning in every consumer of a library comes from the library's config script. FLTK's
CMake writes its own `CMAKE_C_FLAGS` into `fltk-config` as both `--cflags` and `--cxxflags`, so
every FLTK program (flrig, fldigi) would compile its C++ with the tree's C flags, and with the
prefix map and fortify include directory of FLTK's build. The `fltk` recipe therefore runs cmake
with `CFLAGS` and `CXXFLAGS` empty and gives the tree's flags, followed by Release's
`-O3 -DNDEBUG`, as `CMAKE_C_FLAGS_RELEASE` and `CMAKE_CXX_FLAGS_RELEASE`, which `fltk-config`
does not read.
A script that copies `CFLAGS` into `CXXFLAGS` by mistake fails the same way, and `config.log`
shows `-std=gnu11` on a `g++` line. LibreOffice's `configure` saves `$CFLAGS` before its
`-Wsfinae-incomplete` probe and restores it as `$CXXFLAGS`, so its C++20 probe fails with
`checking whether g++ supports C++20... configure: error: no`. There the fix is a patch to the
generated `configure` alone (`libreoffice`'s `sfinae-probe-cxxflags.patch`). The build reruns
`autogen.sh` when `configure.ac` is newer than the configuration, so that file is left untouched.
### A source written for C23
`error: unknown type name 'bool'`, with GCC's note that `bool` is defined in ``. The
source is C23, where `bool`, `true` and `false` are keywords, and its build names no standard
because GCC's own default is GNU C23. The tree's `-std=gnu11` in `CFLAGS` takes that default away.
Name C23 after it; the last `-std=` on a command line is the one GCC takes:
```sh
export CFLAGS="$CFLAGS -std=gnu23"
```
`gthumb` is the example.
### A standard header the source never includes
`error: 'int64_t' was not declared in this scope`, or `'int64_t' does not name a type` in a
declaration, with GCC's note that it `is defined in header ''`. GCC 16's C++ library headers do not include `` and similar headers on the
way to their own, so a source file that uses a type without including its header fails. The same
holds for `'pthread_t' does not name a type` in a file that never includes ``, and
for `'QElapsedTimer' was not declared` in a file that never includes ``, which no
other Qt header brings in, and for `'PATH_MAX' was not declared` without ``. Ship a `.patch` that adds the `#include` the note names, to the header
it points at. `onnxruntime`'s `gcc16-cstdint.patch`, `audacity`'s `sqlite-helpers-cstdint.patch`, `kodi`'s `platformdefs-limits.patch`, `flmsg`'s
`font-browser-pthread.patch` and `labplot`'s `elapsed-timer-include.patch` are the examples. Build the rest of the port with
`ninja -k 0` (or `make -k`) before writing the patch, so it names every file at once.
### A project macro that breaks a library header
`expected unqualified-id before numeric constant`, pointing at a `#define` in the project's own
header but reported inside a system library's header (`volk_common.h` here).
The project defines a short name as an object-like macro, and a library header included after it
uses the same name for a variable. A newer release of the library added the name. Ship a `.patch`
that turns the macro into a `constexpr` constant, which a local variable may shadow. `satdump`'s
`satdump-calibration-constants.patch` does this for `c1`, `c2` and `e_num`, which libvolk's
inline functions name too.
### An imported library that names only its Debug build
`ninja: error: 'build/lib/libfmtd.a', needed by 'kodi-wayland', missing and no known rule to make
it`, in a build configured as Release, after the library's own external project has built
`libfmt.a`.
A find module that creates an imported target sets `IMPORTED_CONFIGURATIONS` once for Release and
again for Debug with `set_target_properties`, which replaces the list instead of adding to it. The
target then lists a Debug build alone, CMake can resolve it to the `d`-suffixed archive, and the
external project, built as Release, never makes it. Kodi's other find modules share the code and
still resolve to Release, so only the library the error names needs the patch. Ship a `.patch` that appends each configuration with
`set_property(... APPEND PROPERTY IMPORTED_CONFIGURATIONS ...)`. `kodi`'s
`fmt-imported-configs.patch` does this for its internal fmt.
### A stub whose QML target is off with its module
`CMake Error ... Argument musesounds_qml is not a target!`, from `qt_add_qml_module` in another
module's `CMakeLists.txt`, after a module was switched off on the configure line.
The project's QML option for a module defaults to the module's own option, so switching the module
off switches off the stub's QML target too, while another QML module still names that target as a
dependency. Switch the QML option back on: the stub then builds its placeholder page under the
same target. `musescore` passes `-D MUE_BUILD_MUSESOUNDS_QML=ON` beside
`-D MUE_BUILD_MUSESOUNDS_MODULE=OFF`.
### A project function named like a C library extension
`error: call of overloaded 'pow10(long unsigned int)' is ambiguous`, at a call to a `static`
function the file defines itself.
musl's `` declares the GNU extension `double pow10(double)` under `_GNU_SOURCE`, and g++
always defines `_GNU_SOURCE`. A file-local function of the same name then overloads it, and an
argument of neither parameter type matches both equally. Ship a `.patch` that renames the local
function. `audacity`'s `beats-formatter-pow10.patch` renames its `pow10` to `powerOf10`.
### A stub that includes the real module's header
`fatal error: vsteffectsmodule.h: No such file or directory`, compiling a file under `stubs/` after
a module was switched off on the configure line.
A project that builds a stub in place of each module it can leave out compiles the stub only in
configurations its own CI never tries. A stub source that includes the real module's header
fails, because only the stub's directory is on its include path. Ship a `.patch` that includes the
stub's own header, which declares the same class. `audacity`'s `vst-stub-include.patch` does this
for the VST stub that `AU_MODULE_EFFECTS_VST=OFF` builds.
### A library member renamed under a consumer
`'struct ' has no member named ''; did you mean ''?`, from a program built
against a library port newer than the release it was written for.
The library renamed the member and kept its meaning. Ship a `.patch` that uses the new name.
Moving the library back would break its other consumers. sgp4 3.0 renamed
`CoordTopocentric::range_rate` to `rangeRate`, and `sdrangel`'s `sgp4-range-rate.patch` follows it.
### A library member whose type changed
`incompatible types in assignment of 'char*' to 'char [256]'`, from a program assigning to a
structure member of a library port newer than the release it was written for. The library made
the member a fixed array the caller writes into, where it was a pointer the caller allocated.
Ship a `.patch` that copies into the array, bounded by its size. x265's `param.rc.statFileName`
is such an array, and `avidemux`'s `x265-stat-filename.patch` writes the two-pass log name into it
with `snprintf`.
### An OpenSSL structure read field by field
`error: invalid use of incomplete type 'const ASN1_GENERALIZEDTIME'` (or `ASN1_STRING`,
`ASN1_TIME`, `ASN1_INTEGER`), with a note pointing at `struct asn1_string_st` in OpenSSL's headers.
The `openssl` port is OpenSSL 4, whose `ASN1_STRING` is opaque: code reads it through
`ASN1_STRING_type()`, `ASN1_STRING_length()` and `ASN1_STRING_get0_data()`, which every OpenSSL
since 1.1.0 has. Look for the upstream commit that adapts the project to OpenSSL 4 and ship it as a
`.patch`; `dcmtk`'s `openssl4.patch` is the example. A program that must stay on OpenSSL 3 links the
`openssl3` slot instead (see [A second version beside the first](/docs/kdos/05-developer/writing-ports.html#a-second-version-beside-the-first)).
### An upstream `-Werror`
The build fails on a warning you have never seen upstream report.
An upstream `-Werror` is a promise about upstream's compiler and C library, not about these. A
compiler newer than the one upstream tested, or musl's headers in place of glibc's, produces
warnings upstream has never seen. Turn `-Werror` off: `-Wno-error` in `CFLAGS`,
`--disable-werror` for a configure script that offers it, `-Dwerror=false` for meson,
`CMAKE__FLAGS_RELEASE` for a CMake project that appends `-Werror` to `CMAKE__FLAGS`
after the environment's flags (the per-configuration variable comes last on the compiler line, as
the `stlink` and `wsjtx` ports set it), or the
variable a makefile appends after its own `-Werror` where it replaces `CFLAGS` wholesale:
`EXTRA_OPTFLAGS=-Wno-error` for edk2's BaseTools, as the `qemu` port sets. Where no variable
reaches the flags, a patch that appends `-Wno-error` after upstream's `-Werror` is the smallest
change: the `qemu` port's `edk2-gcc-no-werror.patch` does that for edk2's firmware. That keeps
every warning printed and stops upstream deciding which of them ends the build. Chasing them one
suppression at a time costs a round trip per diagnostic.
### Compiler flags passed as make arguments
An undeclared constant, or a size mismatch, that reads like a missing header.
A variable on the make command line overrides both the environment and the makefile's own
assignment, including its appending form. That is the wrong precedence for compiler flags, because
a makefile's own flags are its configuration: architecture width, installation paths, feature
constants. Export the flags instead of passing them as a make argument. A makefile that appends
then appends to yours, and one that assigns has already discarded the environment and needs
nothing from it.
### A library dropped by `--as-needed`
`undefined reference to` a symbol whose library is on the link line, or a program that links and
then runs without a codec, driver or plugin that registers itself from a library's constructor.
`LDFLAGS` from `20_selfhost` on carries `-Wl,--as-needed`, so the linker records a library only
when an object before it on the command line uses one of its symbols. A makefile that names
`-lfoo` ahead of the objects loses the library; a library the program needs only for what its
constructor does is dropped as unused. Where the build system lets you, move the library after
the objects; otherwise turn the option off for the port:
```sh
export LDFLAGS="$LDFLAGS -Wl,--no-as-needed"
```
### A guessed virtual call into a class the library never compiles
`.so: undefined reference to 'vtable for '`, with a method of the same class
beside it, when a program links against a shared library the port has just built, and ``
is declared in a header the library includes but defined in a source file it does not compile.
At `-O2` GCC devirtualises speculatively: a virtual call through a base pointer gets an inline
guess at the one implementation the translation unit can see, and the guess references that
class's vtable and methods. A header that declares a class for code the library leaves out still
makes it the only candidate, so the library is linked with a reference nobody defines.
`afflib`'s LZMA glue includes 7-Zip's `FileStreams.h` for its in-memory streams and compiles none
of the file streams it declares. Turn the guess off for the port:
```sh
export CXXFLAGS="$CXXFLAGS -fno-devirtualize-speculatively"
```
### A plugin that needs lazy binding
`Error relocating : : symbol not found`, from a program opening a plugin with
`dlopen`, where the symbol is defined by another plugin or library the program opens later.
musl defers an unresolved symbol in a library opened with `RTLD_LAZY`, and binds it once a later
library supplies it, but only when the library was not linked with `-z now`, which `LDFLAGS` from
`20_selfhost` on passes. Link that port's plugins lazily:
```sh
export LDFLAGS="$LDFLAGS -Wl,-z,lazy"
```
### A backtick inside double quotes
`No rule to make target`, printed from the middle of an unrelated step.
An `echo` that tells somebody to run a command, written with backticks inside a double-quoted
string, does not print that instruction: the shell runs it. Quote a command a diagnostic names with
single quotes. Preflight checks every `echo` in the build system's own scripts under `script/`; in
a port's `build.sh`, the rule is yours to keep.
### A parallel install race
`No rule to make target '\'`, naming an object that compiled a moment earlier, during
`make install`.
Every phase exports `MAKEFLAGS=-j$KDOS_JOBS`, from `script/env/common.env`, so the install runs in
parallel as well as the build. A project whose install targets regenerate their own dependency files
races itself: a half-written `.dep` file ends on a bare line continuation, and make reads the backslash as
a target.
Use `make -j1 … install` for that project, as the `libburn`, `libisofs`, `xfsprogs` and several
other ports do. A `-j` on the command line overrides the one in `MAKEFLAGS`, and only that
invocation is serialised; the compile keeps its parallelism. Being a race, it can pass on one
run and fail on the next, so a recipe that has built before is not evidence against it.
### A compiler that was OOM-killed
`c++: fatal error: Killed signal terminated program cc1plus`, or a compile or link step that ends
with no diagnostic at all, in a large C++ port such as WebKit, QtWebEngine, LLVM or a browser;
`dmesg` on the host names the process the kernel's OOM killer chose.
Every phase runs `KDOS_JOBS` jobs at once, and a heavy C++ translation unit or link can need more
than 2 GiB on its own, which is the budget per job the default job count assumes. Lower the job
count for the build: `make build KDOS_JOBS=6`. It reaches make, `cmake --build` (WebKit and most
CMake ports build through it) and cargo, and it caps the container's CPUs, which the chroot sees
through its read-only cgroup mount, so a bare `ninja` in a recipe follows it too. WebKit runs at
most four links at once whatever the job count; its CMake reads `WEBKIT_NINJA_COMPILE_MAX` and
`WEBKIT_NINJA_LINK_MAX` from the environment at configure time to size its compile and link pools
apart from the job count, and since the chroot clears the environment, only `webkitgtk`'s own
`build.sh` can export them.
### A dependency the list happened to satisfy
A header that is plainly missing, in a port that has built many times before, on a fresh build
only. `sys/queue.h` is the one to expect on musl.
The port never declared the dependency and was satisfied by whatever else had already pulled it
in. In KDOS `sys/queue.h` (with `sys/tree.h` and `sys/cdefs.h`) comes from `libbsd`, which
`41_system` installs and which ports such as `netcat`, `libtirpc` and `samba` depend on. An
incremental build already has it. On an empty target tree the solver's order decides, and a port
built before its accidental provider fails.
Name the dependency in `depends`. The key exists so that the solver orders the build, and a
dependency that holds only by accident fails the first time the order changes. A build with
`--port-jobs` finds such a port on any tree, since a level builds against the lower levels only; see
[A port that needs `--port-jobs 1`](#a-port-that-needs---port-jobs-1).
### The time-protocol helper
GStreamer's precision-time-protocol (PTP) helper, `gst-ptp-helper`, is written in Rust, and
GStreamer's `meson.build` compiles it whenever meson finds a Rust compiler. The `gstreamer` port
does not depend on the `rust` port, so without a switch whether the helper is built, and whether
its Rust build can fail the port, would depend on what else the target tree happens to hold (see
[A dependency the list happened to satisfy](#a-dependency-the-list-happened-to-satisfy)). The port
disables it with `-Dptp-helper=disabled` on its `meson setup` line; a recipe building GStreamer's
core elsewhere needs the same option.
### A file that is not UTF-8
The chroot phases export `LANG` and `LC_ALL` as `C.UTF-8` (`script/env/chroot.env`), which musl
provides with no locale files installed, so Qt's tools, Sphinx and GHC's Hadrian read non-ASCII
text in the locale's encoding without failing. Under it GNU `grep` treats a file holding bytes that
are not valid UTF-8, a Latin-1 header or a binary, as binary data: a match prints
`binary file matches` instead of the line, and a configure script that extracts a version with
`grep` gets nothing and fails later on an empty value. Run that one command under `LC_ALL=C`, which
reads every byte as a character. Reproducibility does not depend on the choice: musl collates every
locale byte by byte and formats dates in `C.UTF-8` exactly as in `C`.
The host phases, `00_cross` and `10_bootstrap`, keep `common.env`'s `LC_ALL=C`, whose character set
under musl is ASCII. A tool there that decodes text in the locale's encoding rejects any byte over
127, with `invalid or incomplete multibyte or wide character` or `hGetContents: invalid argument`;
run that one command under `LC_ALL=C.UTF-8`.
### An empty submodule in a release archive
A directory the build expects exists and is empty. A forge's generated release archive carries a
git submodule's directory empty, because the archive is made from the repository without recursing
into submodules. Add the submodule as a second `source` extracted where the build looks, or make
it a port.
### Source URLs and archive layouts
These rarely fail a build outright, but each can cost a fetch or an unpack round trip:
- A project's usual mirror can be stale while its real releases are elsewhere.
- Forge projects split between release assets and generated archives, at different URL shapes,
and some forges' release download paths serve only files attached by hand; use the archive path
instead.
- Some source hosts rate-limit archive generation, and some block automated fetching entirely, so
a version cannot be checked from a script.
- The top-level directory in an archive varies. `kpkg` strips one path component from the first
source, so an archive with no wrapping directory, or one whose members are written `./dir/…`,
lands in the wrong place. Verify with `tar -tf` when the build reports the source is not where it
should be; preflight checks both shapes.
## When the failure is not here
Work through these in order:
1. **The failing step's log**, under `build/logs//`, read upward from the end.
2. **The port's own `config.log`**, if a configuration script failed, in its work directory
under `build/fs/var/cache/kpkg/work//`. The failing test program is the error.
3. **`testing/preflight.sh`**, which catches wiring failures in a few minutes. Among other things
it checks that every source a recipe declares is hashed (and lists the ones not fetched yet),
that every source file in one of KDOS's own ports is compiled by its recipe, that every meson
option exists, and that every dependency resolves: whole classes of failure that never reach a
compiler.
4. **Whether the source was fetched at all.** The build has no network, so a declared source that
`make fetch` did not put beside its recipe can only fail at the unpack. `make fetch-check` says
which.
5. **Whether the target tree is ahead of the phase you are running.** See
[Re-running an early phase on a later tree](#re-running-an-early-phase-on-a-later-tree).
## See also
- [Writing ports](/docs/kdos/05-developer/writing-ports.html): the recipe format, the canonical build shapes, vendoring and
publishing sources
- [The build system](/docs/kdos/05-developer/build-system.html): phases, the chroot, step logs, snapshots and narrowed plans
- [Developing](/docs/kdos/05-developer/developing.html): where sources come from, the make targets and the fast loops
- [Testing](/docs/kdos/05-developer/testing.html): everything preflight checks before a build starts
- [How KDOS is built](/docs/kdos/05-developer/how-kdos-is-built.html): the build from `git clone` to a bootable ISO, as one
story
- [The ports catalogue](/docs/kdos/06-reference/ports-catalogue.html): every port by shelf, with the phase
that builds it
---
*Part V — Building and developing, chapter 34.* Previous: [33. Writing ports](/docs/kdos/05-developer/writing-ports.html) · [Contents](/docs/kdos/) · Next: [35. The C libraries](/docs/kdos/05-developer/c-libraries.html)