kdos

Build troubleshooting

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) A source that was never fetched
sha256 MISMATCH for <file> from <url> from ports/fetch, or sha256 MISMATCH for <file> from kpkg A downloaded source that fails its hash
No sha256 for <file> in the recipe — refusing to extract an unverified source A source with no hash
Preflight: declares <file>, which is neither in the port directory nor hashed, or ships <file> with no sha256 line A source with no hash
Preflight: ships <file>, whose first bytes are none of gzip, bzip2, xz, zstd, lzip, zip or tar, or ships <file> as an empty file An error page saved as a tarball
Preflight: recipe-hashed archives are tracked by git, or make fetch-check counting fewer sources than the recipes name Sources still in the git index
sha256 MISMATCH for the generated <name>-vendor-<version>.tar.xz A sha256 mismatch on a vendor bundle
cannot generate <bundle> without its source, this needs docker or podman, failed to build the recipe reader, or --tree never generates A vendor bundle that cannot be generated
pre-push: the ports layout is broken at <commit>: Pre-push refused: the ports layout
pre-push: these sources are named by a recipe but not in the archive at that commit Pre-push refused: an unpublished source
Preflight: shelf <shelf> has no volume, or ports/publish saying shelf <shelf> has no volume; run ports/publish --plan A shelf with no volume
Failed to download for every file, is in no cache, and the archive is off, or pre-push: cannot reach the source archive Fetch cannot reach the archive
package resolution failed - see build/logs/.../expansion.log A package list that does not resolve
port <name> is filed twice: <path> and <path>, or is in two places A port filed twice
Preflight: phase closure fails, with installed by <phase> (pulled in: …) but not named in its list or but named by <phase> A phase that installs a port it does not name
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/ A phase the orchestrator will not run
The startup picker offers only start fresh on a tree that has snapshots Snapshots the orchestrator does not see
build/iso-build/kdos.iso is open by another process The ISO is in use
snapshot <phase> FAILED: only <size> free, need ~<size> A snapshot the orchestrator refuses
snapshot <phase> FAILED: build/ is mid-restore of <phase>, or a restore of <phase> never finished A snapshot the orchestrator refuses
snapshot <phase> FAILED: mounts still active under build/fs: <paths> A snapshot the orchestrator refuses
refusing to remove <path>: a directory above it is a symlink, refusing removal of '<path>' listed by <file>, or the snapshot of <phase> is unusable A snapshot the orchestrator refuses
deleted <phase>; kept as the base of <phases> A snapshot the orchestrator refuses
A port you changed is marked installed in the build, or kpkg reports Skipping <port> (already installed) A recipe change that did not rebuild its port
A variable passed to make build has no effect on a chroot phase A variable that does not reach the chroot
A CMake port fails or misbehaves and the compiler cache is suspected A compiler cache under suspicion
Reusing <port> from the store (<key12>), or a port that behaves like an older build of itself with KDOS_PKG_STORE=1 A port that came from the package store
Removing orphan for a file another package still lists, then not found on that tool A package manager older than its source
A port that fails, or builds without a feature, only under --port-jobs A port that needs --port-jobs 1
<port> took paths from <port>; the serial order had <port> last A port that needs --port-jobs 1
its kpkg has no --build-only; building one port at a time A port that needs --port-jobs 1
10_bootstrap files over a later build, or a later tree filed under that phase’s snapshot Re-running an early phase on a later tree
A ports/ helper that fails to start after you ran it in a container Helpers compiled against the other C library
Dynamic loading not supported from a Rust crate, or a static archive’s undefined references at its link Rust with a binding generator
'cstddef' file not found from clang or bindgen, in a header that compiles with gcc An LLVM that guessed its triple
'+amx-tf32' is not a recognized feature for this target from rustc A Rust release older than the system LLVM
rustc <version> is not supported by the following packages A crate newer than the toolchain
no matching package named … with the vendor tree present A vendor bundle in the wrong place
CMake being compiled from source during a download step A Python backend resolving a system tool
unknown type name 'bool' inside a GCC target header, while building libgcc A language standard reaching the compiler’s own runtime
'fenv_t' has not been declared, then Cannot compile std module, in 10_bootstrap The installed C++ headers shadowing the ones being built
undefined reference to libintl_gettext Gettext on musl
'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 A glibc-only call on musl
'TCGETS2' undeclared, or another kernel ioctl request missing, with the matching structure found A kernel ioctl request missing on musl
A wide-character curses function as an implicit declaration The wide curses API
ubrk_* missing at link An ICU component not propagated
cp: not writing through dangling symlink in an install step A link at a copy’s destination
ERROR: lint: <path>: mode 0600: not readable by users, or a program with no x bit A file installed unreadable
ERROR: lint: <path>: … with any other reason, then the staged tree failed <N> lint check(s) A package the lint refuses
A package index or a clone reached during the offline build A build that reaches the network
Unknown options: … at meson setup An unknown meson option
A meson option value that is not one of the choices A meson feature given a boolean
Error loading shared library at run time, or a stale package-config file shadowing a fixed one A missing meson prefix or library directory
find_package called with invalid argument "1", or a config file not compatible with requested version "1" A requirement a find module misreads
Missing Qt6::GuiPrivate, or another Qt6::<Module>Private target that does not exist A Qt private module not loaded
Eigen3 library NOT FOUND with Eigen installed, or #error Eigen requires at least c++14 support Eigen without its legacy variables
cannot mix girepository 1.0 (via libpeas) and 2.0 (via pygobject) Two introspection runtimes in one process
glib.h: No such file or directory in one plugin of an explicit plugin list A plugin built with no compiler flags
cairo.h: No such file or directory from FL/Fl_Cairo.H FLTK’s headers needing Cairo’s
'class QTextStream' has no member named 'setCodec', 'QRegExp' was not declared, in a Qt program built with qmake6 A Qt 5 program built against Qt 6
qt6_wrap_cpp: TARGET parameter is empty A source file in a moc list
#error "Qt requires a C++17 compiler" with -std=c++17 -std=c++11 on the compile line An older standard after CMake’s
unsupported Lua version (i.e. not Lua 5.1, 5.2, 5.3, or 5.4) from sol2 A bundled header shadowed by the system’s
expected unqualified-id before numeric constant inside a library header A project macro that breaks a library header
call of overloaded 'pow10(...)' is ambiguous (or another GNU math name) at a project’s own function A project function named like a C library extension
fatal error: <module>.h: No such file or directory in a stub module built because the real one is off A stub that includes the real module’s header
ninja: error: '<dir>/lib<name>d.a', needed by '<program>', missing and no known rule to make it in a Release build An imported library that names only its Debug build
Argument <module>_qml is not a target! from qt_add_qml_module, after a module was switched off A stub whose QML target is off with its module
has no member named '<old>'; did you mean '<new>'? against a library port A library member renamed under a consumer
incompatible types in assignment of 'char*' to 'char [N]' at a library’s structure member A library member whose type changed
cannot convert 'std::nullptr_t' to '<integer type>' at a NULL NULL used as an integer
Compatibility with CMake < 3.5 has been removed An old CMake policy floor
Unsupported class file major version 69 (or another number) from Groovy or another JVM build tool A JVM tool older than the JDK
Policy CMP0026 may not be set to OLD behavior A policy set to its removed behaviour
too few arguments to function 'GifFileType* DGifOpen(void*, InputFunc, int*)', or 'GifLastError' was not declared A version test that excludes a newer major release
An option you passed had no effect, with a warning about unused variables A misspelt CMake option
Every static link failing on unwinder symbols A CMake file with no project declaration
Installing: a library under /var/cache/kpkg/work/… instead of /usr/lib A library installed into the source tree
cp: -r not specified; omitting directory on a path that looks like the binary Go building into its own directory
C compiler cannot create executables C compiler cannot create executables
The same, with a working compiler on the search path A configuration script preferring another compiler
SyntaxError: Missing parentheses in call to 'print', python2: command not found, or make: python2: No such file or directory A build calling python
A crash or failure in a tool the port does not depend on, such as inkscape, run by the build An optional tool found in the tree
g++ -std=gnu++11 on every compile line, then requires a C++17 capable compiler or a missing std:: member Autoconf lowering the C++ standard
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*' u8 literals under C++20
error: incompatible pointer types Newer-compiler diagnostics as errors
Error: operand type mismatch for 'shr' from lto-wrapper A shift count out of range in inline assembly
implicit declaration of function 'strdup' (or fileno, exp10) in a KDE project built with -std=c99 A POSIX function hidden by a strict C standard
Checking for availability of switch -std=c++11 in g++ ... no after g++ works, or checking whether g++ supports C++20... configure: error: no C flags handed to the C++ compiler
unknown type name 'bool' in a port’s own source, with a note naming <stdbool.h> A source written for C23
error: 'int64_t' was not declared in this scope or 'int64_t' does not name a type (or another fixed-width type), with a note naming <cstdint> A standard header the source never includes
error: invalid use of incomplete type 'const ASN1_GENERALIZEDTIME', or another ASN1_* type An OpenSSL structure read field by field
Warnings you have never seen upstream, made fatal An upstream -Werror
An undeclared constant that reads like a missing header Compiler flags passed as make arguments
undefined reference to a symbol of a library that is on the link line, or a plugin or codec that registers itself missing at run time A library dropped by --as-needed
<library>.so: undefined reference to 'vtable for <Class>' from a library the port itself built A guessed virtual call into a class the library never compiles
Error relocating <library>: <symbol>: symbol not found when a program opens a plugin A plugin that needs lazy binding
No rule to make target printed from the middle of an unrelated step A backtick inside double quotes
No rule to make target '\' during an install A parallel install race
Killed signal terminated program cc1plus, or a compiler or linker ending with no message A compiler that was OOM-killed
A BSD header missing on a fresh build only A dependency the list happened to satisfy
GStreamer’s core compiling or failing in a Rust helper (gst-ptp-helper) The time-protocol helper
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 A file that is not UTF-8
A submodule directory present but empty An empty submodule in a release archive
A downloaded archive that does not exist, or unpacks oddly Source URLs and archive layouts

Sources, fetching and publishing

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:

ports/fetch <port>
sha256sum ports/core/*/<port>/<file>

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):

Variable Default Effect
KDOS_SOURCES_REPO kunaldawn/kdos The GitHub repository holding the archive
KDOS_SOURCES_BASE https://github.com/$KDOS_SOURCES_REPO/releases/download 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.

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)