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,MIN_RUSTC_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_LDFLAGSand are passed plainly, with-latomicauto-dropped where the gcc lacks it. They are not wrapped in--as-needed:LDFLAGSprecedes the objects, so a front-placed library there resolves nothing yet and the wrap discarded it outright. 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. -
Rust builds on the legacy archs, and overlays are a first-class notion. The archs
rustuphas no usablerust-stdfor — PowerPC e500 (qoriq,ppc853x), ARMv588f6281,x86-5.2— now get a Rust toolchain built from source, published as an archive and pulled in like any other dependency. That fixes long-standing failures on those archs (SPE float, the ppc853x TLS relocations,AtomicU64). It also generalises: a component shipped beside a base toolchain is an overlay, switched withOVERLAY_RUSTC/OVERLAY_BINUTILSfromlocal.mk, andmake helpshows which are active for your arch.
September 4th 2026 — Build logs keep what the console showed (#7396)
-
What was lost: a package refused by a pre-check left a build log holding a single line.
$(error)fires at parse time, so no recipe of the inner make ever runs -- and the teeing lived inside a recipe. The reason for the refusal, make's own*** ... Stop.and theError 1cascade all went to the console alone, and the[BEGIN]/[END]markers only ever reachedstatus-build.log. -
Teeing moved one level up. It now happens in
arch-%, above the make that fails, so that make's output and its exit cascade both pass through the pipe. A parse-time$(error)produces no recipe to tee from, which is why writing the message by hand was not enough on its own. precheck_fatalreplaces nine copies of the sameifneq/shell/errorblock and sends one message to all three readers: the console, the package's build log, and the CI's collected unsupported list. It also fixes a timestamp in that list -- written$(date --date=now ...)it was make expanding an undefined variable, so every line began with a bare" - "; escaped to$$(date ...)the shell runs it.- One logging mechanism instead of two.
LOG_WRAPPEDand the new helper carried the same body --script(1)for a pty so stderr is captured too,sedto strip ANSI on the way to the file, and aLOGGING_ENABLEDguard so exactly one level tees and nothing is written twice. They are now_runlog(the shell fragment, underscored because it needspipefailand bash from its caller) andRUNLOG(the facade: builds the make command from a goal, writes toDEFAULT_LOG, reports failure toSTATUS_LOG).LOG_WRAPPEDis gone; nine call sites renamed, no behaviour change. -
Toolchain lines name their directory. A status line for an overlay consumer logged an empty arch and called itself
toolchain, because the branch meant for it was guarded on$(TC_NAME)$(TC_VERS)and a consumer setsTC_VERSbut noTC_NAME. Both branches now use the directory name minus itssyno-prefix, so every overlay is visible:
Neither TC_NAME nor TC_ARCH can do this alone: a consumer sets no TC_NAME,
and on the generic toolchains TC_ARCH holds the reference model, so
syno-aarch64-6.2.4 would have logged rtd1296 and syno-x64-7.1 apollolake.
- TIME_CMD: GNU time(1) is now resolved by path. The recipes that pipe need
bash for pipefail, and under bash time is a keyword that rejects -o, so
PSTAT_TIME broke as soon as the teeing recipe switched shells. Empty if absent,
which simply disables PSTAT.
- Known limit: an error can only be captured one level above the make that
fails, so the outermost make: *** [arch-x64-7.1] Error 2 is structurally
unreachable -- there is no outer make to tee it.
- Package-facing: nothing changes for a successful build -- one [BEGIN], one
[END], no duplicated output. A package that calls the old LOG_WRAPPED should
use RUNLOG.
- Pull request: #7396
September 4th 2026 — Link librt and libatomic instead of discarding them (#7433)
-
What:
TC_EXTRA_LDFLAGSno longer wraps-lrt/-latomicin-Wl,--as-needed ... -Wl,--no-as-needed: -
Why:
--as-neededkeeps a library only if it resolves a symbol that is already undefined when the linker reaches it.LDFLAGSis placed before the objects and libraries being linked, so nothing is undefined yet and the bracketed-lrtwas dropped every time. The declaration introduced by #7314 was therefore inert — measurably identical to declaring nothing: -
How it surfaced: on glibc < 2.17 toolchains (
ppc853x-5.2,88f6281-5.2,88f6281-6.1),libsrt.socallsclock_gettimewithout declaring it. ffmpeg reported the resulting link failure asERROR: srt >= 1.3.0 not found using pkg-config, which reads as a missing dependency rather than a missing library. -
Package-facing: the five per-package workarounds it forced are gone —
cross/ffmpeg4throughcross/ffmpeg8each carried
to put back what the toolchain had already declared. A package should not have
to re-declare a toolchain library; if you have such a workaround, drop it.
- Cost: one DT_NEEDED entry on binaries that never call into librt or
libatomic. Both are part of the toolchain's own runtime and present on every
target that ships them.
- Also in this PR: dependency-list now keys its output on the package
folder instead of $(NAME). Three packages set SPK_NAME to something
else — spk/ffmpeg4 (ffmpeg), spk/mkvtoolnix_22 (mkvtoolnix),
spk/mono_58 (mono) — while every consumer of the list already looked the
key up as a directory (.github/actions/prepare.sh, spk-meta/base.mk). The
three silently fell out of the CI build matrix, and the latter two collided
with the mkvtoolnix / mono folders that still exist beside them. This is
the rule SPK_FOLDER already states in spksrc.rules/pre-check.mk. The
hand-maintained fixup list in prepare.sh (nzbdrone -> sonarr3,
python -> python2, both stale) is removed with it.
- Confirmed: with DSM 5.2 switched on for the run, cross/ffmpeg4 -- the only
package in the tree with no MIN_GCC_VERSION, and so the only one that reaches
a pre-2.17 toolchain -- built on both, with its workaround removed:
```
ffmpeg4: (ppc853x-5.2) DONE glibc 2.8, gcc 4.3.7
ffmpeg4: (88f6281-5.2) DONE glibc 2.15, gcc 4.6.4
```
- Documented in Extra flags a toolchain can declare.
- Pull request: #7433
August 17th 2026 — Custom from-source Rust toolchains, and the overlay family (#7353)
- What: the archs
rustupships no usable prebuiltrust-stdfor now build Rust from source —native/rustc-1.82produces arust-<id>-<rev>.txz, a per-arch consumer undertoolchain/syno-<arch>-<dsm>_rust-<vers>_gcc-<gcc>/downloads it, and the base toolchain pulls that in throughDEPENDS. Tier-3 PowerPC e500 (qoriq,ppc853x) has no prebuilt std at all; ARMv588f6281andx86-5.2only ship one built against a newer glibc than DSM provides. - Why it matters beyond Rust: it introduces the
OVERLAY_<component>family — a component shipped beside a base toolchain rather than replacing it. Rust and binutils 2.30 are the first two; a gcc-8.5 overlay is the next. Every decision is resolved once, inmk/spksrc.common/overlay.mk, which keeps three questions apart: available (TC_OVERLAY_<c>— the consumer dir), requested (OVERLAY_<c>), active (OVERLAY_<c>_ON). Conflating them is what produced the bugs this split now prevents. -
Switches, written to
local.mkbymake setupand overridable per build:with precedence
command line > environment > local.mk > defaults. A request that cannot be honored degrades to the stock tools and says so in a banner rather than failing.make helpincross/,spk/anddiyspk/prints the switches, the value in effect, and what it resolves to for your arch. -
Arch issues fixed along the way: per-target rustflags, so the PowerPC SPE codegen options actually reach the compiler (
-Ctarget-cpu=e500 -Ctarget-feature=+spe, not+efpu2) — the qoriqbat/lsdSIGILL of #7304; the ppc853x TLS/PIE relocations, by routing only the Rust link through binutils 2.30 while C keeps the vendoras/ld; andAtomicU64on 32-bit, by widening the target spec wherever the arch's gcc shipslibatomic(which unblockedhelixon qoriq). - Package-facing: nothing changes on a standard arch. On a legacy one, a package
that needs a newer toolchain than these pin should say so with
MIN_RUSTC_VERSION/MIN_GLIBC_VERSIONrather than an arch list. See Toolchain: custom from-source Rust. - Choosing instead of refusing: a floor refuses an arch, but the
cross/<pkg>virtuals have to pick a version of themselves instead.TC_RUSTCis now published besideTC_GCC/TC_GLIBC/TC_KERNEL— the rustc a toolchain pins, orstablewhen it uses rustup's newest — so a virtual routes with$(call version_ge,$(TC_RUSTC),<vers>).cross/bat,cross/ripgrep,cross/lsdandcross/ezamoved off$(ARMv5_ARCHS) $(PPC_ARCHS), which had been standing in for "pinned to rustc 1.82" and so silently missedx86-5.2, a fourth arch this PR pins.cross/helixlikewise replaced its ARMv7L exclusion withMIN_GCC_VERSION = 4.9: its C++ tree-sitter grammars build with-std=c++14, which g++ rejects before 4.9. Same story for the$(OLD_PPC_ARCHS)exclusions on the rust packages, which already named their reason in a comment:cross/batandcross/fdreachpipe2through a dependency crate, so they declareMIN_GLIBC_VERSION = 2.9;cross/ripgrep's pcre2 wants-std=c11, soMIN_GCC_VERSION = 4.6. Those lists read "except qoriq" whileqoriq-5.2runs the same glibc 2.8 and gcc 4.3.7 asppc853x, so the floors refuse an arch the lists let through.cross/sdandcross/ezalose their exclusion outright: their only blocker was std's ownpthread_setname_np, which the weak-link patch above resolves, and both are confirmed building and running on ppc853x (@hgy59) — sosynocli-fileships them there. - Pull request: #7353
July 24th 2026 — Host a native build's output as a reusable archive (#7327, #7386)
-
What: a new opt-in step,
spksrc.build/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.
- Relocated (#7386): moved spksrc.native/archive.mk →
spksrc.build/archive.mk — the helper is a shared build step, not native-only
(a from-source rustc reuses it in #7353). No behaviour change.
- Pull requests: #7327,
#7386
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. The wrap was removed in #7433 — see the September 4th 2026 entry: it discarded both libraries instead of pruning them. - 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 – August 24th 2026 — Bounded mirror fallback for downloads (4 PRs)
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 (#7254): the download logic was split into per-method macros
(
DOWNLOAD_GIT/SVN/HG/HTTP, dispatched onPKG_DOWNLOAD_METHOD), and a plain download now walks a list of candidate URLs, stopping at the first success. Mirrors of the big source hosts are tried automatically, andPKG_DIST_MIRRORSlets a package name its own fallback base URLs. Mirroring applies tohttponly — the VCS methods fetch a revision from one place and tar it themselves. - 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. -
The families since (#7349, #7384, #7401): the variables were renamed
<FAMILY>_MIRRORS→MIRROR_<FAMILY>, and the table grew to seven:
MIRROR_FREEDESKTOP is the one that does not preserve the path: its
mirrors flatten the layout, so only the file name is appended to each base
and the bases embed $(PKG_NAME). Every base is ?=, so local.mk can
override any of them.
- X.org was dropped in #7349, deliberately: no working mirror was found.
Its base pointed at mirror.csclub.uwaterloo.ca/x.org/releases, which
404s, and it matched only www.x.org/releases/ while every X.org package
in the tree downloads from www.x.org/archive/individual/.... A family
with no reachable mirror and no matching URL only adds candidates that
fail. Those packages have no automatic fallback as a result; libX11
moved to xorg.freedesktop.org and is covered by MIRROR_FREEDESKTOP.
The durable answer for the rest is the sources release, as below.
- Documented in
Source downloads and mirrors.
- Pull requests: #7254,
#7349,
#7384,
#7401
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