diff --git a/.gitignore b/.gitignore index 2583b54a..364c6267 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ debian/kernel.release debian/localversion debian/pkgversion +# Generated by debian/scripts/bundle-dkms-modules.sh during the build +debian/*-modules-*.substvars + # Generated maintainer scripts (versioned names, created during build) debian/linux-image-*.postinst debian/linux-image-*.preinst diff --git a/README.md b/README.md index 44c02bfa..74d0a647 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ its own values for: | `srcpkg` | Debian source package name. | | `binpkg` | Kernel image metapackage name. | | `kernel_config` | Extra fragments applied on top of `debian/config-available/`, all of which is applied to every build, one per array element. A bare name selects `debian/config-available/.config`; an `intree:` entry names a fragment shipped by the kernel source, as a path relative to the kernel source root (e.g. `intree:arch/arm64/configs/qcom_debug.config`), so it stays versioned with the kernel it targets. Empty for variants that need nothing beyond `config-available/`; today it carries only `intree:` fragments. `resolve-matrix.sh` joins it into the comma-separated `kernel-config` workflow input. | -| `dkms` | Out-of-tree DKMS modules built against this kernel and bundled into its `linux-image` package, as an object keyed by suite, each entry a list of modules named as the stem of their `-dkms` package (e.g. `kgsl`). A suite's entry is optional: a suite with no entry, and `{}` itself, bundles nothing. An empty list does the same for the suite it names. A listed module is a presence contract: a build fails rather than shipping an image without it. There is no default or fallback, so suites that can't build a given module (e.g. an Ubuntu-family suite lacking a package) simply list less, or omit an entry entirely. `resolve-matrix.sh` joins the resolved leg's suite into the comma-separated `dkms` workflow input. | +| `dkms` | Out-of-tree DKMS modules built against this kernel, as an object keyed by suite, each entry a list of modules named as the stem of their `-dkms` package (e.g. `kgsl`). Each module produces its own `-modules-` package plus an unversioned `-modules-` metapackage, rather than being installed into the image; a non-empty list also produces one `-modules` metapackage covering them all. A suite's entry is optional: a suite with no entry, and `{}` itself, builds nothing. An empty list does the same for the suite it names. A listed module is a presence contract: a build fails rather than publishing without it. There is no default or fallback, so suites that can't build a given module (e.g. an Ubuntu-family suite lacking a package) simply list less, or omit an entry entirely. `resolve-matrix.sh` joins the resolved leg's suite into the comma-separated `dkms` workflow input; see [debian/README.md](debian/README.md) for what the packaging does with it. | | `debian_version_stub` | Base Debian revision, shared by a variant's Daily and Release rows. Must not end in `~`; the suite suffix is derived, not stored here. | | `debian_version_suffix` | `~` for Daily rows, empty for Release rows. Documents the delivery-type half of the revision formula on the row itself; `resolve-matrix.sh` rejects a row where this disagrees with `type`, but derivation always computes this suffix from `type`, never reads this field. | | `localversion`, `kver_extra` | Optional version overrides forwarded to packaging. | @@ -253,8 +253,8 @@ This repository contains two separate parts: decides *how* it is built. This document covers the CI generator. For the packaging internals: `debian/rules` -targets, the config fragment merge pipeline, DKMS module bundling and the produced -package layout see [debian/README.md](debian/README.md). +targets, the config fragment merge pipeline, the out-of-tree DKMS module packages +and the produced package layout see [debian/README.md](debian/README.md). ```mermaid flowchart LR @@ -397,7 +397,11 @@ For the current matrix, package generation produces: | `linux-image-qcom-next__arm64.deb` | Image metapackage that tracks the newest kernel image. | | `linux-headers-__arm64.deb` | Versioned headers for DKMS and out-of-tree modules. | | `linux-headers-qcom-next__arm64.deb` | Headers metapackage. | -| `linux-image--dbg__arm64.deb` | Kernel and module debug symbols. | +| `linux-image--dbg__arm64.deb` | Kernel and in-tree module debug symbols. | +| `-modules-__arm64.deb` | Prebuilt out-of-tree modules, one package per `dkms` entry, built from that `-dkms` source against this kernel. Installs under `/lib/modules//updates/qli/`. Conflicts with `-dkms`. | +| `-modules--dbg__arm64.deb` | Debug symbols for the above. | +| `linux-image-qcom-next-modules-__arm64.deb` | Modules metapackage, one per `dkms` entry, that tracks the newest build of that module for this variant. | +| `linux-image-qcom-next-modules__arm64.deb` | Metapackage depending on every `linux-image-qcom-next-modules-` above, so the whole out-of-tree module set installs under one name. Built only when the variant has `dkms` entries. | `-rcN` remains in `uname -r`, module paths, boot assets, and versioned package names. Only the Debian version field converts it to `~rcN`, so a release @@ -427,7 +431,9 @@ sudo apt install linux-image-qcom-next When installing downloaded artifacts directly, install the versioned image and its metapackage together. Add the headers packages when DKMS or other -out-of-tree module builds are required. +out-of-tree module builds are required, and the module packages for whichever +out-of-tree modules the hardware needs — `linux-image-qcom-next-modules` takes +the whole set. ## Manual Builds diff --git a/build-kernel.sh b/build-kernel.sh index 147678a4..10be1f4c 100755 --- a/build-kernel.sh +++ b/build-kernel.sh @@ -71,11 +71,13 @@ OPTIONS: which is always applied in full. An "intree:" prefix names a path relative to the kernel source root (e.g. intree:arch/arm64/configs/qcom_debug.config) - --dkms LIST Comma-separated out-of-tree DKMS modules to build and - bundle into linux-image-, without the -dkms - suffix (e.g. --dkms kgsl,camx). Each entry needs its - -dkms package available to the build. Empty by - default (bundle nothing). + --dkms LIST Comma-separated out-of-tree DKMS modules to build + against this kernel, without the -dkms suffix + (e.g. --dkms kgsl,camx). Each entry needs its + -dkms package available to the build, and + ships in its own -modules- package + rather than in linux-image-. Empty by + default (build nothing). Paths: -k, --kernel-dir DIR Kernel source directory (default: $KERNEL_DIR) diff --git a/ci/build-matrix.json b/ci/build-matrix.json index e038ed8d..2d5fd875 100644 --- a/ci/build-matrix.json +++ b/ci/build-matrix.json @@ -21,6 +21,18 @@ "binpkg": "linux-image-qcom-next", "kernel_config": [], "dkms": { + "trixie": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ], + "forky": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ], "resolute": [ "kgsl" ] @@ -42,7 +54,20 @@ "srcpkg": "linux-qcom-next", "binpkg": "linux-image-qcom-next", "kernel_config": [], - "dkms": {}, + "dkms": { + "trixie": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ], + "forky": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ] + }, "debian_version_stub": "0qli1", "debian_version_suffix": "" }, @@ -63,7 +88,20 @@ "intree:arch/arm64/configs/qcom_debug.config", "intree:kernel/configs/debug.config" ], - "dkms": {}, + "dkms": { + "trixie": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ], + "forky": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ] + }, "debian_version_stub": "0qli1", "debian_version_suffix": "~" }, @@ -84,7 +122,20 @@ "intree:arch/arm64/configs/qcom_debug.config", "intree:kernel/configs/debug.config" ], - "dkms": {}, + "dkms": { + "trixie": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ], + "forky": [ + "kgsl", + "camx", + "iris-vpu", + "audioreach" + ] + }, "debian_version_stub": "0qli1", "debian_version_suffix": "" } diff --git a/debian/README.md b/debian/README.md index 424ea9cb..88326a21 100644 --- a/debian/README.md +++ b/debian/README.md @@ -19,6 +19,10 @@ Packages follow the standard Debian/Ubuntu kernel naming convention: | `linux-image--dbg` | Debug symbols | `linux-image-7.2.0-qcom-next-20260826-dbg` | | `` | Image metapackage tracking the newest kernel image | `linux-image-qcom-next` | | `` | Headers metapackage tracking the newest headers | `linux-headers-qcom-next` | +| `-modules-` | Prebuilt out-of-tree modules, one per `DKMS_MODULES` entry | `kgsl-modules-7.2.0-qcom-next-20260826` | +| `-modules--dbg` | Debug symbols for the above | `kgsl-modules-7.2.0-qcom-next-20260826-dbg` | +| `-modules-` | Modules metapackage tracking the newest build of that module | `linux-image-qcom-next-modules-kgsl` | +| `-modules` | Metapackage depending on every `-modules-` above | `linux-image-qcom-next-modules` | **``** is the full `kernelrelease` string (`uname -r`), which includes the base kernel version and the LOCALVERSION suffix encoding the variant and @@ -28,12 +32,19 @@ the versioned packages carry no separate flavour suffix. **``** and **``** are per-variant metapackage names set from the build matrix (`binpkg` / derived headers name). They stay constant across snapshots and depend on the newest versioned package, so installing -`linux-image-qcom-next` follows the latest build of that variant. - -`debian/control.in` declares all five, so a build publishes five binary packages -per variant. `dh_strip` is run with `--no-automatic-dbgsym`, so debhelper -generates no additional `-dbgsym` package: debug symbols are shipped only by the -declared `linux-image--dbg`. +`linux-image-qcom-next` follows the latest build of that variant. The module +metapackages extend `` rather than deriving a name of their own, so +every unversioned name a variant publishes shares one prefix and `apt search +linux-image-qcom-next` finds the whole set. + +`debian/control.in` declares the first five, so a build with no DKMS modules +publishes five binary packages per variant. Each `DKMS_MODULES` entry adds three +more, appended to `debian/control` by `prepare` from `debian/control-dkms.in`, +and a non-empty list adds one `-modules` stanza from +`debian/control-dkms-all.in`. A variant with two modules therefore publishes +twelve binary packages. `dh_strip` is run with `--no-automatic-dbgsym`, so +debhelper generates no additional `-dbgsym` package: debug symbols are shipped +only by the declared `-dbg` packages. **`-qcom`** is a static flavour suffix appended by the packaging, identifying Qualcomm-packaged kernels independently of the branch name. @@ -53,6 +64,8 @@ pkg-linux-qcom/ ├── build-source-package.sh ← Prepared tree → reproducible .orig.tar.gz, .dsc, .changes ├── debian/ │ ├── control.in ← Source-of-truth template (version-controlled) +│ ├── control-dkms.in ← Per-DKMS-module stanza template (version-controlled) +│ ├── control-dkms-all.in ← All-modules metapackage template (version-controlled) │ ├── changelog.in ← Source-of-truth template (version-controlled) │ ├── control ← Generated by 'prepare' (gitignored) │ ├── changelog ← Generated by 'prepare' (gitignored) @@ -75,7 +88,7 @@ pkg-linux-qcom/ │ │ └── usb-can.config ← USB CAN adapters │ ├── dkms-modules ← Generated by 'prepare' from DKMS_MODULES (gitignored) │ └── scripts/ -│ └── bundle-dkms-modules.sh ← DKMS build-and-bundle tool (called by rules; standalone-capable) +│ └── bundle-dkms-modules.sh ← DKMS build-and-stage tool (called by rules; standalone-capable) ├── .gitignore └── README.md ``` @@ -88,6 +101,8 @@ pkg-linux-qcom/ | File | Status | Description | |------|--------|-------------| | `debian/control.in` | ✅ Committed | Template with `@KVER@` placeholder | +| `debian/control-dkms.in` | ✅ Committed | Template for the `-modules-@KVER@` stanza group, appended once per `DKMS_MODULES` entry | +| `debian/control-dkms-all.in` | ✅ Committed | Template for the `@BINPKG@-modules` stanza, appended once when `DKMS_MODULES` is not empty | | `debian/changelog.in` | ✅ Committed | Template with `@KVER@` placeholder | | `debian/rules` | ✅ Committed | Build rules + `prepare` target | | `debian/linux-image.preinst.in` | ✅ Committed | Pre-install script template (`@KVER@` substituted at build time) | @@ -97,10 +112,11 @@ pkg-linux-qcom/ | `debian/config/*.config` | ✅ Committed | Always-applied config fragments | | `debian/config-available/*.config` | ✅ Committed | Packaging fragments, all applied to every build | | `debian/dkms-modules` | 🔄 Generated | Produced by `make -f debian/rules prepare DKMS_MODULES=...` | -| `debian/scripts/bundle-dkms-modules.sh` | ✅ Committed | DKMS build-and-bundle tool | +| `debian/scripts/bundle-dkms-modules.sh` | ✅ Committed | DKMS build-and-stage tool | | `debian/control` | 🔄 Generated | Produced by `make -f debian/rules prepare KVER=...` | | `debian/changelog` | 🔄 Generated | Produced by `make -f debian/rules prepare KVER=...` | | `debian/kernel.release` | 🔄 Generated | Produced during `dpkg-buildpackage` | +| `debian/*-modules-*.substvars` | 🔄 Generated | Written by `bundle-dkms-modules.sh` during `dpkg-buildpackage` (the `Built-Using` value) | --- @@ -153,17 +169,19 @@ make -f debian/rules prepare KVER=7.2.0-qcom-next-20260826 # With optional extra suffix (CI build ID, user tag, etc.): make -f debian/rules prepare LOCALVERSION=-qcom-next-20260826 KVER_EXTRA=-ci42 -# Selecting the out-of-tree DKMS modules to bundle: +# Selecting the out-of-tree DKMS modules to build: make -f debian/rules prepare LOCALVERSION=-qcom-next-20260826 DKMS_MODULES=kgsl,camx ``` This produces: - `debian/control` — with the versioned package names, e.g. `linux-image-7.2.0-qcom-next-20260826`, the metapackage names from the - matrix, e.g. `linux-image-qcom-next`, and one `-dkms` build dependency - per `DKMS_MODULES` entry + matrix, e.g. `linux-image-qcom-next`, one `-dkms` build dependency per + `DKMS_MODULES` entry, one group of `-modules-` stanzas per entry, + appended from `debian/control-dkms.in`, and the `-modules` stanza + from `debian/control-dkms-all.in` when the list is not empty - `debian/changelog` — with the source package name, e.g. `linux-qcom-next` -- `debian/dkms-modules` — the manifest of out-of-tree modules to bundle, +- `debian/dkms-modules` — the manifest of out-of-tree modules to build, written from `DKMS_MODULES` (empty when none were requested) **Why this step is required:** `dpkg-buildpackage` reads `debian/control` before @@ -328,6 +346,10 @@ CI writes every orig in one builder image. Installed paths: - `/boot/vmlinuz-` — compressed kernel image - `/boot/config-` — kernel `.config` +- `/boot/System.map-` — kernel symbol table. Required by the depmod + snippet debhelper generates for every package that ships a module: it is + guarded by `[ -e /boot/System.map- ]`, so without this file no + `modules.dep` is ever regenerated on the target. - `/lib/modules//` — stripped kernel modules - `/usr/lib/linux-image-/` — all DTBs (vendor subdirs preserved) - `/lib/modules//build` → `/usr/src/linux-headers-/` (symlink) @@ -426,7 +448,7 @@ script (`scripts/package/builddeb`). Debug symbols are installed under both can be installed simultaneously. Installed paths: -- `/usr/lib/debug/lib/modules//` — per-module debug symbols extracted via `objcopy --only-keep-debug` +- `/usr/lib/debug/lib/modules//kernel/` — in-tree module debug symbols extracted via `objcopy --only-keep-debug` - `/usr/lib/debug/lib/modules//vmlinux` — unstripped vmlinux (for `perf`, `crash`) - `/usr/lib/debug/boot/vmlinux-` → symlink to vmlinux (for `systemtap`) - `/usr/lib/debug/vmlinux-` → symlink to vmlinux (for `kdump-tools`) @@ -435,20 +457,69 @@ Depends on `linux-image-` (same version). Virtual packages provided: `linux-image-dbg` +### `-modules-` — Out-of-tree modules + +One package per `DKMS_MODULES` entry, built from that entry's `-dkms` +source against the kernel this same build produced. + +Installed paths: +- `/lib/modules//updates/qli/*.ko` — the modules, stripped + +Depends on `linux-image-` (same version), and `Conflicts`/`Replaces` +`-dkms`. Provides the virtual package `-modules`. + +Only the modules are shipped. Whatever `modprobe.d` snippets, udev rules or +initramfs hooks the `-dkms` package carries are not carried over with +them. + +### `-modules--dbg` — Out-of-tree module debug symbols + +Installed paths: +- `/usr/lib/debug/lib/modules//updates/qli/*.ko` — per-module debug symbols + +Depends on `-modules-` (same version). + +### `-modules-` and `-modules` — Module metapackages + +`-modules-` depends on the newest `-modules-` for +this variant, one per `DKMS_MODULES` entry, so the modules follow the kernel +across snapshots the way `` makes the image follow it. + +`-modules` depends on every `-modules-` above, so the +whole out-of-tree module set for the variant installs under a single name. It +is generated only when the module list is non-empty. + --- -## DKMS module bundling +## Out-of-tree DKMS modules Out-of-tree kernel modules selected for a build are built at -`dpkg-buildpackage` time and bundled directly into `linux-image-`. -The target device receives the pre-built `.ko` without needing a compiler, -kernel headers, or DKMS tooling installed. +`dpkg-buildpackage` time, against the kernel that same build produced, and each +one ships in its own binary package. The target device receives the pre-built +`.ko` without needing a compiler, kernel headers, or DKMS tooling installed. + +The modules are **not** installed into `linux-image-`. A system installs +the modules it has hardware for and nothing else, which is what the packages +above are for: the choice is a package, not a rootfs-wide `/etc/modprobe.d` +blacklist that would outlive the kernel it was written for. + +`-modules-` depends on the exact `linux-image-` it was built +against, and `Conflicts`/`Replaces` `-dkms`. The two forms are +deliberately mutually exclusive: a `-dkms` install would build a second copy of +the same modules into `/lib/modules//updates/dkms/`, at the same depmod +precedence as the copy shipped here, leaving which one loads up to depmod's +ordering rather than to a decision anyone made. + +`Built-Using` names the source package and version the modules were compiled +from, so the archive retains that source alongside the binary. The module +source itself is never copied into `src:linux-qcom-next`; the existing DKMS +source packages are unchanged by any of this. The module set is an input to the build, not a property of the packaging: it comes in as a comma-separated list (`prepare-source.sh --dkms`, `build-kernel.sh --dkms`, or `DKMS_MODULES=` straight to `debian/rules -prepare`), and CI supplies it from the `dkms` field of its build matrix. No -modules are bundled when the list is empty. +prepare`), and CI supplies it from the `dkms` field of its build matrix. An +empty list produces no module packages at all. ### How it works @@ -463,18 +534,33 @@ packages have been staged. The script: 3. Reads `PACKAGE_NAME` / `PACKAGE_VERSION` from the package's `dkms.conf`. 4. Builds with `dkms build` against the staged kernel headers, using a private `--dkmstree` (`mktemp`) to avoid writing to the root-owned `/var/lib/dkms/`. + DKMS remains the abstraction: the packaging never learns how a given module + builds. 5. Judges the outcome by `.ko` artifact presence, not `dkms` exit code. On failure: prints `make.log` tail (compile error) or `BUILD_EXCLUSIVE` gate analysis (skip), then hard-fails — a manifest entry is a presence contract. -6. For each produced `.ko`: collision-checks against already-bundled and in-tree - modules; installs to `lib/modules//extra/`; extracts debug symbols via - `objcopy --only-keep-debug` into the `-dbg` package; strips with - `strip --strip-debug` (required for kernel modules — a full strip drops the - symtab and relocations needed by the module loader). +6. For each produced `.ko`: collision-checks against modules already staged by + this run and against in-tree modules; installs to + `-modules-/lib/modules//updates/qli/`; extracts debug + symbols via `objcopy --only-keep-debug` into the matching `-dbg` package; + strips with `strip --strip-debug` (required for kernel modules — a full + strip drops the symtab and relocations needed by the module loader). +7. Writes `debian/.substvars` with the `Built-Using` value read from the + `-dkms` package. + +`updates/` rather than `extra/`: `extra/` is not in depmod's default search +order on Debian at all — it is Fedora's convention — so a module placed there +has the *lowest* precedence. `updates/qli/` is the structural sibling of the +`updates/dkms/` a real DKMS install uses. + +`/boot/System.map-` matters here. The depmod snippet debhelper generates +for each of these packages is guarded by `[ -e /boot/System.map- ]`, so +without the image shipping that file nothing would regenerate `modules.dep` on +the target and the modules would not be loadable. ### Selecting modules -`debian/rules prepare` turns one `DKMS_MODULES` list into the two things a +`debian/rules prepare` turns one `DKMS_MODULES` list into the three things a build needs, so they cannot drift apart: ```bash @@ -484,18 +570,23 @@ prepare-source.sh --source-dir /path/to/kernel --dkms kgsl,camx - `debian/control` gains `dkms, kgsl-dkms, camx-dkms` in `Build-Depends`, so the module sources are installed in the build environment. An empty list adds nothing, not even `dkms`. +- `debian/control` also gains a `-modules-` stanza group per entry, + appended from `debian/control-dkms.in`: the versioned modules package, its + `-dbg`, and the unversioned `-modules-` metapackage that depends + on the versioned one. A non-empty list adds one `-modules` stanza on + top, from `debian/control-dkms-all.in`, depending on every per-module + metapackage, so the whole set installs under a single name. - `debian/dkms-modules` is written as the manifest `bundle-dkms-modules.sh` reads — one name per line, without the `-dkms` suffix. -Both files are generated and gitignored; neither is edited by hand. Entries are +All are generated and gitignored; none is edited by hand. Entries are validated at `prepare` time: a name must look like a package stem, must omit the `-dkms` suffix, and must not repeat. A listed module is a presence contract — if it fails to build, or its `BUILD_EXCLUSIVE` gates exclude this kernel, the package build fails rather than -shipping a kernel image without it. To stop bundling a module, drop it from the -list its build was given (for CI builds, the `dkms` field in -`ci/build-matrix.json`). +publishing without it. To stop building a module, drop it from the list its +build was given (for CI builds, the `dkms` field in `ci/build-matrix.json`). ### Standalone developer use @@ -507,9 +598,13 @@ debian/scripts/bundle-dkms-modules.sh \ --kver 6.12.0-qcom-next-20260210 \ --headers-dir /path/to/kernel-source/debian/linux-headers-6.12.0-qcom-next-20260210/usr/src/linux-headers-6.12.0-qcom-next-20260210 \ --image-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210 \ - --dbg-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210-dbg + --stage-root /path/to/kernel-source/debian ``` +`--image-pkg-dir` is read, never written: it supplies the in-tree module list +for the collision check and `boot/config-` for `BUILD_EXCLUSIVE_CONFIG` +analysis. The module packages are staged under `--stage-root`. + Run `debian/scripts/bundle-dkms-modules.sh --help` for full usage, prerequisites, and all available options (`--arch`, `--objcopy`, `--modules-manifest`). @@ -543,6 +638,9 @@ sudo dpkg -i linux-headers-_1-1_arm64.deb # Install debug symbols sudo dpkg -i linux-image--dbg_1-1_arm64.deb +# Install an out-of-tree module set built against this kernel +sudo dpkg -i kgsl-modules-_1-1_arm64.deb + # Remove a specific kernel version (does not affect other installed kernels) sudo dpkg -r linux-image- diff --git a/debian/clean b/debian/clean index 39dfb22e..3e21df42 100644 --- a/debian/clean +++ b/debian/clean @@ -1,4 +1,5 @@ debian/kernel.release +debian/*-modules-*.substvars debian/linux-image-*.postinst debian/linux-image-*.preinst debian/linux-image-*.postrm diff --git a/debian/control-dkms-all.in b/debian/control-dkms-all.in new file mode 100644 index 00000000..da1e6d2d --- /dev/null +++ b/debian/control-dkms-all.in @@ -0,0 +1,9 @@ +Package: @BINPKG@-modules +Architecture: arm64 +Section: kernel +Depends: ${misc:Depends}@DKMS_META_DEPENDS@ +Description: Qualcomm ARM64 out-of-tree kernel modules (meta-package) + This meta-package always depends on every out-of-tree module meta-package + built for the latest kernel image of this variant, so installing it alongside + @BINPKG@ pulls in the full module set and keeps it in step with the kernel + across apt upgrades. diff --git a/debian/control-dkms.in b/debian/control-dkms.in new file mode 100644 index 00000000..8bd2942c --- /dev/null +++ b/debian/control-dkms.in @@ -0,0 +1,42 @@ +Package: @MODNAME@-modules-@KVER@ +Architecture: arm64 +Section: kernel +Depends: ${misc:Depends}, linux-image-@KVER@ (= ${binary:Version}) +Conflicts: @MODNAME@-dkms +Replaces: @MODNAME@-dkms +Provides: @MODNAME@-modules +Built-Using: ${dkms:Built-Using} +Description: @MODNAME@ kernel modules for Qualcomm ARM64 Linux @KVER@ + Out-of-tree @MODNAME@ modules, built from the @MODNAME@-dkms source against + kernel release @KVER@ and shipped prebuilt, so nothing has to be compiled on + the target and neither dkms nor the kernel headers need to be installed there. + . + The modules are installed in /lib/modules/@KVER@/updates/qli/. Only the + modules are shipped: any modprobe.d snippets, udev rules or initramfs hooks + the @MODNAME@-dkms package carries are not carried over with them. + . + This package conflicts with @MODNAME@-dkms. That package would build a second + copy of the same modules into /lib/modules/@KVER@/updates/dkms/, which sits at + the same depmod precedence as the copy here, so which one loaded would depend + on depmod's ordering rather than on a decision anyone made. + +Package: @MODNAME@-modules-@KVER@-dbg +Architecture: arm64 +Section: debug +Priority: optional +Depends: ${misc:Depends}, @MODNAME@-modules-@KVER@ (= ${binary:Version}) +Description: @MODNAME@ kernel module debug symbols for @KVER@ + Debug symbols for the @MODNAME@ modules in @MODNAME@-modules-@KVER@, following + the GNU debuglink layout used by the upstream kernel builddeb script. + . + Files installed: + - /usr/lib/debug/lib/modules/@KVER@/updates/qli/ (per-module .ko debug symbols) + +Package: @BINPKG@-modules-@MODNAME@ +Architecture: arm64 +Section: kernel +Depends: @MODNAME@-modules-@KVER@ (= ${binary:Version}), ${misc:Depends} +Description: @MODNAME@ kernel modules for Qualcomm ARM64 Linux (meta-package) + This meta-package always depends on the @MODNAME@ modules built for the latest + @BINPKG@ kernel image, so installing it keeps the @MODNAME@ modules in step + with the kernel across apt upgrades. diff --git a/debian/control.in b/debian/control.in index 6741d426..94603330 100644 --- a/debian/control.in +++ b/debian/control.in @@ -23,6 +23,7 @@ Description: Qualcomm ARM64 Linux kernel image @KVER@ Files installed: - /boot/vmlinuz-@KVER@ - /boot/config-@KVER@ + - /boot/System.map-@KVER@ - /lib/modules/@KVER@/ - /usr/lib/linux-image-@KVER@/ (DTBs) - /usr/lib/firmware/@KVER@/device-tree (Ubuntu flash-kernel symlink) diff --git a/debian/rules b/debian/rules index 7891ccf8..c94f9db8 100755 --- a/debian/rules +++ b/debian/rules @@ -61,12 +61,20 @@ KREL_FILE := debian/kernel.release # SRCPKG Source package name (default: linux-qcom-next). # BINPKG Binary metapackage name (default: linux-image-qcom-next). # DEBIAN_REVISION Debian revision component of the package version (default: 0qcom1). -# DKMS_MODULES Comma-separated out-of-tree DKMS modules to build and bundle -# into linux-image-, each named without the -dkms suffix -# (e.g. DKMS_MODULES=kgsl,camx). Empty (the default) bundles +# DKMS_MODULES Comma-separated out-of-tree DKMS modules to build and ship, +# each named without the -dkms suffix (e.g. +# DKMS_MODULES=kgsl,camx). Empty (the default) builds # nothing. Each name must have a -dkms package available # in the build environment; the Build-Depends entry for it is # generated from this list, so nothing else declares it. +# Each entry produces its own binary packages, the versioned +# -modules- and -modules--dbg plus +# the unversioned -modules- metapackage, whose +# control stanzas are generated from debian/control-dkms.in. +# A non-empty list also produces one -modules +# metapackage depending on all of them, generated from +# debian/control-dkms-all.in. +# The modules are NOT installed into linux-image-. # GIT_CLONE Kernel repository URL, recorded in debian/changelog. # GIT_REF Resolved kernel ref (tag or branch), recorded in the changelog. # GIT_SHA Full kernel HEAD commit SHA, recorded in the changelog. @@ -83,7 +91,11 @@ KREL_FILE := debian/kernel.release # by hand. # # Outputs (generated files, all listed in debian/clean): -# debian/control Substituted from debian/control.in +# debian/control Substituted from debian/control.in, with one group of +# stanzas per DKMS_MODULES entry appended from +# debian/control-dkms.in, plus a single all-modules +# metapackage stanza from debian/control-dkms-all.in +# when the list is not empty # debian/changelog Substituted from debian/changelog.in # debian/dkms-modules Manifest of the DKMS_MODULES entries, read at build time # by debian/scripts/bundle-dkms-modules.sh. Always written, @@ -243,6 +255,41 @@ prepare: -e "s|@PKGVER@|$$PKG_VERSION|g" \ -e "s|@DKMS_BUILD_DEPENDS@|$$DKMS_BUILD_DEPENDS|g" \ debian/control.in > debian/control; \ + \ + # One group of binary package stanzas per bundled module, appended to the + # generated control. Every module named in DKMS_MODULES ships as its own + # -modules- package rather than being copied into the image, so + # the stanzas cannot be spelled in control.in: there is one per entry in a + # list that is a build input. + # + # Appended from the same DKMS_LIST that drives Build-Depends and the + # dkms-modules manifest, so a module cannot be declared in control without + # also being built, or built without being declared. + for mod in $$DKMS_LIST; do \ + printf '\n' >> debian/control; \ + sed \ + -e "s|@MODNAME@|$$mod|g" \ + -e "s|@KVER@|$$KVER_RESOLVED|g" \ + -e "s|@BINPKG@|$(BINPKG)|g" \ + debian/control-dkms.in >> debian/control; \ + done; \ + \ + # One more stanza, once, when anything was bundled: the metapackage that + # pulls in every per-module metapackage above, so the whole out-of-tree + # module set for a variant installs under a single name. + # It depends on those metapackages rather than on the versioned packages, + # so what it names does not have to be re-resolved on every snapshot. + if [ -n "$$DKMS_LIST" ]; then \ + DKMS_META_DEPENDS=""; \ + for mod in $$DKMS_LIST; do \ + DKMS_META_DEPENDS="$$DKMS_META_DEPENDS, $(BINPKG)-modules-$$mod (= \$${binary:Version})"; \ + done; \ + printf '\n' >> debian/control; \ + sed \ + -e "s|@BINPKG@|$(BINPKG)|g" \ + -e "s|@DKMS_META_DEPENDS@|$$DKMS_META_DEPENDS|g" \ + debian/control-dkms-all.in >> debian/control; \ + fi; \ sed \ -e "s|@SRCPKG@|$(SRCPKG)|g" \ -e "s|@PKGVER@|$$PKG_VERSION|g" \ @@ -421,10 +468,20 @@ override_dh_auto_install: [ -n "$$OBJ_DIR" ] && CFG_PATH="$$OBJ_DIR/.config"; \ [ -f "$$CFG_PATH" ] || { echo "ERROR: Missing .config at $$CFG_PATH"; exit 1; }; \ \ + # Locate System.map (objdir-aware). Required, not optional: the depmod + # snippet debhelper generates for every package shipping a module is + # guarded by [ -e /boot/System.map- ], so without this file the + # snippet is a no-op and nothing on the target ever regenerates + # modules.dep. + MAP_PATH="System.map"; \ + [ -n "$$OBJ_DIR" ] && MAP_PATH="$$OBJ_DIR/System.map"; \ + [ -f "$$MAP_PATH" ] || { echo "ERROR: Missing System.map at $$MAP_PATH"; exit 1; }; \ + \ # /boot artifacts mkdir -p "$$PKG/boot"; \ cp -a -T "$$IMG_PATH" "$$PKG/boot/vmlinuz-$$BASE"; \ cp -a -T "$$CFG_PATH" "$$PKG/boot/config-$$BASE"; \ + cp -a -T "$$MAP_PATH" "$$PKG/boot/System.map-$$BASE"; \ \ # Modules (stripped) $(MAKE) $(KBUILD_O_ARG) ARCH=$(ARCH) modules_install \ @@ -518,13 +575,17 @@ override_dh_auto_install: # Delegated to debian/scripts/bundle-dkms-modules.sh. # The script reads debian/dkms-modules, builds each listed module against the # staging headers produced earlier in this same dpkg-buildpackage run, and - # bundles the resulting .ko files into the linux-image and -dbg staging trees. + # stages the resulting .ko files into debian/-modules-/ and their + # debug symbols into debian/-modules--dbg/ — the packages whose + # control stanzas 'prepare' generated from the same manifest. + # --image-pkg-dir is read, not written: the in-tree module list for the + # collision check and boot/config- for BUILD_EXCLUSIVE_CONFIG analysis. # It can also be invoked directly by a developer — see the script's --help. $(CURDIR)/debian/scripts/bundle-dkms-modules.sh \ --kver "$$BASE" \ --headers-dir "$(CURDIR)/$$HDRDIR" \ --image-pkg-dir "$(CURDIR)/$$PKG" \ - --dbg-pkg-dir "$(CURDIR)/$$DBG_PKG" \ + --stage-root "$(CURDIR)/debian" \ --arch "$(DKMS_ARCH)" \ --objcopy "$(OBJCOPY)" \ --modules-manifest "$(CURDIR)/debian/dkms-modules" diff --git a/debian/scripts/bundle-dkms-modules.sh b/debian/scripts/bundle-dkms-modules.sh index 28bad3b6..7c31e99d 100755 --- a/debian/scripts/bundle-dkms-modules.sh +++ b/debian/scripts/bundle-dkms-modules.sh @@ -3,14 +3,19 @@ # SPDX-License-Identifier: BSD-3-Clause set -euo pipefail -# bundle-dkms-modules.sh — Build and bundle out-of-tree DKMS modules into the -# linux-image staging tree at dpkg-buildpackage time. +# bundle-dkms-modules.sh — Build out-of-tree DKMS modules against the kernel +# being produced and stage each one into its own binary package tree at +# dpkg-buildpackage time. # # This script is the single source of truth for DKMS module integration. # It is called by debian/rules override_dh_auto_install after the kernel image, # modules, headers, and debug packages have been staged, and can also be invoked # directly by a developer who has already staged those trees manually. # +# The modules do NOT go into linux-image-. Each one ships in its own +# -modules- package, whose control stanza 'debian/rules prepare' +# generated from the same manifest entry that brings the module here. +# # What it does (for each module listed in the manifest): # 1. Resolves the installed -dkms package via dpkg -L (authoritative, no globbing). # 2. Reads PACKAGE_NAME / PACKAGE_VERSION from the package's dkms.conf. @@ -22,9 +27,9 @@ set -euo pipefail # On failure: adds BUILD_EXCLUSIVE gate analysis when dkms attempted no # build, then hard-fails — a manifest entry is a presence contract. # 6. For each produced .ko: -# - Collision-checks against already-bundled modules and in-tree modules. -# - Installs to /lib/modules//extra/.ko -# - Extracts debug symbols to /usr/lib/debug/lib/modules//extra/.ko +# - Collision-checks against already-staged modules and in-tree modules. +# - Installs to /-modules-/lib/modules//updates/qli/.ko +# - Extracts debug symbols to /-modules--dbg/usr/lib/debug/lib/modules//updates/qli/.ko # via objcopy --only-keep-debug (Stage 1, non-destructive). # - Strips the shipped copy with `strip --strip-debug` (Stage 2). # --strip-debug is required for kernel modules: a full strip drops the @@ -34,7 +39,8 @@ set -euo pipefail # - The kernel image staging tree must exist at --image-pkg-dir with: # lib/modules//kernel/ (in-tree modules, for collision detection) # boot/config- (kernel .config, for BUILD_EXCLUSIVE_CONFIG checks) -# - The debug package staging tree must exist at --dbg-pkg-dir. +# - The directory holding the per-package staging trees must exist at +# --stage-root (absolute). In a source package this is debian/. # - The kernel headers must be fully staged at --headers-dir (absolute path). # This is the directory containing Makefile, include/, scripts/, arch/, etc. # It must be an absolute path: dkms invokes make from inside the module @@ -49,7 +55,7 @@ set -euo pipefail # --kver "$BASE" \ # --headers-dir "$(CURDIR)/debian/linux-headers-$BASE/usr/src/linux-headers-$BASE" \ # --image-pkg-dir "$(CURDIR)/debian/linux-image-$BASE" \ -# --dbg-pkg-dir "$(CURDIR)/debian/linux-image-$BASE-dbg" \ +# --stage-root "$(CURDIR)/debian" \ # --arch "$(DKMS_ARCH)" \ # --objcopy "$(OBJCOPY)" \ # --modules-manifest "$(CURDIR)/debian/dkms-modules" @@ -59,7 +65,7 @@ set -euo pipefail # --kver 6.12.0-qcom-next-20260210 \ # --headers-dir /path/to/kernel-source/debian/linux-headers-6.12.0-qcom-next-20260210/usr/src/linux-headers-6.12.0-qcom-next-20260210 \ # --image-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210 \ -# --dbg-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210-dbg +# --stage-root /path/to/kernel-source/debian SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -76,7 +82,7 @@ _DKMS_SRC_ROOT="${_BUNDLE_DKMS_SRC_ROOT:-/usr/src}" KVER="" HEADERS_DIR="" IMAGE_PKG_DIR="" -DBG_PKG_DIR="" +STAGE_ROOT="" # Default manifest: debian/dkms-modules (one level up from debian/scripts/) MODULES_MANIFEST="${SCRIPT_DIR}/../dkms-modules" # dkms --arch speaks uname -m vocabulary (aarch64), not kbuild vocabulary (arm64). @@ -112,16 +118,20 @@ REQUIRED: module source directory, so a relative path fails. In debian/rules this is: \$(CURDIR)/debian/linux-headers-\$BASE/usr/src/linux-headers-\$BASE - --image-pkg-dir DIR Path to the linux-image staging tree root. - .ko files are installed under: - /lib/modules//extra/ + --image-pkg-dir DIR Path to the linux-image staging tree root. Read + from, never written to: it supplies the in-tree + module list for the collision check and + boot/config- for BUILD_EXCLUSIVE_CONFIG + analysis. In debian/rules this is: \$(CURDIR)/debian/linux-image-\$BASE - --dbg-pkg-dir DIR Path to the debug package staging tree root. - Debug symbols are installed under: - /usr/lib/debug/lib/modules//extra/ + --stage-root DIR Directory the per-package staging trees live in. + MUST be absolute, and must already exist. + Each module is staged into two trees below it: + /-modules-/ + /-modules--dbg/ In debian/rules this is: - \$(CURDIR)/debian/linux-image-\$BASE-dbg + \$(CURDIR)/debian OPTIONAL: --modules-manifest FILE Path to the dkms-modules manifest. @@ -144,8 +154,9 @@ PREREQUISITES (developer standalone use): 2. --headers-dir must point to a fully staged kernel headers tree. 3. --image-pkg-dir must contain lib/modules//kernel/ (in-tree modules) and boot/config- (kernel .config). - 4. --dbg-pkg-dir must exist (can be empty; subdirs are created as needed). - 5. --headers-dir must be an absolute path. + 4. --stage-root must exist; it is the directory per-package staging trees + are created in, i.e. debian/ in a source package. + 5. --headers-dir and --stage-root must be absolute paths. 6. This script does NOT cross-compile: dkms builds each module with the host toolchain (no ARCH/CROSS_COMPILE is plumbed). Run it on a native arm64 host (or an arm64 chroot / qemu-user environment) so the produced .ko @@ -165,14 +176,14 @@ EXAMPLES: --kver 6.12.0-qcom-next-20260210 \\ --headers-dir /build/kernel/debian/linux-headers-6.12.0-qcom-next-20260210/usr/src/linux-headers-6.12.0-qcom-next-20260210 \\ --image-pkg-dir /build/kernel/debian/linux-image-6.12.0-qcom-next-20260210 \\ - --dbg-pkg-dir /build/kernel/debian/linux-image-6.12.0-qcom-next-20260210-dbg + --stage-root /build/kernel/debian # Developer standalone path: debian/scripts/bundle-dkms-modules.sh \\ --kver 6.12.0-qcom-next-20260210 \\ --headers-dir /path/to/staged/linux-headers-6.12.0-qcom-next-20260210 \\ --image-pkg-dir /path/to/staged/linux-image-6.12.0-qcom-next-20260210 \\ - --dbg-pkg-dir /path/to/staged/linux-image-6.12.0-qcom-next-20260210-dbg \\ + --stage-root /path/to/staged/debian \\ --arch aarch64 # With explicit manifest and objcopy: @@ -180,7 +191,7 @@ EXAMPLES: --kver 6.12.0-qcom-next-20260210 \\ --headers-dir /path/to/headers \\ --image-pkg-dir /path/to/image-pkg \\ - --dbg-pkg-dir /path/to/dbg-pkg \\ + --stage-root /path/to/debian \\ --modules-manifest /path/to/debian/dkms-modules \\ --objcopy aarch64-linux-gnu-objcopy EOF @@ -198,7 +209,7 @@ while [[ $# -gt 0 ]]; do --kver) require_val "$@"; KVER="$2"; shift 2 ;; --headers-dir) require_val "$@"; HEADERS_DIR="$2"; shift 2 ;; --image-pkg-dir) require_val "$@"; IMAGE_PKG_DIR="$2"; shift 2 ;; - --dbg-pkg-dir) require_val "$@"; DBG_PKG_DIR="$2"; shift 2 ;; + --stage-root) require_val "$@"; STAGE_ROOT="$2"; shift 2 ;; --modules-manifest) require_val "$@"; MODULES_MANIFEST="$2"; shift 2 ;; --arch) require_val "$@"; DKMS_ARCH="$2"; shift 2 ;; --objcopy) require_val "$@"; OBJCOPY="$2"; shift 2 ;; @@ -216,7 +227,7 @@ _missing=() [[ -n "$KVER" ]] || _missing+=(--kver) [[ -n "$HEADERS_DIR" ]] || _missing+=(--headers-dir) [[ -n "$IMAGE_PKG_DIR" ]] || _missing+=(--image-pkg-dir) -[[ -n "$DBG_PKG_DIR" ]] || _missing+=(--dbg-pkg-dir) +[[ -n "$STAGE_ROOT" ]] || _missing+=(--stage-root) if [[ ${#_missing[@]} -gt 0 ]]; then log_error "Missing required arguments: ${_missing[*]}" log_error "Run with --help for usage." @@ -232,6 +243,14 @@ fi exit 1 } +# --stage-root must be absolute for the same reason every other staging path +# here is: this script is called with the build tree's cwd from debian/rules +# but is also documented as standalone-callable from anywhere. +[[ "$STAGE_ROOT" == /* ]] || { + log_error "--stage-root must be an absolute path (got: $STAGE_ROOT)" + exit 1 +} + # --------------------------------------------------------------------------- # Read and parse the manifest # --------------------------------------------------------------------------- @@ -275,15 +294,18 @@ fi log_error "The linux-image staging tree must exist before calling this script." exit 1 } -if [[ ! -d "$DBG_PKG_DIR" ]]; then - log_warn "--dbg-pkg-dir does not exist: $DBG_PKG_DIR (will be created as needed)" -fi +[[ -d "$STAGE_ROOT" ]] || { + log_error "--stage-root does not exist: $STAGE_ROOT" + log_error "It is the directory the per-package staging trees are created in" + log_error "(debian/ in a source package), so it must already be there." + exit 1 +} log_step "DKMS module bundling configuration:" log_info " kver: $KVER" log_info " headers-dir: $HEADERS_DIR" log_info " image-pkg-dir: $IMAGE_PKG_DIR" -log_info " dbg-pkg-dir: $DBG_PKG_DIR" +log_info " stage-root: $STAGE_ROOT" log_info " modules-manifest: $MODULES_MANIFEST" log_info " arch: $DKMS_ARCH" log_info " objcopy: $OBJCOPY" @@ -330,6 +352,17 @@ echo DKMS_TREE="$(mktemp -d)" trap 'rm -rf "$DKMS_TREE"' EXIT +# --------------------------------------------------------------------------- +# Every module staged by this run, keyed on the basename it will install under. +# +# Each module package stages into its own tree, but on the target they all +# unpack into one /lib/modules//updates/qli/, so two modules sharing a +# basename still collide there. A per-tree existence check cannot see that; +# this can. The value is the manifest entry that claimed the name, so the +# error can say which one. +# --------------------------------------------------------------------------- +declare -A seen_ko=() + # --------------------------------------------------------------------------- # Main loop: build and bundle each listed module # --------------------------------------------------------------------------- @@ -477,10 +510,26 @@ for name in $DKMS_MODULES; do [[ -z "$comp" ]] || \ echo " Note: found compressed module output ($comp); compressed dkms output is not supported." >&2 fi - log_error "Refusing to ship linux-image-$KVER without $PKG_NAME." + log_error "Refusing to ship $name-modules-$KVER without $PKG_NAME." exit 1 fi + # ── Per-module staging trees ───────────────────────────────────────────── + # The modules ship in -modules-, not in the kernel image, and + # their debug files in the matching -dbg package. Both names are the ones + # 'debian/rules prepare' spelled into debian/control from the same manifest + # entry, so a typo here surfaces as an empty package rather than silently + # landing in the wrong one -- which is what the post-staging assertion + # below is for. + # + # $name, not $PKG_NAME: the package name stem is what the manifest, the + # Build-Depends entry and the control stanza all agree on. dkms.conf's + # PACKAGE_NAME is free to differ from it (and does -- iris-vpu builds + # iris_vpu), and is only used to address the dkms tree. + MOD_PKG_DIR="$STAGE_ROOT/$name-modules-$KVER" + MOD_DBG_DIR="$STAGE_ROOT/$name-modules-$KVER-dbg" + staged_ko=0 + # ── Install, extract debug, and strip each produced .ko ────────────────── # Mirror the in-tree module treatment: # Stage 1 — install the .ko (unstripped at this point) @@ -495,22 +544,30 @@ for name in $DKMS_MODULES; do # the script on collision errors. while IFS= read -r ko; do b="$(basename "$ko")" - dest="$IMAGE_PKG_DIR/lib/modules/$KVER/extra/$b" - dbg="$DBG_PKG_DIR/usr/lib/debug/lib/modules/$KVER/extra/$b" - - # Guard: duplicate bundled module name (two manifest entries → same basename) - if [[ -e "$dest" ]]; then + dest="$MOD_PKG_DIR/lib/modules/$KVER/updates/qli/$b" + dbg="$MOD_DBG_DIR/usr/lib/debug/lib/modules/$KVER/updates/qli/$b" + + # Guard: duplicate module name across every module staged by this run. + # Checked against the run-scoped set rather than the destination tree: + # each module package has its own tree, so two packages claiming one + # basename would each look unoccupied while still colliding in the + # single updates/qli/ they share once installed. + if [[ -n "${seen_ko[$b]:-}" ]]; then log_error "Duplicate bundled module name: $b" - log_error "Already bundled by an earlier manifest entry — check $MODULES_MANIFEST" + log_error "Already staged by manifest entry '${seen_ko[$b]}' — check $MODULES_MANIFEST" exit 1 fi + seen_ko[$b]="$name" # Guard: in-tree collision (bundled module shares name with an in-tree module) intree="$(find "$IMAGE_PKG_DIR/lib/modules/$KVER/kernel" \ -name "$b" -print -quit 2>/dev/null || true)" if [[ -n "$intree" ]]; then log_error "Bundled module $b collides with in-tree module: $intree" - log_error "Module precedence on the target would be ambiguous (depmod search order)." + log_error " in-tree: $intree" + log_error " this one: /lib/modules/$KVER/updates/qli/$b" + log_error "updates/ outranks kernel/ in depmod's default search order, so" + log_error "this module would silently take precedence over the in-tree one." exit 1 fi @@ -558,12 +615,63 @@ for name in $DKMS_MODULES; do # Stage 3: strip the shipped copy in place strip --strip-debug "$dest" + staged_ko=$((staged_ko + 1)) + log_info " Installed: $dest (stripped)" log_info " Debug: $dbg" done < <(printf '%s\n' "$kos") - log_info "Bundled $PKG_NAME modules into $(basename "$IMAGE_PKG_DIR")" + # ── Assert the package is not empty ────────────────────────────────────── + # dkms produced at least one .ko -- that is checked above -- but nothing + # downstream notices if none of them reached the staging tree. A binary + # package with an empty tree builds cleanly: dh_installdeb creates DEBIAN/, + # dh_gencontrol and dh_builddeb are happy, and dh_missing does not look + # here. The result is a published .deb containing nothing. + # + # Check the destination rather than trusting the loop, so a wrong + # --stage-root or a mistyped package name is caught where it happened. + if [[ "$staged_ko" -eq 0 ]]; then + log_error "Staged no modules into $name-modules-$KVER despite dkms producing:" + printf '%s\n' "$kos" | sed 's/^/ | /' >&2 + log_error "The package would ship empty. Check --stage-root ($STAGE_ROOT)." + exit 1 + fi + + # ── Record the DKMS source package in Built-Using ──────────────────────── + # The .ko in this package was compiled from source that lives in another + # source package entirely, and nothing in the Debian metadata would say so: + # the binary is emitted by src:linux-qcom-next, which does not contain a + # line of the module's code. Built-Using names the exact source that did, + # which is what keeps it retained in the archive alongside the binary. + # + # ${source:Package} / ${source:Version} rather than the binary's own name + # and version: dpkg parses these out of the binary's "Source: name (ver)" + # field when it has one, and falls back to the binary version, epoch + # included, when it does not. Both are the right answer. + dkms_src="$(dpkg-query -W -f='${source:Package} (= ${source:Version})' \ + "${name}-dkms" 2>/dev/null)" || { + log_error "Could not read the source package of ${name}-dkms" + log_error "It resolved through dpkg -L a moment ago, so this is unexpected." + exit 1 + } + + # Written into the staging root, which is where dh_gencontrol looks. Safe + # to write here: dh_prep is the only thing that truncates a .substvars and + # it runs before dh_auto_install; everything after this point merges. + # + # Rewritten rather than appended, because this script is also callable by + # hand outside the dh sequence, where no dh_prep has cleared the file and a + # second run would otherwise accumulate duplicate keys. + substvars="$STAGE_ROOT/$name-modules-$KVER.substvars" + if [[ -f "$substvars" ]]; then + grep -v '^dkms:Built-Using=' "$substvars" > "$substvars.new" || true + mv "$substvars.new" "$substvars" + fi + printf 'dkms:Built-Using=%s\n' "$dkms_src" >> "$substvars" + + log_info "Staged $staged_ko module(s) into $name-modules-$KVER" + log_info " Built-Using: $dkms_src" echo done diff --git a/prepare-source.sh b/prepare-source.sh index db701f01..f68e89ef 100755 --- a/prepare-source.sh +++ b/prepare-source.sh @@ -106,12 +106,18 @@ OPTIONS: DKMS modules: --dkms LIST Comma-separated out-of-tree DKMS modules to build - against this kernel and bundle into - linux-image-, each named without the -dkms + against this kernel, each named without the -dkms suffix (e.g. --dkms kgsl,camx). Each entry needs a -dkms package available to the build; the Build-Depends entry is generated from this list. - Empty (the default) bundles no modules. + Each entry produces its own binary packages, + -modules-, its -dbg, and the + unversioned -modules- metapackage; + a non-empty list also produces one + -modules metapackage depending on all of + them. The modules are not installed into + linux-image-. + Empty (the default) builds no modules. Paths: --debian-dir DIR Path to the debian/ packaging directory