diff --git a/README.md b/README.md index ac57987..cf6b04a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ Packages follow the standard Debian/Ubuntu kernel naming convention: | `linux-image-` | Kernel image, modules, DTBs | `linux-image-7.2.0-qcom-next-20260826` | | `linux-headers-` | Headers for out-of-tree modules | `linux-headers-7.2.0-qcom-next-20260826` | | `linux-image--dbg` | Debug symbols | `linux-image-7.2.0-qcom-next-20260826-dbg` | -| `linux-headers--dbgsym` | Headers debug symbols, generated automatically by debhelper | `linux-headers-7.2.0-qcom-next-20260826-dbgsym` | | `` | Image metapackage tracking the newest kernel image | `linux-image-qcom-next` | | `` | Headers metapackage tracking the newest headers | `linux-headers-qcom-next` | @@ -28,9 +27,10 @@ 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 five of these; the `-dbgsym` package is produced by -debhelper rather than declared, so a build publishes six binary packages per -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`. **`-qcom`** is a static flavour suffix appended by the packaging, identifying Qualcomm-packaged kernels independently of the branch name. @@ -217,9 +217,6 @@ clone → prepare → build. Run it from the repo root. # Build with explicit LOCALVERSION ./build-kernel.sh --tag qcom-next-7.2-rc7-20260826 --localversion qcom-next-20260826 -# Build debug variant -./build-kernel.sh --latest-tag --localversion debug --profiles debug - # Use local kernel source (skip clone) ./build-kernel.sh --local-source /path/to/kernel-source --localversion qcom-next-20260826 @@ -442,7 +439,6 @@ this fixed order: | 2 | `debian/rules` | Disable `CONFIG_LOCALVERSION_AUTO` (prevents git hash in `uname -r`) | | 3 | Unified fragment pipeline | `arch/arm64/configs/prune.config`, then `arch/arm64/configs/qcom.config` (both if present), then `debian/config/*.config` (sorted) | | 4 | `debian/rules` | Re-check `CONFIG_LOCALVERSION_AUTO` (merges may re-enable it) | -| 5 | `arch/arm64/configs/debug.config` | Debug options — only when `DEB_BUILD_PROFILES=debug` | ### Unified config fragment pipeline (step 3) @@ -562,18 +558,6 @@ EOF Commit it to `config-available/`. It will not affect any build until explicitly activated via `--enable-configs my-feature` or a manual copy to `config/`. -### Debug build (step 5) - -```bash -# Via build-kernel.sh -./build-kernel.sh --latest-tag --profiles debug - -# Via dpkg-buildpackage directly -DEB_BUILD_PROFILES=debug dpkg-buildpackage -us -uc -b -``` - -Merges `arch/arm64/configs/debug.config` from the kernel source if present. - --- ## Configuration knobs @@ -585,7 +569,6 @@ Merges `arch/arm64/configs/debug.config` from the kernel source if present. | Always-on config | Add `*.config` to `debian/config/` | Applied on every build | | Optional config | `--enable-configs name` or copy from `debian/config-available/` | Activated per build | | Out-of-tree build dir | `O=` or `KBUILD_OUTPUT=` | Reads artifacts from objdir | -| Debug build | `DEB_BUILD_PROFILES=debug` or `--profiles debug` | Merges `arch/arm64/configs/debug.config` | | Parallel jobs | `DEB_BUILD_OPTIONS=parallel=N` | Controls `make -jN` | --- diff --git a/build-kernel.sh b/build-kernel.sh index 8fd946a..f762e08 100755 --- a/build-kernel.sh +++ b/build-kernel.sh @@ -50,7 +50,7 @@ OPTIONS: (default: $DEFAULT_DISTRO) --build-mode MODE docker|native|sbuild (default: $DEFAULT_BUILD_MODE) --docker-build PATH Path to docker_deb_build.py (docker mode) - --profiles PROFILES DEB_BUILD_PROFILES (e.g. debug) + --profiles PROFILES DEB_BUILD_PROFILES (default: none) --kernel-config LIST Extra config fragments, beyond debian/config-available/ which is always applied in full. An "intree:" prefix names a path relative to the kernel source root @@ -76,7 +76,6 @@ EXAMPLES: $0 --local-source /path/to/kernel --build-mode native $0 --local-source /path/to/kernel --kver-extra -mybuild $0 --latest-tag --kernel-config docker,systemd-boot - $0 --latest-tag --profiles debug DISTRIBUTIONS: noble Ubuntu 24.04 LTS diff --git a/debian/rules b/debian/rules index 6b4ab45..ae2768c 100755 --- a/debian/rules +++ b/debian/rules @@ -179,7 +179,6 @@ override_dh_auto_configure: # invocation (later fragments win, overrides are reported), followed by # make olddefconfig to resolve the resulting dependencies. # 4. Re-check CONFIG_LOCALVERSION_AUTO (merges may re-enable it) - # 5. arch/arm64/configs/debug.config — only when DEB_BUILD_PROFILES=debug # --------------------------------------------------------------------------- # Step 1: Base defconfig @@ -243,16 +242,6 @@ override_dh_auto_configure: echo "# CONFIG_LOCALVERSION_AUTO is not set" >> "$$CONFIG_FILE"; \ fi - # Step 5: Merge debug.config if debug build profile is active. - @if echo "$(DEB_BUILD_PROFILES)" | grep -qw "debug"; then \ - if [ -f arch/arm64/configs/debug.config ]; then \ - echo "Debug profile active: merging arch/arm64/configs/debug.config..."; \ - $(MAKE) $(KBUILD_O_ARG) ARCH=$(ARCH) debug.config; \ - else \ - echo "Warning: debug profile requested but arch/arm64/configs/debug.config not found"; \ - fi; \ - fi - override_dh_auto_build: # Read LOCALVERSION from debian/localversion (written by 'prepare' target). # This avoids the fragile back-derivation from the source package name that @@ -386,7 +375,7 @@ override_dh_auto_install: src_mod="$$mod"; \ [ -n "$$OBJ_DIR" ] && src_mod="$$OBJ_DIR/$$mod"; \ [ -f "$$src_mod" ] || continue; \ - dbg="$$DBG_PKG/usr/lib/debug/lib/modules/$$BASE/$$mod"; \ + dbg="$$DBG_PKG/usr/lib/debug/lib/modules/$$BASE/kernel/$$mod"; \ mkdir -p "$${dbg%/*}"; \ $(OBJCOPY) --only-keep-debug "$$src_mod" "$$dbg" 2>/dev/null || \ cp -a "$$src_mod" "$$dbg"; \ @@ -423,6 +412,15 @@ override_dh_auto_install: --objcopy "$(OBJCOPY)" \ --modules-manifest "$(CURDIR)/debian/dkms-modules" +# Ignore files shipped in debug package. +override_dh_dwz: + dh_dwz -X/usr/lib/debug/ + +# Ignore files shipped in debug package. +# Don't build "-dbgsym" packages. +override_dh_strip: + dh_strip -X/usr/lib/debug/ --no-automatic-dbgsym + # Skip automatic dh_installmodules — called manually in override_dh_installdeb. override_dh_installmodules: @echo "Skipping automatic dh_installmodules (called manually in override_dh_installdeb)" diff --git a/debian/scripts/bundle-dkms-modules.sh b/debian/scripts/bundle-dkms-modules.sh index c2649f0..5773d0b 100755 --- a/debian/scripts/bundle-dkms-modules.sh +++ b/debian/scripts/bundle-dkms-modules.sh @@ -16,10 +16,12 @@ set -euo pipefail # 2. Reads PACKAGE_NAME / PACKAGE_VERSION from the package's dkms.conf. # 3. Builds the module with `dkms build` against the staged kernel headers, # using a private --dkmstree (mktemp) to avoid writing to /var/lib/dkms/. -# 4. Judges the outcome by artifact presence, not dkms exit code. -# On failure: prints make.log tail (build failure) or BUILD_EXCLUSIVE gate -# analysis (skip), then hard-fails — a manifest entry is a presence contract. -# 5. For each produced .ko: +# 4. Prints the dkms make.log for every module, built or not: the private +# dkms tree is deleted on exit, so this is the only record left in CI. +# 5. Judges the outcome by artifact presence, not dkms exit code. +# 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 @@ -279,6 +281,38 @@ log_info " objcopy: $OBJCOPY" log_info " modules: $DKMS_MODULES" echo +# --------------------------------------------------------------------------- +# Report any dkms configuration present on the build host. +# +# dkms hardcodes the paths it reads configuration from: /etc/dkms/framework.conf +# and /etc/dkms/framework.conf.d/*.conf in read_framework_conf, and the +# /etc/dkms/*.conf overrides in read_conf. None of them can be +# redirected by an option or an environment variable, so a build cannot opt out +# of whatever the host happens to ship; it can only be explicit about it. +# +# Most of the exposure is already closed: framework.conf accepts a fixed +# variable list, it is sourced before the command line is parsed so --dkmstree +# and --kernelsourcedir are higher priority and the --directive passed to dkms +# build is applied after every conf file. What remains is tmp_location, +# parallel_jobs, the compress_*_opts, and post_transaction, the last being an +# arbitrary command dkms will run. +# +# Print whatever is there so a surprising host setting shows up in the build +# log rather than acting silently. This is deliberately not fatal: a build host +# is allowed to have dkms configured. +# --------------------------------------------------------------------------- +_dkms_host_conf=0 +for _conf in /etc/dkms/framework.conf /etc/dkms/framework.conf.d/*.conf; do + [[ -e "$_conf" ]] || continue + _dkms_host_conf=1 + log_warn "Host dkms configuration in effect: $_conf" + grep -vE '^[[:space:]]*(#|$)' "$_conf" | sed 's/^/ | /' || true +done +if [[ "$_dkms_host_conf" -eq 0 ]]; then + log_info "No host dkms framework configuration found." +fi +echo + # --------------------------------------------------------------------------- # Private DKMS tree — redirects artifacts away from /var/lib/dkms/ (root-owned, # not writable under fakeroot / non-root dpkg-buildpackage). @@ -346,33 +380,60 @@ for name in $DKMS_MODULES; do log_info " kernelsourcedir: $HEADERS_DIR" log_info " dkmstree: $DKMS_TREE" + # ── Report per-module dkms.conf overrides on the build host ────────────── + # read_conf sources these after the vendor dkms.conf, so they can change how + # this module is built. The --directive below still wins over them, but + # anything they set that we do not pin takes effect silently otherwise. + for _conf in "/etc/dkms/$PKG_NAME.conf" \ + "/etc/dkms/$PKG_NAME-$PKG_VER.conf" \ + "/etc/dkms/$PKG_NAME-$PKG_VER-$KVER.conf" \ + "/etc/dkms/$PKG_NAME--$KVER.conf"; do + [[ -e "$_conf" ]] || continue + log_warn "Host override for $PKG_NAME in effect: $_conf" + grep -vE '^[[:space:]]*(#|$)' "$_conf" | sed 's/^/ | /' || true + done + # ── Run dkms build ──────────────────────────────────────────────────────── # Capture exit code separately: dkms exit-code conventions vary across # versions (a BUILD_EXCLUSIVE skip can exit 0). Outcome is judged by # artifact presence, not exit code. + # + # --directive STRIP=no stops dkms from stripping the module. dkms_rc=0 dkms build "$PKG_NAME/$PKG_VER" \ --kernelsourcedir "$HEADERS_DIR" \ --dkmstree "$DKMS_TREE" \ -k "$KVER" \ --arch "$DKMS_ARCH" \ + --directive "STRIP=no" \ || dkms_rc=$? + # ── Report the build log ────────────────────────────────────────────────── + # dkms writes make.log inside the private dkms tree, which is deleted on + # EXIT, so this is the only surviving record of the build in CI. + # + # Print it whether or not the build succeeded. A module that builds can + # still be compiled with the wrong flags, and the compiler command lines + # are the only place that is visible — debian/rules exports KBUILD_VERBOSE, + # so they are all here. + mklog="$(find "$DKMS_TREE/$PKG_NAME/$PKG_VER" -name make.log 2>/dev/null \ + | head -1 || true)" + if [[ -n "$mklog" ]]; then + log_step "dkms build log for $PKG_NAME/$PKG_VER ($mklog):" + sed 's/^/ | /' "$mklog" + echo + fi + # ── Judge outcome by artifacts ──────────────────────────────────────────── # A .ko under //// means success. # dkms's make.log separates the two failure modes: # - make.log present → build was attempted and failed # - no make.log → dkms attempted no build (BUILD_EXCLUSIVE gate) - # The log is printed inline because the private dkms tree is deleted on - # EXIT, so it is the only surviving record in CI. kos="$(find "$DKMS_TREE/$PKG_NAME/$PKG_VER/$KVER" -name '*.ko' 2>/dev/null || true)" if [[ "$dkms_rc" -ne 0 || -z "$kos" ]]; then - mklog="$(find "$DKMS_TREE/$PKG_NAME/$PKG_VER" -name make.log 2>/dev/null \ - | head -1 || true)" if [[ -n "$mklog" ]]; then - log_error "dkms build failed for $PKG_NAME/$PKG_VER on kernel $KVER (dkms exit $dkms_rc); make.log tail:" - tail -n 300 "$mklog" | sed 's/^/ | /' >&2 + log_error "dkms build failed for $PKG_NAME/$PKG_VER on kernel $KVER (dkms exit $dkms_rc); see the build log above." else log_error "$PKG_NAME/$PKG_VER produced no module for kernel $KVER; dkms attempted no build (dkms exit $dkms_rc)." gates="$(grep -E '^[[:space:]]*BUILD_EXCLUSIVE' "$conf" 2>/dev/null || true)" @@ -444,9 +505,42 @@ for name in $DKMS_MODULES; do install -D -m 644 "$ko" "$dest" # Stage 2: extract debug symbols before stripping (non-destructive read) + # Falling back to a full copy keeps the -dbg package usable when objcopy + # cannot extract, but that is a degradation, not a normal outcome: + # report what objcopy said instead of discarding it. mkdir -p "${dbg%/*}" - "$OBJCOPY" --only-keep-debug "$dest" "$dbg" 2>/dev/null \ - || cp -a "$dest" "$dbg" + if ! objcopy_err="$("$OBJCOPY" --only-keep-debug "$dest" "$dbg" 2>&1)"; then + log_warn "objcopy --only-keep-debug failed for $b; copying the module instead" + printf '%s\n' "$objcopy_err" | sed 's/^/ | /' >&2 + cp -a "$dest" "$dbg" + fi + + # Stage 2b: the extraction above succeeds even when there is nothing to + # extract, so assert the result actually carries DWARF. + # + # Capture readelf's output rather than piping it into `grep -q`: grep -q + # exits at the first match, readelf dies of SIGPIPE on its next write, + # and under `set -o pipefail` the pipeline reports that failure — firing + # this assertion on a file that does carry DWARF. Whether readelf gets + # far enough to be killed depends on stdio flush timing, so the pipeline + # form fails only sometimes, which is worse than failing always. + if ! dbg_sections="$(readelf -SW "$dbg" 2>&1)"; then + log_error "readelf failed on the extracted debug file for $b" + log_error " debug: $dbg" + printf '%s\n' "$dbg_sections" | sed 's/^/ | /' >&2 + exit 1 + fi + if ! grep -q '\.debug_info' <<< "$dbg_sections"; then + log_error "No DWARF in the extracted debug file for $b" + log_error " module: $ko" + log_error " debug: $dbg" + log_error "The -dbg package would ship a debug file with no symbols." + log_error "Check that the module was compiled with debug information" + log_error "and that nothing stripped it before this script ran." + log_error "Sections in the extracted debug file:" + printf '%s\n' "$dbg_sections" | sed 's/^/ | /' >&2 + exit 1 + fi # Stage 3: strip the shipped copy in place strip --strip-debug "$dest" diff --git a/prepare-source.sh b/prepare-source.sh index 886e6d8..6d38e27 100755 --- a/prepare-source.sh +++ b/prepare-source.sh @@ -76,11 +76,6 @@ OPTIONS: redundant, since that fragment is already applied. Entries are processed in LC_ALL=C sorted order. - Debug: - --debug Enable debug build: copies arch/arm64/configs/debug.config - from the kernel source into debian/config/ so it is - applied as a config fragment during the build. - Paths: --debian-dir DIR Path to the debian/ packaging directory (default: $DEBIAN_DIR) @@ -113,7 +108,6 @@ SRCPKG="$DEFAULT_SRCPKG" BINPKG="$DEFAULT_BINPKG" DEBIAN_REVISION="$DEFAULT_DEBIAN_REVISION" KERNEL_CONFIG="" -DEBUG=false while [[ $# -gt 0 ]]; do case $1 in @@ -126,7 +120,6 @@ while [[ $# -gt 0 ]]; do --debian-revision) DEBIAN_REVISION="$2"; shift 2 ;; --kernel-config) KERNEL_CONFIG="$2"; shift 2 ;; --debian-dir) DEBIAN_DIR="$2"; shift 2 ;; - --debug) DEBUG=true; shift ;; -h|--help) usage ;; *) log_error "Unknown option: $1"; exit 1 ;; esac @@ -178,7 +171,6 @@ log_info " Debian revision: $DEBIAN_REVISION" [[ -n "$LOCALVERSION" ]] && log_info " LOCALVERSION: $LOCALVERSION" [[ -n "$KVER_EXTRA" ]] && log_info " KVER_EXTRA: $KVER_EXTRA" [[ -n "$KERNEL_CONFIG" ]] && log_info " Kernel config: $KERNEL_CONFIG" -[[ "$DEBUG" == true ]] && log_info " Debug build: yes" echo # ── Inject debian/ ─────────────────────────────────────────────────────────── @@ -285,18 +277,6 @@ if [[ -n "$KERNEL_CONFIG" ]]; then done < <(printf '%s\n' "${CFG_LIST[@]}" | tr -d ' ' | LC_ALL=C sort) fi -# ── Debug config fragment ───────────────────────────────────────────────────── -if [[ "$DEBUG" == true ]]; then - DEBUG_CONFIG="$SOURCE_DIR/kernel/configs/debug.config" - if [[ -f "$DEBUG_CONFIG" ]]; then - mkdir -p "$SOURCE_DIR/debian/config" - cp "$DEBUG_CONFIG" "$SOURCE_DIR/debian/config/debug.config" - log_info "Copied kernel/configs/debug.config into debian/config/" - else - log_warn "kernel/configs/debug.config not found — debug config will not be applied" - fi -fi - # ── Prepare: generate control, changelog, localversion, pkgversion ─────────── log_step "Running debian/rules prepare..." PREPARE_ARGS="DISTRO=$DISTRO SRCPKG=$SRCPKG BINPKG=$BINPKG DEBIAN_REVISION=$DEBIAN_REVISION"