Framework Changes¶
A curated log of notable changes to the spksrc build framework — the mk/
tree and the conventions every package Makefile relies on — newest first.
This is not an exhaustive changelog (for that, see git log -- mk/). Each
entry is collapsed to its date and title; expand it (▸) for what changed, why,
and a link to the pull request. Efforts that span several pull requests are a
single entry with the individual PRs nested inside.
Highlights — the short version¶
If you only read one thing, read this. The details are in the dated log below.
-
make helpknows your package. Run it inside any package directory for a context-aware list of the targets and variables that actually apply there: -
Declare what a package needs, not where it fails. Instead of hand-maintaining an
UNSUPPORTED_ARCHSlist, state the capability floor:MIN_GCC_VERSION,MIN_GLIBC_VERSION,REQUIRE_64BIT. The framework refuses exactly the architectures whose toolchain cannot meet it, with a human-readable reason, and the gate stays correct on its own as toolchains move. See Architecture Support. -
Every build system uses the same variable names now. CMake no longer has its own
CMAKE_ARGS: pass configure options throughCONFIGURE_ARGSfor autotools, CMake and Meson alike. Compile and install options are likewise unified onCOMPILE_ARGSandINSTALL_ARGS(the oldCOMPILE_MAKE_OPTIONS/INSTALL_MAKE_OPTIONSare gone). See Build System Selection and Compile and Install Arguments. -
A build directory is one variable.
CMAKE_BUILD_DIR/MESON_BUILD_DIR/NINJA_BUILD_DIRare unified onBUILD_DIR, which also opts an autotools package into an out-of-tree build. -
Flaky download host? Add a mirror. Set
PKG_DIST_MIRRORSto one or more fallback base URLs; each is tried in turn and still checked againstdigests. See Source downloads and mirrors. -
include ../../mk/spksrc.common.mkbefore any macro call. Theversion_*macros (and friends) are only defined oncespksrc.common.mkis included, so a Makefile must include it before the firstversion_ge,version_lt, … it uses. -
Turning a package off? Prefer
DISABLEDoverBROKEN. ADISABLEDfile in a package folder skips it exactly likeBROKEN, but reads as an intentional choice rather than a failure. Put the reason in the file. See the package lifecycle guide. -
The target ABI reaches every language now. A toolchain declares its ABI once in
TC_EXTRA_BUILD_FLAGS; the framework folds it into everyTC_EXTRA_<LANG>FLAGSand the link, so C, C++, Fortran and the linker all agree on the ABI. Link-time libraries (-lrt,-latomic) live inTC_EXTRA_LDFLAGS,-latomicauto-dropped where the gcc lacks it and both linked--as-neededso a binary depends on them only when it truly uses them. See Extra flags a toolchain can declare. -
Build a host tool once, host it, reuse it. A native package hosts its build output as a release archive with a single line —
ARCHIVE_NAME— so an expensive tool (llvm, the gcc-8.5 overlays) is built once and pulled in viaDEPENDSinstead of rebuilt from source.make nativecleandrops a native package's build cookies to re-run it from scratch, the native counterpart ofspkclean.
July 24th 2026 — Host a native build's output as a reusable archive (#7327)
-
What: a new opt-in step,
spksrc.native/archive.mk(included byspksrc.native-cc.mk), tars a native package's install tree into a release archive afterinstall, so an expensive tool is built once and re-consumed viaDEPENDSinstead of rebuilt from source. A package enables it with a single line:
The rest defaults — ARCHIVE_EXT (txz, mapped to the tar compression like
extract.mk does in reverse), ARCHIVE_DIR ($(WORK_DIR)) and ARCHIVE_KEEP
(./install) — with an optional debug-symbol strip and ARCHIVE_EXCLUDES. It
follows the usual pre_/archive_target/post_ pattern (override ARCHIVE_TARGET,
or set it to nop), runs automatically from _all, is a no-op unless
ARCHIVE_NAME is set, and is guarded by a status cookie like every other step
(extract, compile, ...) so it runs once per work dir. print-archive-name
lets a generator resolve the name without building.
- Also: nativeclean, the native counterpart of spkclean, drops the master
package's build cookies so every step re-runs on the next make while keeping the
work dir; and the variables carry no redundant NATIVE_ prefix (ARCHIVE_*,
matching ARCHIVE_CMD / ARCHIVE_COOKIE). The step just runs and lets tar
fail if nothing was built, rather than pre-checking a sentinel.
- Why: the archive step was open-coded per package (native/llvm-14.0-build
carried its own build-archive recipe); this factors it into one shared,
defaulted helper. native/llvm-14.0-build adopts it here; the gcc-8.5
overlays reuse it in #7324.
- Pull request: #7327
July 23rd 2026 — Carry the runtime library the binary asks for, by symbol version (#7322)
- What: the strip step copies the runtime libraries DSM does not ship
(
libatomic,libquadmath,libgfortran-- theTC_LIBS_DEFAULTlist) from the toolchain. It now selects the copy whose symbol versions satisfy the binary (readelf -Vvsstrings), instead of the first one a plainfind -nameturns up. - Why: a find by name returns every copy in the toolchain at once (the
sysroot's, the compiler's
lib64, a multilib) and handed the first to abasenameexpecting one. Choosing by symbol version is correct with the multilib case today and ready for several gcc versions to coexist under a future overlay. - No package-facing change.
- Pull request: #7322
July 23rd 2026 — Detect Fortran by probing the compiler (#7321)
- What:
TC_HAS_FORTRANwas a static "7.x / SRM 1.3 / 6.2.4-x64 ship gfortran" table; it is now a probe of the actualgfortranbinary, evaluated (likeTC_HAS_LIBATOMIC) in the tc_vars sub-make after the toolchain is extracted, so cross packages read the baked result. - Why: the table is a proxy for the stock toolchains only -- it cannot see a compiler swapped in underneath, e.g. a gcc overlay that adds gfortran to an arch the table calls Fortran-less. Probing the binary stays correct whatever provides it, and gives the same answer as the table on every stock toolchain today.
- No package-facing change.
- Pull request: #7321
July 23rd 2026 — Toolchain ABI and link flags reach every language (#7314)
A toolchain's ABI/arch flags now consistently reach every language and the link, and two link-time libraries stopped being hand-maintained arch lists.
- What: a toolchain declares its ABI once in
TC_EXTRA_BUILD_FLAGS(-march,-mcpu,-mfpu,-mfloat-abi, ...); the framework folds it into eachTC_EXTRA_<LANG>FLAGS(C / CPP / C++ / Fortran) and intoTC_EXTRA_LDFLAGS, so every language and the gcc link driver build with the same ABI.-lrt(glibc < 2.17,clock_gettime) and-latomic(ARMv5 / PowerPC, no native 64-bit atomics) moved out of about two dozen per-package arch lists intoTC_EXTRA_LDFLAGS;-latomicis kept only when the toolchain's gcc actually ships it, detected withgcc -print-file-name=libatomic.so. Both are wrapped in-Wl,--as-needed ... -Wl,--no-as-neededso, now that they are declared toolchain-wide, a binary records alibrt/libatomicdependency only when it truly references one. - Why: passing the ABI only through
CFLAGSsilently built C++ / Fortran objects with a different ABI than the C they link against, and the rt/atomic arch lists had to be rechecked by hand each time a toolchain moved.TC_EXTRA_RUSTFLAGSis left out on purpose — rustc takes its ABI through-Ctarget-cpu, and a crate's C dependencies get it viaCFLAGS_<target> = TC_EXTRA_CFLAGS. - Documented in Extra flags a toolchain can declare.
- Pull request: #7314
July 23rd 2026 — Declare toolchain capabilities instead of arch lists (#7313)
A package can now say what it needs from a toolchain rather than list the architectures where it happens to fail today.
- What: three declarative floors —
MIN_GCC_VERSION,MIN_GLIBC_VERSIONandREQUIRE_64BIT— checked against the toolchain's ownTC_GCC/TC_GLIBC(andTC_KERNEL), now declared in each toolchain Makefile and read statically. An unmet floor makespre-check.mkrefuse that architecture with a human-readable reason, and several reasons accumulate (a 32-bit target on an old gcc reports both). About two dozencross/packages andffmpeg7/8dropped theirUNSUPPORTED_ARCHSarch lists in favour of a floor. - Why: a hardcoded arch list says where a package fails, not why; it must be rechecked by hand every time a toolchain moves and cannot express "any arch whose gcc is older than X". A declared floor can, and stays correct on its own.
- Beyond a DSM floor:
REQUIRED_MIN_DSMwas frequently used as a proxy for "needs a recent enough compiler", then topped up withUNSUPPORTED_ARCHSfor the architectures a single DSM floor still missed — a DSM version does not map to one gcc across every arch, so an older platform can ship an older gcc on the same DSM.MIN_GCC_VERSIONstates the real requirement and covers all of those cases at once, dynamically.REQUIRED_MIN_DSM/REQUIRED_MAX_DSM/REQUIRED_MIN_SRMandUNSUPPORTED_ARCHSremain for genuine OS-version and per-arch constraints that are not a capability floor. - Also:
TC_GCCis read from the toolchain Makefile instead of runninggcc -dumpversion, so the compiler version is known before anything is extracted. - Documented in Architecture Support.
- Pull request: #7313
July 13th 2026 — Build-variable standardization (3 PRs)
A three-part effort so that every build system (autotools, CMake, Meson) exposes the same package-facing variable names. Before it, a Makefile looked different depending on the underlying build tool; after it, the same variable means the same thing everywhere.
CONFIGURE_ARGS — unify the configure arguments (#7279)
CMake was the odd one out: it used CMAKE_ARGS / ADDITIONAL_CMAKE_ARGS
while autotools and Meson already passed their options through
CONFIGURE_ARGS.
- What: renamed
CMAKE_ARGS→CONFIGURE_ARGSandADDITIONAL_CMAKE_ARGS→ADDITIONAL_CONFIGURE_ARGSacross every cmake package (no alias);ADDITIONAL_CONFIGURE_ARGSis now honoured by autotools and Meson too. - Why: one variable for "arguments to the configure step" regardless
of the build system.
ADDITIONAL_CONFIGURE_ARGSremains for the rare case where a package reusesCONFIGURE_ARGSfor its own auxiliary invocations and needs extra args to reach only the framework's call (seecross/x265). - Documented in Build System Selection.
- Pull request: #7279
COMPILE_ARGS / INSTALL_ARGS — unify the compile & install arguments (#7280)
The compile and install steps had an autotools-only slot
(COMPILE_MAKE_OPTIONS / INSTALL_MAKE_OPTIONS) with no CMake/Meson
equivalent.
- What: introduced
COMPILE_ARGSandINSTALL_ARGS. On the autotools / plain-make path they are the make command (replacing the removed*_MAKE_OPTIONS); for CMake and Meson they are appended as-is tocmake --build/cmake --installandninja/ninja install. - Defaults on the make path: when unset,
COMPILE_ARGSdefaults to-j$(NCPUS)andINSTALL_ARGStoinstall DESTDIR=… prefix=…, so a package's own make routines can reference them and inherit sensible behaviour (e.g.cross/cairo-1.16gains-j;cross/glibc-*drop their explicit-j). - Why the scoping matters: the defaults are gated by
DEFAULT_ENV(and anINSTALL_TARGETpython check) so they never leak into CMake/Meson/rust/python builds — the native cmake/meson env files declareDEFAULT_ENVfor the same reason. - Documented in Compile and Install Arguments.
- Pull request: #7280
BUILD_DIR — unify the build directory (#7282)
CMake, Meson and Ninja each had their own build-directory variable
(CMAKE_BUILD_DIR / MESON_BUILD_DIR / NINJA_BUILD_DIR).
- What: unified them on a single
BUILD_DIR, set per build system by the matching env file, and extended out-of-tree build support to the autotools / plain-make path: it builds in-source by default and opts in to an out-of-tree build by settingBUILD_DIR. - Why: one name for "where the build happens", and a framework
mechanism for out-of-tree autotools builds that packages previously
hand-rolled —
cross/glibcdropped its three custom configure/compile/install targets in favour of a one-lineBUILD_DIR. - Pull request: #7282
July 13th 2026 — Disable a package with BROKEN or DISABLED (#7283)
- What: a package is skipped when it has a
BROKENor aDISABLEDfile in its folder — both are honoured byspksrc.rules/pre-check.mk(build time) and the CIprepare.sh(package selection). UseDISABLEDwhen a package is intentionally turned off andBROKENwhen it is actually failing. - Why: the marker file now reads its intent. First use:
spk/ffmpeg{4,5,6}were disabled — no new release is planned and disabling them keeps their large codec dependency trees out of the build. See the package lifecycle guide. - Pull request: #7283
July 2nd 2026 — Context-aware make help (#7250)
Context-aware help at the repo root and inside each package.
Pull request: #7250
July 2nd 2026 — Build logs off the repo root (#7256)
Build logs are no longer pinned to the repository root, keeping the tree clean.
Pull request: #7256
July 1st 2026 — Generic, bounded mirror fallback for downloads (#7254)
A download used to be a single request to PKG_DIST_SITE: if that host was
down, or its TLS certificate had just expired, the build failed.
- What: the download logic was split into per-method macros, and a
download now walks a list of candidate URLs, stopping at the first success.
Mirrors of the big source hosts (GNU, SourceForge, GNOME, X.Org,
kernel.org) are tried automatically, and
PKG_DIST_MIRRORSlets a package name its own fallback base URLs. - Why bounded: the candidate list is finite (primary URL + family mirrors
PKG_DIST_MIRRORS, de-duplicated) and each is retriedDOWNLOAD_TRIEStimes, so a download that cannot succeed fails rather than looping.- Why it is safe: every candidate is checked against
digests, so a mirror serving different bytes fails the build instead of poisoning it. - Documented in Source downloads and mirrors.
- Pull request: #7254
January 29th – July 1st 2026 — Reorganize mk/ into functional submodules (8 PRs)
mk/ was a flat pile of spksrc.*.mk files. Over six months it was
reorganized so related logic lives together in concern-based submodules,
while the entry-point files a package Makefile actually includes
(spksrc.cross-cc.mk, spksrc.spk.mk, spksrc.cross-cmake.mk, …) stay at
the root. This was not one big-bang move but a sequence of PRs, each carving
out one concern at a time.
Final layout:
mk/
├── spksrc.cross-cc.mk ┐ entry points a Makefile includes
├── spksrc.cross-cmake.mk │ stay at the root (unchanged include paths)
├── spksrc.spk.mk ┘
├── spksrc.common/ archs, directories, macros, logs, stage0
├── spksrc.build/ per-step recipes: configure, compile, install, patch, …
├── spksrc.cross/ cross-build env per build system: cmake, meson, rust, go
├── spksrc.native/ native-build env per build system
├── spksrc.rules/ depend, dependency-tree, pre-check, digests, tests
├── spksrc.spk/ spk assembly: copy, icon, strip, publish
├── spksrc.spk-meta/ meta initiators: ffmpeg, python, videodriver
├── spksrc.service/ DSM service scripts and installers
├── spksrc.wheel/ python wheel build/install
├── spksrc.toolchain/ toolchain fetch + tc_vars generation
├── spksrc.toolkit/ build-host toolkit (mirrors toolchain)
└── spksrc.kernel/ kernel-module build support
spksrc.common/ — split common.mk (#6906)
Split the overloaded spksrc.common.mk into spksrc.common/
(archs.mk, macros.mk, logs.mk, …) and had every cross/ and spk/
Makefile include spksrc.common.mk before any version_* macro call.
Pull request: #6906
spksrc.toolchain/ (#6914)
Moved spksrc.tc.mk and the toolchain logic into spksrc.toolchain/
(tc-base.mk, tc-versions.mk, tc_vars.mk, …).
Pull request: #6914
spksrc.toolkit/ (#6973)
Reorganized the build-host toolkit the same way, mirroring the toolchain
layout (tk-base.mk, tk-versions.mk, tk_vars.mk, …).
Pull request: #6973
spksrc.kernel/ (#6994)
Split kernel-module support into spksrc.kernel/ (base.mk,
headers.mk, module.mk, versions.mk, …).
Pull request: #6994
spksrc.spk-meta/ — meta initiators (#7008)
Split the ffmpeg / python / videodriver meta initiators out of the
monolithic mk files into spksrc.spk/ and spksrc.spk-meta/, and renamed
the meta-spk initiator to spksrc.spk-meta.mk.
Pull request: #7008
The bulk: build / cross / native / rules / service / wheel (#7237)
The largest move: carved the remaining per-step recipes, per-build-system
env files, rules, service scripts and wheel logic into
spksrc.build/, spksrc.cross/, spksrc.native/, spksrc.rules/,
spksrc.service/ and spksrc.wheel/.
Pull request: #7237
June 28th 2026 — Boxed *.mk file headers (#7242)
Standardized every *.mk header to one boxed format, so each file states its
purpose, inputs and outputs consistently.
Pull request: #7242
June 22nd–25th 2026 — Meta cross-dependency environment (4 PRs)
How a "meta" package (ffmpeg, videodriver, python) exposes its cross-dependencies to consumers.
Introduce the meta cross-dependency environment (#7222)
Introduced META_PKGCONFIG_DIRS + tc_vars.meta.mk.
Pull request: #7222
Resolve CMake meta deps via the toolchain file (#7223)
Resolve OPENSSL_ROOT_DIR + CMAKE_FIND_ROOT_PATH through the generated
toolchain file instead of ad-hoc detection.
Pull request: #7223
June 10th 2026 — Toolchain cache poisoning fix (#7196)
A stale toolchain cache was silently dropping TC_GCC-gated DEPENDS.
Pull request: #7196
June 10th 2026 — Stage-0 toolchain bootstrap (#7184)
Bootstrap the toolchain before deriving TC_GCC, so version-gated
dependencies (version_ge TC_GCC …) parse correctly on a cold tree. This
refines the stage0 minimal environment introduced in March (see below).
Pull request: #7184
January – May 2026 — Faster, parallel dependency resolution (5 PRs)
Dependency resolution used to run through a shell script
(mk/dependency-list.sh) walked serially. It is now a pure-Makefile
implementation (spksrc.rules/dependency-tree.mk) that the framework can
walk in parallel.
Faster dependency-flat (#6894)
Rewrote the flat dependency walk and fixed a basename collision in the
parallel walk (packages of the same name in different directories, e.g.
native/erlang and cross/erlang, shared a done-file).
Pull request: #6894
Pure-Makefile dependency-tree (#6952)
Replaced the legacy dependency-list.sh with
spksrc.dependency-tree.mk, consolidating resolution into the framework
and enabling parallel builds — up to ~2.9× faster.
Pull request: #6952
April 19th – 22nd 2026 — Deduplicate applied patches (2 PRs)
- What: a patch listed both as an
archand as agroup(armv7, x64 could be both) was applied twice.spksrc.build/patch.mknow guarantees each patch appears once inPATCHES, with md5sum-based deduplication. - Why: applying a patch twice either fails or double-applies a hunk; the build should be independent of how a patch happened to be listed.
- Pull requests: #7098, #7104
April 5th – 7th 2026 — Standardize meta package variable names (4 PRs)
The ffmpeg, videodriver and python "meta" packages each exposed their install prefix under a differently-shaped variable name.
- What: standardized the
*_INSTALL_PREFIXvariables —VIDEODRV_*INSTALL_PREFIX(#7043),FFMPEG_*INSTALL_PREFIX(#7044),PYTHON_*INSTALL_PREFIX(#7045) — and the surrounding python / openssl / ffmpeg / videodriver variable names (#7041). - Why: a consumer of any meta package now finds the same variable shape regardless of which one it depends on. This is the naming half of the meta cross-dependency environment (the June 22nd–25th entry above).
- Pull requests: #7041, #7043, #7044, #7045
March – April 2026 — stage0 minimal environment (3 PRs)
- What:
spksrc.common/stage0.mkloads a minimal environment early — just enough for theversion_*macros andTC_GCC— so a package Makefile can callversion_*before the full toolchain environment exists (#7031), with a more robustBASEDIRdetection (#7032) and a follow-up fix for a subtle ordering bug (#7078). - Why: it keeps the full toolchain environment from leaking into the
dependency traversal, and lets version-gated
DEPENDSparse on a cold tree. The stage-0 toolchain bootstrap (#7184, above) builds on this. - Pull requests: #7031, #7032, #7078