From 183d17a5c84dd4fe445d5fb702ee90ce36cc1c4b Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Tue, 14 Jul 2026 01:51:57 +0800 Subject: [PATCH 01/10] build-kernel-deb: support cross-compilation, install build-dep prerequisites before clean - Detect a build/host arch mismatch and enable cross builds via dpkg foreign-architecture, the "cross" build profile, and DEB_HOST_ARCH/GNU_TYPE/MULTIARCH propagation. - Factor debian/control generation and native-host-tool-dep patching into scripts/lib/gen-real-control.sh, shared with build-docker-image.sh. - Install fakeroot/debhelper before running `debian/rules clean`, which requires both to run. - Mark native-host build tools (e.g. llvm-*-dev) `:native` via a pattern-keyed NATIVE_HOST_TOOL_DEPS table, and restore debian/control from a backup on exit. - Support OUTPUT_DIR override, and exit with an explicit error message when apt-get build-dep or dch need root but no sudo binary is available. - Add binary-indep to the build targets so the arch:all linux-qcom-headers-* package is produced alongside the flavour packages. Signed-off-by: Guanquan Tian --- scripts/build-kernel-deb.sh | 225 +++++++++++++++++++++++++++----- scripts/lib/gen-real-control.sh | 124 ++++++++++++++++++ 2 files changed, 317 insertions(+), 32 deletions(-) create mode 100644 scripts/lib/gen-real-control.sh diff --git a/scripts/build-kernel-deb.sh b/scripts/build-kernel-deb.sh index a63dde74ba9d8..063938b9be2c6 100755 --- a/scripts/build-kernel-deb.sh +++ b/scripts/build-kernel-deb.sh @@ -6,32 +6,52 @@ # source tree (as checked out from a series branch) # # Usage: -# build-kernel-deb.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] +# build-kernel-deb.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] [VERSION_SUFFIX] # # Arguments: -# SOURCE_DIR Root of the kernel source tree containing debian/ (default: .) -# ARCH Target Debian architecture: arm64 | amd64 (default: arm64) -# FLAVOR Kernel flavour: generic | lowlatency | all (default: generic) -# JOBS Parallel make jobs (default: nproc) +# SOURCE_DIR Root of the kernel source tree containing debian/ (default: .) +# ARCH Target Debian architecture to compile for: arm64 (default: arm64). +# Build host may be arm64 (native) or amd64 (cross-compile via +# gcc-aarch64-linux-gnu); amd64 is not a supported target since +# the qcom/qcom-rt flavours are arm64-only. +# FLAVOR Kernel flavour: qcom | qcom-rt | all (default: qcom) +# JOBS Parallel make jobs (default: nproc) +# VERSION_SUFFIX Optional string appended to the package/kernel version, +# e.g. "+g1a2b3c4" or "+myuser1" (default: none). Pass +# "auto" to generate "+g" from SOURCE_DIR's +# current HEAD (requires SOURCE_DIR to be a git work tree). +# Modifies debian.qcom/changelog, but the script restores it +# to HEAD before every run, so this never leaves the tree +# dirty. # # Output: # Built .deb packages are placed in ./output/ relative to the working -# directory from which this script is invoked. +# directory from which this script is invoked, unless OUTPUT_DIR is set +# in the environment (relative or absolute; normalized to an absolute path +# up front), in which case that path is used instead. # # Notes: -# • Designed for native arm64 builds (Ubuntu 24.04 arm64 host). -# • The Ubuntu kernel build needs ~20 GB of free disk space. -# • A full build (all flavours) can take 2+ hours; 'generic' is ~1 hour. -# • Run as a normal user; sudo is used only for apt-get. +# • Supports native (arm64 host) and cross (amd64 host, e.g. via dpkg +# cross-architecture + gcc-aarch64-linux-gnu) builds. +# • Needs ~20 GB free disk space. +# • A full build (all flavours) takes 2+ hours; a single flavour ~1 hour. +# • Designed to run inside a Docker container with preinstalled dependencies. +# • For Docker builds, use the docker-build-kernel.sh wrapper. set -euo pipefail SOURCE_DIR="${1:-.}" ARCH="${2:-arm64}" -FLAVOR="${3:-generic}" +FLAVOR="${3:-qcom}" JOBS="${4:-$(nproc)}" +VERSION_SUFFIX="${5:-}" +SKIP_BUILD_DEP="${SKIP_BUILD_DEP:-0}" -OUTPUT_DIR="$(pwd)/output" +OUTPUT_DIR="${OUTPUT_DIR:-$(pwd)/output}" +# Normalize to an absolute path now, before any `cd` below changes pwd out +# from under a relative OUTPUT_DIR. +mkdir -p "${OUTPUT_DIR}" +OUTPUT_DIR="$(cd "${OUTPUT_DIR}" && pwd)" # --------------------------------------------------------------------------- # Helpers @@ -40,13 +60,42 @@ log() { printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >&2; } die() { log "ERROR: $*"; exit 1; } hr() { log "$(printf '%0.s─' {1..60})"; } +is_truthy() { + case "${1:-}" in + 1|[tT][rR][uU][eE]|[yY][eE][sS]|[oO][nN]) return 0 ;; + *) return 1 ;; + esac +} + +# is_git_worktree, add_cleanup/run_cleanup (+ trap), run_debian_rules_clean, +# reset_and_track_changelog, patch_native_host_tool_deps: shared with +# build-docker-image.sh, defined in gen-real-control.sh. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/gen-real-control.sh +source "${SCRIPT_DIR}/lib/gen-real-control.sh" + +if [ "${VERSION_SUFFIX}" = "auto" ]; then + is_git_worktree "${SOURCE_DIR}" \ + || die "VERSION_SUFFIX=auto requires '${SOURCE_DIR}' to be a git work tree" + AUTO_COMMIT="$(git -C "${SOURCE_DIR}" rev-parse --short HEAD)" + VERSION_SUFFIX="+g${AUTO_COMMIT}" +fi + +BUILD_ARCH="$(dpkg --print-architecture)" +CROSS_BUILD=false +if [ "${ARCH}" != "${BUILD_ARCH}" ]; then + CROSS_BUILD=true +fi + hr log "Ubuntu kernel .deb build" log " Source dir : ${SOURCE_DIR}" -log " Arch : ${ARCH}" +log " Build arch : ${BUILD_ARCH}" +log " Host arch : ${ARCH}$([ "${CROSS_BUILD}" = true ] && echo ' (cross-compiling)')" log " Flavour : ${FLAVOR}" log " Jobs : ${JOBS}" log " Output dir : ${OUTPUT_DIR}" +log " Version : ${VERSION_SUFFIX:-(none)}" hr # --------------------------------------------------------------------------- @@ -56,51 +105,163 @@ hr || die "No debian/rules found in '${SOURCE_DIR}' – is this a kernel source tree?" # --------------------------------------------------------------------------- -# 2. Install build dependencies +# 2. Prepare build environment # --------------------------------------------------------------------------- hr -log "Installing build dependencies (requires sudo)..." -sudo apt-get update -qq -sudo apt-get build-dep -y "${SOURCE_DIR}" \ - || die "apt-get build-dep failed" +SUDO="" +if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" +fi + +if [ "${CROSS_BUILD}" = true ]; then + if dpkg --print-foreign-architectures | grep -qxF "${ARCH}"; then + log "dpkg foreign architecture ${ARCH} already enabled (baked into the build image) — skipping." + else + log "Enabling dpkg foreign architecture ${ARCH} for cross-compilation..." + ${SUDO} dpkg --add-architecture "${ARCH}" + fi +fi + +# For cross-compilation, set DEB_HOST_ARCH and related variables. +CROSS_ENV="" +if [ "${CROSS_BUILD}" = true ]; then + DEB_HOST_GNU_TYPE="$(dpkg-architecture -a"${ARCH}" -qDEB_HOST_GNU_TYPE)" + DEB_HOST_MULTIARCH="$(dpkg-architecture -a"${ARCH}" -qDEB_HOST_MULTIARCH)" + CROSS_ENV="DEB_HOST_ARCH=${ARCH} DEB_HOST_GNU_TYPE=${DEB_HOST_GNU_TYPE} DEB_HOST_MULTIARCH=${DEB_HOST_MULTIARCH}" +fi + +# `clean` regenerates debian/control and copies debian./changelog to debian/changelog. +DEBIAN_DIR="$(get_debian_dir "${SOURCE_DIR}")" + +reset_and_track_changelog "${SOURCE_DIR}" "${DEBIAN_DIR}" + +if [ -n "${VERSION_SUFFIX}" ]; then + export DEBEMAIL="${DEBEMAIL:-build-kernel-deb@localhost}" + export DEBFULLNAME="${DEBFULLNAME:-build-kernel-deb.sh}" + + # Ensure dch is available + if ! command -v dch >/dev/null 2>&1; then + if [ -n "${SUDO}" ] && ! command -v sudo >/dev/null 2>&1; then + die "dch is missing and this isn't running as root with no sudo binary available to install devscripts. Install devscripts into the image ahead of time, or run as root." + fi + log "dch not found, installing devscripts..." + ${SUDO} apt-get update -qq + ${SUDO} apt-get install -y devscripts || die "Failed to install devscripts" + fi + + BASE_VERSION="$(dpkg-parsechangelog -l"${SOURCE_DIR}/${DEBIAN_DIR}/changelog" -S Version)" \ + || die "Failed to read current version from ${SOURCE_DIR}/${DEBIAN_DIR}/changelog" + rm -f "${SOURCE_DIR}/${DEBIAN_DIR}/changelog.dch" + log "Tagging local version suffix '${VERSION_SUFFIX}' onto ${DEBIAN_DIR}/changelog..." + ( cd "${SOURCE_DIR}" && dch --changelog "${DEBIAN_DIR}/changelog" \ + --newversion "${BASE_VERSION}${VERSION_SUFFIX}" "Local build" ) \ + || die "Failed to apply version suffix via dch" + + if is_git_worktree "${SOURCE_DIR}"; then + add_cleanup "git -C \"${SOURCE_DIR}\" checkout -- \"${DEBIAN_DIR}/changelog\" 2>/dev/null || true" + fi +fi + +run_debian_rules_clean "${SOURCE_DIR}" "${CROSS_ENV}" + +# --------------------------------------------------------------------------- +# 3. Install build dependencies (if not skipped) +# --------------------------------------------------------------------------- +hr + +log "Installing build dependencies${SUDO:+ (requires sudo)}..." +if is_truthy "${SKIP_BUILD_DEP}"; then + log " SKIP_BUILD_DEP set — skipping apt-get build-dep." +else + # Not root and no sudo: apt-get build-dep can't escalate. Fail fast with a + # clear error instead of a confusing "sudo: command not found" from deep + # inside apt. Fix: SKIP_BUILD_DEP=1 with deps preinstalled (e.g. baked into + # the image via build-docker-image.sh), or run as root. + if [ -n "${SUDO}" ] && ! command -v sudo >/dev/null 2>&1; then + die "apt-get build-dep needs root, but this isn't running as root and no sudo binary is available. Set SKIP_BUILD_DEP=1 (and make sure build-deps are already installed — e.g. baked into the image via build-docker-image.sh), or run as root." + fi + + # Enable deb-src for build-dep to work + log "Enabling deb-src sources..." + ${SUDO} sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true + + if [ "${CROSS_BUILD}" = true ]; then + patch_native_host_tool_deps "${SOURCE_DIR}/debian/control" + fi + + ${SUDO} apt-get update -qq + BUILD_DEP_PROFILE_OPT="" + if [ "${CROSS_BUILD}" = true ]; then + # For cross-compilation, use the "cross" build profile. + BUILD_DEP_PROFILE_OPT="--build-profiles cross" + fi + ( cd "${SOURCE_DIR}" && ${SUDO} apt-get build-dep -y --host-architecture "${ARCH}" ${BUILD_DEP_PROFILE_OPT} . ) \ + || die "apt-get build-dep failed" + + if [ "${CROSS_BUILD}" = true ]; then + # For cross-compilation, also install native copies of build dependencies. + log "Installing native (build-arch) copies of build dependencies for host-tool helpers..." + ( cd "${SOURCE_DIR}" && ${SUDO} apt-get build-dep -y . ) \ + || die "apt-get build-dep (native pass) failed" + fi +fi # --------------------------------------------------------------------------- -# 3. Build +# 4. Build # --------------------------------------------------------------------------- hr log "Starting kernel build (flavour=${FLAVOR}, arch=${ARCH}, jobs=${JOBS})..." -# Determine the debian/rules target +# Determine the debian/rules target(s). binary-${FLAVOR} alone builds only +# the arch-specific flavour packages; the common linux-qcom-headers-* +# package (arch: all, hard-depended on by linux-headers-*-${FLAVOR}) comes +# from binary-indep only, so it must be appended explicitly. `all` maps to +# "binary", which already runs binary-arch + binary-indep for every flavour. if [ "${FLAVOR}" = "all" ]; then RULES_TARGET="binary" else - RULES_TARGET="binary-${FLAVOR}" + RULES_TARGET="binary-${FLAVOR} binary-indep" fi -# The Ubuntu kernel build system reads DEB_BUILD_OPTIONS for parallelism export DEB_BUILD_OPTIONS="parallel=${JOBS} nocheck" -# Run the build (native arm64 – no cross-compilation flags needed) +# do_skip_checks=true bypasses the config-policy check (avoids failures when +# optional toolchains like Rust/bindgen are absent). RULES_TARGET is +# unquoted so make receives multiple targets as separate arguments. ( cd "${SOURCE_DIR}" - fakeroot debian/rules "${RULES_TARGET}" \ + env ${CROSS_ENV} fakeroot debian/rules ${RULES_TARGET} do_skip_checks=true \ || die "debian/rules ${RULES_TARGET} failed" ) # --------------------------------------------------------------------------- -# 4. Collect output packages +# 5. Collect output packages # --------------------------------------------------------------------------- hr +# Clear only the artifact types we produce, not the whole dir. mkdir -p "${OUTPUT_DIR}" +rm -f "${OUTPUT_DIR}"/*.deb "${OUTPUT_DIR}"/*.changes "${OUTPUT_DIR}"/*.buildinfo -# The Ubuntu kernel build drops .deb files one level above the source tree +# Collect .deb files from the parent directory matching this build's version. PARENT_DIR=$(dirname "$(realpath "${SOURCE_DIR}")") -find "${PARENT_DIR}" -maxdepth 1 \ - \( -name "*.deb" -o -name "*.changes" -o -name "*.buildinfo" \) \ - | while read -r f; do - cp "${f}" "${OUTPUT_DIR}/" - log " Collected: $(basename "${f}")" - done +DEB_VERSION="$(dpkg-parsechangelog -l "${SOURCE_DIR}/debian/changelog" -S Version)" \ + || die "Failed to read package version from debian/changelog" +# .deb filenames drop the epoch ("1:7.0.0" -> "7.0.0..."); no-op otherwise. +DEB_VERSION="${DEB_VERSION##*:}" +[ -n "${DEB_VERSION}" ] \ + || die "Empty package version parsed from ${SOURCE_DIR}/debian/changelog" + +# Read into the parent shell so the counter survives to the empty-collection check. +collected=0 +while IFS= read -r -d '' f; do + mv "${f}" "${OUTPUT_DIR}/" || die "Failed to move $(basename "${f}") to ${OUTPUT_DIR}" + log " Collected: $(basename "${f}")" + collected=$((collected + 1)) +done < <(find "${PARENT_DIR}" -maxdepth 1 -name "*_${DEB_VERSION}_*" \ + \( -name "*.deb" -o -name "*.changes" -o -name "*.buildinfo" \) -print0) + +[ "${collected}" -gt 0 ] \ + || die "Build finished but no artifacts matching version '${DEB_VERSION}' were found in ${PARENT_DIR}" hr log "Build complete." diff --git a/scripts/lib/gen-real-control.sh b/scripts/lib/gen-real-control.sh new file mode 100644 index 0000000000000..0866c0b5dc6a3 --- /dev/null +++ b/scripts/lib/gen-real-control.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# +# gen-real-control.sh - Shared helpers for producing a kernel source tree's +# real, version-accurate debian/control (as generated +# by `debian/rules clean`) and for patching it for +# cross-builds. +# +# Sourced by build-kernel-deb.sh and build-docker-image.sh. The sourcing +# script must define log() and die() before sourcing, and must set a SUDO +# variable (empty string when already running as root) before calling +# run_debian_rules_clean() — checked there, since both callers set SUDO +# after sourcing this file. + +declare -f log >/dev/null 2>&1 || { echo "gen-real-control.sh: log() must be defined before sourcing this file" >&2; exit 1; } +declare -f die >/dev/null 2>&1 || { echo "gen-real-control.sh: die() must be defined before sourcing this file" >&2; exit 1; } + +# --------------------------------------------------------------------------- +# Cleanup-action stack: several steps below need to undo a temporary edit on +# exit (success or failure). A single `trap ... EXIT` would be silently +# clobbered by the next one that registers it, so instead each step pushes a +# command string here and one trap runs them all, in reverse (LIFO) order. +# --------------------------------------------------------------------------- +CLEANUP_CMDS=() +add_cleanup() { CLEANUP_CMDS+=("$1"); } +run_cleanup() { + local i + for (( i=${#CLEANUP_CMDS[@]}-1; i>=0; i-- )); do + eval "${CLEANUP_CMDS[$i]}" + done +} +trap run_cleanup EXIT + +is_git_worktree() { + local dir="$1" out real + out="$(git -C "${dir}" rev-parse --is-inside-work-tree 2>&1)" && return 0 + if printf '%s' "${out}" | grep -q 'detected dubious ownership'; then + real="$(realpath "${dir}" 2>/dev/null)" || return 1 + git config --global --get-all safe.directory 2>/dev/null | grep -qxF "${real}" \ + || git config --global --add safe.directory "${real}" 2>/dev/null || true + git -C "${dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1 && return 0 + fi + return 1 +} + +get_debian_dir() { + awk -F= '($1 == "DEBIAN") { print $2 }' "$1/debian/debian.env" +} + +# Resets any leftover modified debian//changelog from a previous +# interrupted run, and registers a cleanup to restore it again on exit. +# No-op when SOURCE_DIR is not a git work tree (there's nothing to check out +# back to). +reset_and_track_changelog() { + local source_dir="$1" debian_dir="$2" + if is_git_worktree "${source_dir}"; then + git -C "${source_dir}" checkout -- "${debian_dir}/changelog" 2>/dev/null || true + add_cleanup "git -C \"${source_dir}\" checkout -- \"${debian_dir}/changelog\" 2>/dev/null || true" + fi +} + +# Ensures fakeroot/debhelper are present, then runs `debian/rules clean` in +# SOURCE_DIR to (re)generate debian/control and debian/changelog. Resulting +# control file is always at SOURCE_DIR/debian/control. +run_debian_rules_clean() { + local source_dir="$1" cross_env="${2:-}" tool_pkg pkg bin + + [ -v SUDO ] || die "run_debian_rules_clean: SUDO must be set (possibly empty) by the caller before this is called" + + # `debian/rules clean` needs fakeroot and debhelper (dh_testdir/dh_clean) + # to run at all — independent of SKIP_BUILD_DEP, since clean must succeed + # before debian/control exists for apt-get build-dep to read. Only + # installs what's missing; no-op on images that already have them. + for tool_pkg in fakeroot:fakeroot debhelper:dh_testdir; do + pkg="${tool_pkg%%:*}"; bin="${tool_pkg##*:}" + if ! command -v "${bin}" >/dev/null 2>&1; then + if [ -n "${SUDO}" ] && ! command -v sudo >/dev/null 2>&1; then + die "${pkg} is missing and this isn't running as root with no sudo binary available to install it. Install ${pkg} into the image ahead of time, or run as root." + fi + log "Installing ${pkg} (required to run debian/rules clean)..." + ${SUDO} apt-get update -qq + ${SUDO} apt-get install -y "${pkg}" || die "Failed to install ${pkg}" + fi + done + + log "Running debian/rules clean in ${source_dir} (generates debian/control and debian/changelog)..." + ( cd "${source_dir}" && env ${cross_env} fakeroot debian/rules clean ) \ + || die "Failed to run debian/rules clean in ${source_dir}" + + [ -f "${source_dir}/debian/control" ] \ + || die "debian/rules clean did not produce ${source_dir}/debian/control" +} + +# Packages debian/control lists without a `:native` qualifier but that the +# kernel's own build rules invoke directly as build-host tools (e.g. +# llvm-config- in debian/rules.d/2-binary-arch.mk) rather than linking +# into the target-arch binaries. Under cross-compilation, `apt-get +# build-dep --host-architecture` installs only the target-arch copy, which +# isn't executable on the build host — these need an explicit native +# (build-arch) install alongside. Keyed by the same package-name/version +# pattern the consuming rules file greps from debian/control. +declare -A NATIVE_HOST_TOOL_DEPS=( + [llvm]='llvm-[0-9]+-dev' +) + +# For each pattern in NATIVE_HOST_TOOL_DEPS matching an *unannotated* +# Build-Depends line in CONTROL_PATH, mark it `:native` so a cross `apt-get +# build-dep --host-architecture` installs the build-host copy instead of a +# non-executable target-arch one. A backup is kept and restored on exit. +patch_native_host_tool_deps() { + local control="$1" key pattern pkg backup + for key in "${!NATIVE_HOST_TOOL_DEPS[@]}"; do + pattern="${NATIVE_HOST_TOOL_DEPS[$key]}" + pkg="$(grep -oE "${pattern}" "${control}" | head -1)" + [ -n "${pkg}" ] || continue # not in this control file at all + grep -q "^ ${pkg},\$" "${control}" || continue # already annotated/restricted — leave as-is + + log "debian/control: ${pkg} is unannotated but is consumed as a build-host tool by the kernel's own rules.d — marking it :native for this cross build..." + backup="${control}.orig-precross" + cp "${control}" "${backup}" + add_cleanup "mv \"${backup}\" \"${control}\" 2>/dev/null || true" + sed -i "s/^ ${pkg},\$/ ${pkg}:native,/" "${control}" + done +} From 7ced7582248f822ce3aa50f5c04de3a58778b193 Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Tue, 14 Jul 2026 01:52:43 +0800 Subject: [PATCH 02/10] scripts: build the kernel build image locally from a checked-in Dockerfile - Add scripts/Dockerfile.kernel-build, built FROM docker.io/library/ubuntu:resolute (falling back to public.ecr.aws/ubuntu/ubuntu:resolute), with build deps resolved dynamically from the kernel tree's own debian/control via `debian/rules clean`. - Add scripts/build-docker-image.sh to drive it: runs `debian/rules clean` on the host, copies the generated debian/control into a scratch build context, and passes TARGET_ARCH/CROSS build args. - The image is built from a Dockerfile checked into this repo. Signed-off-by: Guanquan Tian --- scripts/Dockerfile.kernel-build | 75 ++++++++++++++++++ scripts/build-docker-image.sh | 135 ++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 scripts/Dockerfile.kernel-build create mode 100755 scripts/build-docker-image.sh diff --git a/scripts/Dockerfile.kernel-build b/scripts/Dockerfile.kernel-build new file mode 100644 index 0000000000000..395350d37bea7 --- /dev/null +++ b/scripts/Dockerfile.kernel-build @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Dockerfile.kernel-build - Kernel build environment with dependencies +# resolved dynamically from a real kernel source +# tree's debian/control, instead of a +# hand-maintained control. snapshot. +# +# Based on docker.io/library/ubuntu:resolute, with public.ecr.aws/ubuntu/ubuntu:resolute +# as a fallback for when that tag isn't published under the primary registry. +# Both are genuine multi-arch manifest lists (amd64, arm64, ppc64le, armv7, +# riscv64, s390x), unlike ghcr.io/qualcomm-linux/pkg-builder:resolute, which +# is arm64-only and falls back to full-system QEMU emulation on an amd64 +# host. A multi-arch manifest lets docker build/run pick the native manifest +# for the host, so only the cross-compiler invocations run under +# emulation-free cross toolchains. build-docker-image.sh probes which base +# image is available and passes it via the BASE_IMAGE build arg below. +# +# Not built directly — use build-docker-image.sh, which first runs +# `debian/rules clean` against a real kernel source tree to produce the +# `control` and `extra-tools.txt` files this Dockerfile's build context +# expects, then invokes `docker build` with this file. +# +# Build args: +# BASE_IMAGE Base image reference to build FROM (must be a genuine +# multi-arch manifest list); resolved by build-docker-image.sh +# TARGET_ARCH Target Debian architecture (e.g. arm64, amd64) +# CROSS "true" for cross-compilation (host arch != TARGET_ARCH), +# "false" for a native build (default) + +ARG BASE_IMAGE=docker.io/library/ubuntu:resolute +FROM ${BASE_IMAGE} + +ARG TARGET_ARCH +ARG CROSS=false + +ENV DEBIAN_FRONTEND=noninteractive + +# Base tools to run debian/rules (dh_testdir/dch/etc) and resolve build-dep +# (dpkg-dev). Mirrors docker-pkg-build's Dockerfiles/base-packages.txt, +# trimmed to what this kernel build needs. +RUN apt-get update -qq && \ + apt-get install -y --no-install-recommends \ + ca-certificates dpkg-dev build-essential debhelper fakeroot devscripts && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Enable deb-src for build-dep +RUN sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources + +RUN if [ "${CROSS}" = "true" ]; then dpkg --add-architecture "${TARGET_ARCH}"; fi + +COPY extra-tools.txt /tmp/extra-tools.txt +RUN apt-get update -qq && \ + apt-get install -y $(cat /tmp/extra-tools.txt | tr '\n' ' ') && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Dynamically generated debian/control (from `debian/rules clean` against +# the real kernel source tree, patched for :native host-tool deps when +# cross-compiling) — not a hand-maintained snapshot. +COPY control /tmp/control-dir/debian/control + +RUN apt-get update -qq && \ + cd /tmp/control-dir && \ + if [ "${CROSS}" = "true" ]; then \ + apt-get build-dep -y --host-architecture "${TARGET_ARCH}" --build-profiles cross . && \ + apt-get build-dep -y . ; \ + else \ + apt-get build-dep -y . ; \ + fi && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/control-dir + +# Set default environment for build-kernel-deb.sh +ENV SKIP_BUILD_DEP=1 diff --git a/scripts/build-docker-image.sh b/scripts/build-docker-image.sh new file mode 100755 index 0000000000000..4f309315f9f77 --- /dev/null +++ b/scripts/build-docker-image.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# +# build-docker-image.sh - Build a local Docker image with kernel build +# dependencies resolved dynamically from a real +# kernel source tree's debian/control (via +# `debian/rules clean`), instead of a hand-maintained +# snapshot file. +# +# Usage: +# build-docker-image.sh [SOURCE_DIR] [ARCH] +# +# Arguments: +# SOURCE_DIR Root of the kernel source tree containing debian/ (default: .) +# ARCH Target Debian architecture to compile for: arm64 (default: arm64). +# Build host may be arm64 (native) or amd64 (cross-compile); amd64 +# is not a supported target since the qcom/qcom-rt flavours are +# arm64-only. +# +# Output: +# A local Docker image tagged kernel-build-docker:resolute-target-, +# where is the compile TARGET architecture, not necessarily the +# image's own native architecture (matches the build host, per the base +# image's multi-arch manifest). SKIP_BUILD_DEP=1 is preset so +# build-kernel-deb.sh skips apt-get build-dep when run inside it. Use via: +# IMAGE=kernel-build-docker:resolute-target- docker-build-kernel.sh ... +# +# Notes: +# • debian/control doesn't exist until `debian/rules clean` runs, so this +# script runs it on the host first and copies only the resulting file +# into a scratch build context — the kernel source tree itself is not +# baked into the image. +# • Modifies SOURCE_DIR/debian/changelog during clean; restored on exit +# (success or failure). +# • Base image: docker.io/library/ubuntu:resolute, with +# public.ecr.aws/ubuntu/ubuntu:resolute as a fallback for when that tag +# isn't published under the primary registry. Both are genuine +# multi-arch manifest lists; see Dockerfile.kernel-build. + +set -euo pipefail + +SOURCE_DIR="${1:-.}" +ARCH="${2:-arm64}" + +log() { printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >&2; } +die() { log "ERROR: $*"; exit 1; } +hr() { log "$(printf '%0.s─' {1..60})"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/gen-real-control.sh +source "${SCRIPT_DIR}/lib/gen-real-control.sh" + +[ -f "${SOURCE_DIR}/debian/rules" ] \ + || die "No debian/rules found in '${SOURCE_DIR}' – is this a kernel source tree?" + +SOURCE_DIR="$(cd "${SOURCE_DIR}" && pwd)" + +SUDO="" +if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" +fi + +BUILD_ARCH="$(dpkg --print-architecture)" +CROSS_BUILD=false +if [ "${ARCH}" != "${BUILD_ARCH}" ]; then + CROSS_BUILD=true +fi + +IMAGE_TAG="kernel-build-docker:resolute-target-${ARCH}" + +hr +log "Kernel build image (dynamic dependency resolution)" +log " Source dir : ${SOURCE_DIR}" +log " Build arch : ${BUILD_ARCH}" +log " Target arch: ${ARCH}$([ "${CROSS_BUILD}" = true ] && echo ' (cross-compiling)')" +log " Image tag : ${IMAGE_TAG}" +hr + +CROSS_ENV="" +if [ "${CROSS_BUILD}" = true ]; then + DEB_HOST_GNU_TYPE="$(dpkg-architecture -a"${ARCH}" -qDEB_HOST_GNU_TYPE)" + DEB_HOST_MULTIARCH="$(dpkg-architecture -a"${ARCH}" -qDEB_HOST_MULTIARCH)" + CROSS_ENV="DEB_HOST_ARCH=${ARCH} DEB_HOST_GNU_TYPE=${DEB_HOST_GNU_TYPE} DEB_HOST_MULTIARCH=${DEB_HOST_MULTIARCH}" +fi + +DEBIAN_DIR="$(get_debian_dir "${SOURCE_DIR}")" +reset_and_track_changelog "${SOURCE_DIR}" "${DEBIAN_DIR}" +run_debian_rules_clean "${SOURCE_DIR}" "${CROSS_ENV}" + +# --------------------------------------------------------------------------- +# Scratch build context: just the generated control file + a small tool +# list, not the whole kernel source tree. +# --------------------------------------------------------------------------- +CONTEXT_DIR="$(mktemp -d)" +add_cleanup "rm -rf \"${CONTEXT_DIR}\"" + +cp "${SOURCE_DIR}/debian/control" "${CONTEXT_DIR}/control" + +if [ "${CROSS_BUILD}" = true ]; then + patch_native_host_tool_deps "${CONTEXT_DIR}/control" +fi + +# Extra tool list: kernel-specific build-host tools not covered by +# Dockerfile.kernel-build's base package set. Cross-compiler toolchain +# packages are not listed here — debian/control already carries the full +# GCC_BUILD_DEPENDS set, so apt-get build-dep resolves the right one per ARCH. +printf '%s\n' bc bison flex > "${CONTEXT_DIR}/extra-tools.txt" + +# Prefer the official multi-arch ubuntu:resolute image; fall back to +# public.ecr.aws's mirror for whichever registry hasn't published that tag. +PRIMARY_BASE_IMAGE="docker.io/library/ubuntu:resolute" +FALLBACK_BASE_IMAGE="public.ecr.aws/ubuntu/ubuntu:resolute" +BASE_IMAGE="${PRIMARY_BASE_IMAGE}" +if ! docker manifest inspect "${PRIMARY_BASE_IMAGE}" >/dev/null 2>&1 \ + && ! docker image inspect "${PRIMARY_BASE_IMAGE}" >/dev/null 2>&1; then + log "${PRIMARY_BASE_IMAGE} not available — falling back to ${FALLBACK_BASE_IMAGE}" + BASE_IMAGE="${FALLBACK_BASE_IMAGE}" +fi + +hr +log "Building Docker image via docker build..." +log " Base image : ${BASE_IMAGE}" +docker build \ + -f "${SCRIPT_DIR}/Dockerfile.kernel-build" \ + --build-arg "BASE_IMAGE=${BASE_IMAGE}" \ + --build-arg "TARGET_ARCH=${ARCH}" \ + --build-arg "CROSS=${CROSS_BUILD}" \ + -t "${IMAGE_TAG}" \ + "${CONTEXT_DIR}" \ + || die "docker build failed" + +hr +log "Build complete." +log "Image: ${IMAGE_TAG}" +log "Use it with docker-build-kernel.sh via: IMAGE=${IMAGE_TAG} docker-build-kernel.sh ..." From 195b48b39670e690be77b4e78405e6588ed7720d Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Tue, 14 Jul 2026 01:52:56 +0800 Subject: [PATCH 03/10] docker-build-kernel: bind-mount only build-required paths, run as host uid/gid - Build the local image on demand via build-docker-image.sh if not already present. - Bind-mount the current directory, OUTPUT_DIR, SOURCE_DIR's parent (where debian/rules writes .deb/.changes/.buildinfo), and the script directory (read-only). - Run the container as the invoking host uid/gid. - Resolve SOURCE_DIR from the path passed in. - Support OUTPUT_DIR override, created on the host before the container starts. Signed-off-by: Guanquan Tian --- scripts/docker-build-kernel.sh | 158 +++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100755 scripts/docker-build-kernel.sh diff --git a/scripts/docker-build-kernel.sh b/scripts/docker-build-kernel.sh new file mode 100755 index 0000000000000..41cc3f9fa6a9c --- /dev/null +++ b/scripts/docker-build-kernel.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# +# docker-build-kernel.sh - Build Ubuntu kernel .deb packages inside a Docker container +# +# This script wraps build-kernel-deb.sh and runs it inside a Docker container. +# It automatically detects the current architecture and, if the appropriate +# Docker image isn't already available locally, builds one via +# build-docker-image.sh (using SOURCE_DIR's own debian/control). +# +# Arguments: positional args override env vars, which override defaults. +# SOURCE_DIR is resolved relative to the current working directory. +# +# Usage: +# docker-build-kernel.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] [VERSION_SUFFIX] +# +# Arguments: +# SOURCE_DIR Root of the kernel source tree (relative to current directory, default: resolute-qcom-devel) +# ARCH Target Debian architecture: arm64 (default: arm64) +# FLAVOR Kernel flavour: qcom | qcom-rt | all (default: qcom) +# JOBS Parallel make jobs (default: nproc) +# VERSION_SUFFIX Optional version suffix (default: none) +# +# Environment: +# IMAGE Docker image to use (default: kernel-build-docker:resolute-target-, +# built on demand via build-docker-image.sh if not already +# present locally). Set this to use a different image +# instead — it must already exist locally, since this +# script no longer pulls from a registry. +# OUTPUT_DIR Where to place built .deb packages (default: ./output +# relative to the current directory). Set this to a fixed +# absolute path if you don't want the output location to +# depend on which directory you invoke this script from. +# Created on the host (with your uid/gid) before the +# container starts, so it's never auto-created by Docker +# as root. +# DEBEMAIL Email for changelog entries (default: build-kernel-deb@localhost) +# DEBFULLNAME Full name for changelog entries (default: build-kernel-deb.sh) +# +# Output: +# Built .deb packages are placed in OUTPUT_DIR (default: ./output relative +# to the current directory). + +set -euo pipefail + +# Logging helpers +log() { printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >&2; } +die() { log "ERROR: $*"; exit 1; } +hr() { log "$(printf '%0.s─' {1..60})"; } + +# Resolve the script's absolute directory +SCRIPT_ABS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Get current working directory +CURRENT_DIR="$(pwd)" + +# Parse arguments and environment variables +# Priority: positional arguments > environment variables > defaults +SOURCE_DIR="${1:-${SOURCE_DIR:-resolute-qcom-devel}}" +ARCH="${2:-${ARCH:-arm64}}" +FLAVOR="${3:-${FLAVOR:-qcom}}" +JOBS="${4:-${JOBS:-$(nproc)}}" +VERSION_SUFFIX="${5:-${VERSION_SUFFIX:-}}" + +# Convert SOURCE_DIR (relative to current directory) to an absolute path +SOURCE_DIR_ABS="$(cd "${CURRENT_DIR}" && cd "${SOURCE_DIR}" 2>/dev/null && pwd)" || die "SOURCE_DIR '${SOURCE_DIR}' not found (relative to current directory: ${CURRENT_DIR})" + +# OUTPUT_DIR defaults to ./output relative to CURRENT_DIR; may be relative +# or absolute. Created here on the host so Docker doesn't auto-create it +# (and any missing parents) as root via the -v flag below. +OUTPUT_DIR="${OUTPUT_DIR:-${CURRENT_DIR}/output}" +mkdir -p "${OUTPUT_DIR}" || die "Failed to create OUTPUT_DIR '${OUTPUT_DIR}'" +OUTPUT_DIR="$(cd "${OUTPUT_DIR}" && pwd)" + +# Detect current system architecture +CURRENT_ARCH="$(uname -m)" +case "$CURRENT_ARCH" in + aarch64) CURRENT_ARCH="arm64" ;; + x86_64) CURRENT_ARCH="amd64" ;; +esac + +# Default to the local image build-docker-image.sh produces for this ARCH. +IMAGE="${IMAGE:-kernel-build-docker:resolute-target-${ARCH}}" + +hr +log "Docker kernel build" +log " Source dir : ${SOURCE_DIR} (resolved to: ${SOURCE_DIR_ABS})" +log " Target arch : ${ARCH}" +log " Current arch : ${CURRENT_ARCH}" +log " Flavour : ${FLAVOR}" +log " Jobs : ${JOBS}" +log " Docker image : ${IMAGE}" +log " Output dir : ${OUTPUT_DIR}" +hr + +# Build the local image on demand if not already present (no registry pull — +# see build-docker-image.sh). +log "Checking if Docker image exists locally..." +if docker image inspect "${IMAGE}" >/dev/null 2>&1; then + log "✓ Docker image found locally: ${IMAGE}" +else + log "✗ Docker image not found locally: ${IMAGE}" + log "Building it via build-docker-image.sh..." + "${SCRIPT_ABS}/build-docker-image.sh" "${SOURCE_DIR_ABS}" "${ARCH}" \ + || die "Failed to build local Docker image ${IMAGE}" +fi + +hr + +# Bind mounts: CURRENT_DIR, OUTPUT_DIR, SOURCE_DIR's parent (debian/rules +# writes .deb/.changes/.buildinfo to "..", next to SOURCE_DIR), and +# SCRIPT_ABS (read-only) — not the whole workspace. Duplicate/nested mounts +# are skipped to avoid overlapping -v flags. +SOURCE_DIR_PARENT="$(dirname "${SOURCE_DIR_ABS}")" +# write_dirs need -v (rw); SCRIPT_ABS defaults to :ro unless its path +# coincides with (or is a parent of) a write_dir. +write_dirs=("${CURRENT_DIR}" "${OUTPUT_DIR}" "${SOURCE_DIR_PARENT}") +mount_dirs=("${write_dirs[@]}" "${SCRIPT_ABS}") +docker_mounts=() +docker_mount_flags=() +for d in "${mount_dirs[@]}"; do + covered=false + for m in "${docker_mounts[@]}"; do + [ "${d}" = "${m}" ] && covered=true && break + case "${d}" in "${m}"/*) covered=true; break ;; esac + done + ${covered} && continue + docker_mounts+=("${d}") + needs_write=false + for w in "${write_dirs[@]}"; do + [ "${d}" = "${w}" ] && needs_write=true && break + done + if ${needs_write}; then + docker_mount_flags+=(-v "${d}:${d}") + else + docker_mount_flags+=(-v "${d}:${d}:ro") + fi +done + +docker_flags=(--rm "${docker_mount_flags[@]}") +[ -t 0 ] && [ -t 1 ] && docker_flags+=(-it) + +log "Bind mounts:" +for f in "${docker_mount_flags[@]}"; do + [ "${f}" = "-v" ] && continue + log " ${f}" +done +hr + +# Runs as the invoking host uid/gid so build output keeps host ownership. +exec docker run "${docker_flags[@]}" \ + -u "$(id -u):$(id -g)" \ + -w "${CURRENT_DIR}" \ + -e OUTPUT_DIR="${OUTPUT_DIR}" \ + -e DEBEMAIL="${DEBEMAIL:-}" \ + -e DEBFULLNAME="${DEBFULLNAME:-}" \ + "${IMAGE}" \ + bash "${SCRIPT_ABS}/build-kernel-deb.sh" "${SOURCE_DIR_ABS}" "${ARCH}" "${FLAVOR}" "${JOBS}" "${VERSION_SUFFIX}" From f24834cbf8b4089317835f13f6177e47973f6ced Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Tue, 14 Jul 2026 01:53:09 +0800 Subject: [PATCH 04/10] docs: document the local kernel build workflow in scripts/README.md Add scripts/README.md covering build-kernel-deb.sh, docker-build-kernel.sh, and build-docker-image.sh usage, arguments, environment variables, and cross-compilation notes. Signed-off-by: Guanquan Tian --- .gitignore | 1 + scripts/README.md | 284 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 .gitignore create mode 100644 scripts/README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000..ea1472ec1f38b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000000000..1ed502940d9f7 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,284 @@ +# Kernel Build Scripts + +This directory contains scripts for building Ubuntu kernel .deb packages for Qualcomm platforms. + +## Setup & Build Steps + +### 1. Docker Access + +Ensure your user can run Docker without sudo. If not already configured: + +```bash +sudo usermod -aG docker $USER +newgrp docker +``` + +Verify Docker access: + +```bash +docker ps +``` + +### 2. Prepare Workspace + +Create a workspace directory (you can rename `canonical-pkg` as needed): + +```bash +mkdir canonical-pkg && cd canonical-pkg +``` + +### 3. Pull Required Repositories + +#### a) Pull the CI orchestrator (main branch) + +```bash +git clone --depth 1 https://github.com/qualcomm-linux/pkg-linux-qcom-canonical.git +``` + +#### b) Pull the kernel source (resolute-qcom-devel branch) + +```bash +git clone -b resolute-qcom-devel --depth 1 https://github.com/qualcomm-linux/pkg-linux-qcom-canonical.git resolute-qcom-devel +``` + +#### c) Pull the Qualcomm DTB metadata + +```bash +git clone https://github.com/qualcomm-linux/qcom-dtb-metadata.git +``` + +Make the build scripts executable: + +```bash +chmod +x pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh +chmod +x pkg-linux-qcom-canonical/scripts/build-docker-image.sh +chmod +x qcom-dtb-metadata/build-dtb-image.sh +``` + +After completing the above, your workspace structure should look like: + +``` +canonical-pkg/ +├── pkg-linux-qcom-canonical/ # CI orchestrator (main branch) +│ ├── scripts/ +│ │ ├── docker-build-kernel.sh # Docker wrapper for builds +│ │ ├── build-kernel-deb.sh # Core build script +│ │ ├── build-docker-image.sh # Builds the local Docker image on demand +│ │ ├── Dockerfile.kernel-build # Image definition used by build-docker-image.sh +│ │ ├── lib/ # Shared helpers sourced by the scripts above +│ │ ├── README.md +│ │ └── ... +│ └── ... +├── resolute-qcom-devel/ # Kernel source (resolute-qcom-devel branch) +│ ├── debian.qcom/ # Qualcomm-specific build config +│ ├── arch/ +│ ├── drivers/ +│ └── ... +├── qcom-dtb-metadata/ # DTB metadata +│ ├── build-dtb-image.sh +│ └── ... +└── output/ # Build artifacts (created after first build) + ├── linux-image-*.deb + ├── linux-modules-*.deb + ├── linux-headers-*.deb + └── ... +``` + +--- + +## Quick Start + +After completing the setup steps above, from the workspace directory: + +```bash +# uses default source directory: resolute-qcom-devel +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh + +# Specify a different source directory (relative to current directory) +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh ./resolute-qcom-devel +``` + +Output packages: `./output/` + +--- + +## Common Arguments & Environment Variables + +### Arguments + +```bash +./scripts/docker-build-kernel.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] [VERSION_SUFFIX] +``` + +| Argument | Default | Description | +|----------|---------|-------------| +| `SOURCE_DIR` | `resolute-qcom-devel` | Root of kernel source tree (resolved relative to current working directory) | +| `ARCH` | `arm64` | Target architecture: `arm64` | +| `FLAVOR` | `qcom` | Kernel flavor: `qcom`, `qcom-rt`, or `all` (builds both `qcom` and `qcom-rt`) | +| `JOBS` | `$(nproc)` | Parallel make jobs | +| `VERSION_SUFFIX` | (none) | Version suffix (e.g., `+g1a2b3c4` or `+myuser1`). Pass `auto` to generate from git HEAD | + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `IMAGE` | `kernel-build-docker:resolute-target-` | Docker image to use; built on demand via `build-docker-image.sh` if not already present locally | +| `OUTPUT_DIR` | `./output` (relative to current directory) | Where built `.deb` packages are placed. Set to a fixed path (relative or absolute) if you don't want the output location to depend on which directory you invoke the script from. Created on the host before the container starts, so it's never auto-created by Docker as root | +| `VERSION_SUFFIX` | (none) | Optional version suffix for the kernel package (e.g., `+v1.0`, `+myuser1`). Pass `auto` to generate from git HEAD | +| `DEBEMAIL` | `build-kernel-deb@localhost` | Email for changelog entries | +| `DEBFULLNAME` | `build-kernel-deb.sh` | Full name for changelog entries | + +--- + +## Argument Priority + +Arguments are resolved in the following order (first match wins): + +1. **Positional arguments** — passed directly to the script +2. **Environment variables** — `SOURCE_DIR`, `ARCH`, `FLAVOR`, `JOBS`, `VERSION_SUFFIX` +3. **Default values** — built into the script + +`SOURCE_DIR` is resolved relative to the current working directory. + +### Example + +```bash +# JOBS comes from the environment variable since no 4th positional arg is given +JOBS=8 ./scripts/docker-build-kernel.sh resolute-qcom-devel arm64 qcom +# Result: SOURCE_DIR=resolute-qcom-devel, ARCH=arm64, FLAVOR=qcom, JOBS=8, VERSION_SUFFIX=(none) +``` + +--- + +## docker-build-kernel.sh + +Docker wrapper script for containerized kernel builds. + +### Usage + +```bash +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] [VERSION_SUFFIX] +``` + +### Examples + +```bash +# Basic build +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh + +# Only change source directory (other params use defaults) +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh ./resolute-qcom-devel + +# With custom version +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh ./resolute-qcom-devel arm64 qcom $(nproc) +v1.0 + +# Auto-generate version from git +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh ./resolute-qcom-devel arm64 qcom $(nproc) auto + +# Use environment variable for version suffix +VERSION_SUFFIX=+myuser1 ./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh + +# Auto-generate version via environment variable +VERSION_SUFFIX=auto ./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh + +``` + +--- + +## build-docker-image.sh (build your own local Docker image) + +`docker-build-kernel.sh` builds this image automatically on first use if +`kernel-build-docker:resolute-target-` isn't already present locally — +you normally don't need to invoke it yourself. To build (or rebuild) the +image explicitly — with build-dependencies resolved dynamically from your +actual kernel source tree's `debian/control` (via `debian/rules clean`), +rather than a hand-maintained snapshot file — use: + +```bash +./pkg-linux-qcom-canonical/scripts/build-docker-image.sh [SOURCE_DIR] [ARCH] +``` + +This produces a local image tagged `kernel-build-docker:resolute-target-` +(`` here is the compile *target* architecture, not necessarily the +image's own native architecture — see the note below). To use a different +image instead of the default, point `docker-build-kernel.sh` at it via the +`IMAGE` environment variable: + +```bash +./pkg-linux-qcom-canonical/scripts/build-docker-image.sh ./resolute-qcom-devel arm64 +IMAGE=my-custom-image:tag ./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh ./resolute-qcom-devel arm64 +``` + +Cross-compilation (e.g. building an arm64 image on an amd64 host) is +supported the same way — just pass `arm64` as `ARCH` while running on an +amd64 host, for both `build-docker-image.sh` and `docker-build-kernel.sh`. + +> **Note:** `` in the image tag is the *target* architecture you're +> compiling for, not the image's own native architecture. `FROM +> ubuntu:resolute` always resolves to the multi-arch manifest for the build +> host, so a `resolute-target-arm64` image built on an amd64 host is still a +> native amd64 image — just one with an arm64 cross-toolchain installed +> inside it. Building on an arm64 host would instead be a native arm64 +> image with the cross-toolchain layer skipped. + +> **Note:** `build-docker-image.sh` builds `FROM` the official +> `docker.io/library/ubuntu:resolute` image when available, falling back to +> `public.ecr.aws/ubuntu/ubuntu:resolute` for whichever registry hasn't +> published that tag. Both are genuine multi-arch manifest lists, so this +> fallback doesn't affect which architecture the resulting image runs as on +> your build host. + +--- + +## Create FIT dtb.bin + +After kernel build completes, create the FIT dtb.bin image: + +```bash +cd qcom-dtb-metadata + +# Build dtb.bin from kernel modules deb +sudo ./build-dtb-image.sh --soc hamoa purwa qcs6490 qcs8275 qcs9075 --kernel-deb {kernel-deb-path}/linux-modules-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb --out dtb.bin --prune +``` + +Output: `dtb.bin` + +--- + +## Update Kernel and DTB + +### 1. Install Kernel on Target Machine + +Example for kernel 7.0.0-1006-qcom: + +```bash +# Required +sudo dpkg -i linux-modules-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb +sudo dpkg -i linux-image-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb + +# Optional: kernel headers (for out-of-tree module development) +sudo dpkg -i linux-headers-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb +``` + +### 2. Flash dtb.bin + +First, ensure the `qdl` tool is installed: + +```bash +# Install qdl (Qualcomm Download tool) +sudo apt-get install -y qdl +``` + +Then flash the dtb.bin: + +```bash +qdl --storage spinor xbl_s_devprg_ns.melf write dtb_a dtb.bin +``` + +--- + +## License + +SPDX-License-Identifier: BSD-3-Clause + +See individual script headers for details. From e84a51a36da84bc8b419794081fc0e785499423e Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Mon, 27 Jul 2026 15:13:07 +0800 Subject: [PATCH 05/10] scripts: support building the kernel Docker image behind a proxy - Pass HTTP_PROXY/HTTPS_PROXY/NO_PROXY as Docker build args, scoped to the individual RUN steps in Dockerfile.kernel-build (not ENV), so they never leak into the built image or its containers. - Document the one-time, per-machine proxy setup and the two new env vars in scripts/README.md. - Note that linux-headers-*-qcom depends on linux-qcom-headers-*, so the latter must be installed first. Signed-off-by: Guanquan Tian --- scripts/Dockerfile.kernel-build | 15 ++++++++++++--- scripts/README.md | 18 ++++++++++++++++++ scripts/build-docker-image.sh | 3 +++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/scripts/Dockerfile.kernel-build b/scripts/Dockerfile.kernel-build index 395350d37bea7..f9c6005e5ba71 100644 --- a/scripts/Dockerfile.kernel-build +++ b/scripts/Dockerfile.kernel-build @@ -32,13 +32,20 @@ FROM ${BASE_IMAGE} ARG TARGET_ARCH ARG CROSS=false +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY ENV DEBIAN_FRONTEND=noninteractive # Base tools to run debian/rules (dh_testdir/dch/etc) and resolve build-dep # (dpkg-dev). Mirrors docker-pkg-build's Dockerfiles/base-packages.txt, # trimmed to what this kernel build needs. -RUN apt-get update -qq && \ +# +# Proxy vars are scoped to this RUN only (not ENV), so they won't leak +# into containers started from the built image. +RUN http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" \ + apt-get update -qq && \ apt-get install -y --no-install-recommends \ ca-certificates dpkg-dev build-essential debhelper fakeroot devscripts && \ apt-get clean && \ @@ -50,7 +57,8 @@ RUN sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.s RUN if [ "${CROSS}" = "true" ]; then dpkg --add-architecture "${TARGET_ARCH}"; fi COPY extra-tools.txt /tmp/extra-tools.txt -RUN apt-get update -qq && \ +RUN http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" \ + apt-get update -qq && \ apt-get install -y $(cat /tmp/extra-tools.txt | tr '\n' ' ') && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -60,7 +68,8 @@ RUN apt-get update -qq && \ # cross-compiling) — not a hand-maintained snapshot. COPY control /tmp/control-dir/debian/control -RUN apt-get update -qq && \ +RUN http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" \ + apt-get update -qq && \ cd /tmp/control-dir && \ if [ "${CROSS}" = "true" ]; then \ apt-get build-dep -y --host-architecture "${TARGET_ARCH}" --build-profiles cross . && \ diff --git a/scripts/README.md b/scripts/README.md index 1ed502940d9f7..d6cb996f082d1 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -55,6 +55,19 @@ chmod +x pkg-linux-qcom-canonical/scripts/build-docker-image.sh chmod +x qcom-dtb-metadata/build-dtb-image.sh ``` +### 4. Configure Proxy (First Time per Machine, if Needed) + +The first `docker-build-kernel.sh` run on a machine builds a local Docker image, which needs `apt-get` access to Ubuntu package mirrors. If this machine's network can't reach those mirrors directly, pass a proxy for that one build — it's only used while building the Docker image, never by the container itself or by later kernel builds on the same machine (the image is cached locally once built): + +```bash +# Replace your-proxy-server:8080 with your actual proxy address +HTTP_PROXY=http://your-proxy-server:8080 \ +HTTPS_PROXY=http://your-proxy-server:8080 \ +./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh +``` + +This is a **one-time, per-machine** step. + After completing the above, your workspace structure should look like: ``` @@ -127,6 +140,8 @@ Output packages: `./output/` | `VERSION_SUFFIX` | (none) | Optional version suffix for the kernel package (e.g., `+v1.0`, `+myuser1`). Pass `auto` to generate from git HEAD | | `DEBEMAIL` | `build-kernel-deb@localhost` | Email for changelog entries | | `DEBFULLNAME` | `build-kernel-deb.sh` | Full name for changelog entries | +| `HTTP_PROXY` | (none) | HTTP proxy URL for Docker image build only (e.g., `http://your-proxy-server:8080`). Only needed on first run if your network cannot access Ubuntu package mirrors directly | +| `HTTPS_PROXY` | (none) | HTTPS proxy URL for Docker image build only (e.g., `http://your-proxy-server:8080`). Only needed on first run if your network cannot access Ubuntu package mirrors directly | --- @@ -257,6 +272,9 @@ sudo dpkg -i linux-modules-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb sudo dpkg -i linux-image-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb # Optional: kernel headers (for out-of-tree module development) +# linux-headers-*-qcom depends on the arch-independent linux-qcom-headers-* +# package, so install that first. +sudo dpkg -i linux-qcom-headers-7.0.0-1006_7.0.0-1006.8_all.deb sudo dpkg -i linux-headers-7.0.0-1006-qcom_7.0.0-1006.8_arm64.deb ``` diff --git a/scripts/build-docker-image.sh b/scripts/build-docker-image.sh index 4f309315f9f77..fdcea0a9c7936 100755 --- a/scripts/build-docker-image.sh +++ b/scripts/build-docker-image.sh @@ -125,6 +125,9 @@ docker build \ --build-arg "BASE_IMAGE=${BASE_IMAGE}" \ --build-arg "TARGET_ARCH=${ARCH}" \ --build-arg "CROSS=${CROSS_BUILD}" \ + --build-arg "HTTP_PROXY=${HTTP_PROXY:-}" \ + --build-arg "HTTPS_PROXY=${HTTPS_PROXY:-}" \ + --build-arg "NO_PROXY=${NO_PROXY:-}" \ -t "${IMAGE_TAG}" \ "${CONTEXT_DIR}" \ || die "docker build failed" From 2e26ec9dfcf5fb0366b754d79df15129aac0b5d1 Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Mon, 31 Aug 2026 01:19:11 +0800 Subject: [PATCH 06/10] build-kernel-deb: add opt-in INCREMENTAL_BUILD flag debian/rules clean wipes debian/build/ and debian/stamps/ on every build, discarding kbuild's own object/cmd tracking and the per-flavour stamp state even when nothing changed. Add INCREMENTAL_BUILD to skip that destructive step (falling back to a full clean on the first build for a given SOURCE_DIR), while still refreshing debian/control and invalidating just the flavour-specific stamps so make re-enters those recipes. Incremental is the default; set INCREMENTAL_BUILD=0 to force a full clean. Signed-off-by: Guanquan Tian --- scripts/README.md | 24 ++++++++++++++++++++-- scripts/build-kernel-deb.sh | 24 +++++++++++++++++++++- scripts/docker-build-kernel.sh | 17 ++++++++++++++++ scripts/lib/gen-real-control.sh | 35 +++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index d6cb996f082d1..fbfedfc77bf0a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -138,6 +138,7 @@ Output packages: `./output/` | `IMAGE` | `kernel-build-docker:resolute-target-` | Docker image to use; built on demand via `build-docker-image.sh` if not already present locally | | `OUTPUT_DIR` | `./output` (relative to current directory) | Where built `.deb` packages are placed. Set to a fixed path (relative or absolute) if you don't want the output location to depend on which directory you invoke the script from. Created on the host before the container starts, so it's never auto-created by Docker as root | | `VERSION_SUFFIX` | (none) | Optional version suffix for the kernel package (e.g., `+v1.0`, `+myuser1`). Pass `auto` to generate from git HEAD | +| `INCREMENTAL_BUILD` | `1` | Set to `0`/`false`/`no`/`off` to force a full `debian/rules clean` (`rm -rf debian/build debian/stamps`) even when prior build state exists (falls back to a full clean automatically on the first build for a given `SOURCE_DIR` regardless). Turn off after switching branches, changing `debian/control`-level Build-Depends, or before a release/CI run | | `DEBEMAIL` | `build-kernel-deb@localhost` | Email for changelog entries | | `DEBFULLNAME` | `build-kernel-deb.sh` | Full name for changelog entries | | `HTTP_PROXY` | (none) | HTTP proxy URL for Docker image build only (e.g., `http://your-proxy-server:8080`). Only needed on first run if your network cannot access Ubuntu package mirrors directly | @@ -145,6 +146,25 @@ Output packages: `./output/` --- +### Incremental Builds + +Builds are incremental by default (`INCREMENTAL_BUILD=1`): build state +(`debian/build/`, `debian/stamps/`) persists across runs even though each +build runs in a `--rm` container, so kbuild only recompiles what changed. + +To force a clean build (e.g. after switching branches, changing +`debian/control`-level Build-Depends, or before a release/CI run): + +```bash +# Option 1: just run with INCREMENTAL_BUILD=0 +INCREMENTAL_BUILD=0 ./pkg-linux-qcom-canonical/scripts/docker-build-kernel.sh resolute-qcom-devel arm64 qcom + +# Option 2: delete the incremental state directly +rm -rf resolute-qcom-devel/debian/build resolute-qcom-devel/debian/stamps +``` + +--- + ## Argument Priority Arguments are resolved in the following order (first match wins): @@ -159,8 +179,8 @@ Arguments are resolved in the following order (first match wins): ```bash # JOBS comes from the environment variable since no 4th positional arg is given -JOBS=8 ./scripts/docker-build-kernel.sh resolute-qcom-devel arm64 qcom -# Result: SOURCE_DIR=resolute-qcom-devel, ARCH=arm64, FLAVOR=qcom, JOBS=8, VERSION_SUFFIX=(none) +JOBS=16 ./scripts/docker-build-kernel.sh resolute-qcom-devel arm64 qcom +# Result: SOURCE_DIR=resolute-qcom-devel, ARCH=arm64, FLAVOR=qcom, JOBS=16, VERSION_SUFFIX=(none) ``` --- diff --git a/scripts/build-kernel-deb.sh b/scripts/build-kernel-deb.sh index 063938b9be2c6..2c3007701b9e6 100755 --- a/scripts/build-kernel-deb.sh +++ b/scripts/build-kernel-deb.sh @@ -24,6 +24,18 @@ # to HEAD before every run, so this never leaves the tree # dirty. # +# Environment: +# INCREMENTAL_BUILD Set to 0/false/no/off to force debian/rules clean's +# rm -rf debian/build debian/stamps even when prior +# build state exists, so the next build is guaranteed +# clean (default: 1, incremental — kbuild only +# recompiles files that actually changed). Falls back +# to a full clean automatically on the first build for +# a given SOURCE_DIR. Do not leave enabled across a +# change to debian/control-level Build-Depends, or +# when a guaranteed-clean build is needed (e.g. before +# a release/CI run). +# # Output: # Built .deb packages are placed in ./output/ relative to the working # directory from which this script is invoked, unless OUTPUT_DIR is set @@ -46,6 +58,7 @@ FLAVOR="${3:-qcom}" JOBS="${4:-$(nproc)}" VERSION_SUFFIX="${5:-}" SKIP_BUILD_DEP="${SKIP_BUILD_DEP:-0}" +INCREMENTAL_BUILD="${INCREMENTAL_BUILD:-1}" OUTPUT_DIR="${OUTPUT_DIR:-$(pwd)/output}" # Normalize to an absolute path now, before any `cd` below changes pwd out @@ -96,6 +109,7 @@ log " Flavour : ${FLAVOR}" log " Jobs : ${JOBS}" log " Output dir : ${OUTPUT_DIR}" log " Version : ${VERSION_SUFFIX:-(none)}" +log " Incremental: $(is_truthy "${INCREMENTAL_BUILD}" && echo enabled || echo disabled)" hr # --------------------------------------------------------------------------- @@ -162,7 +176,15 @@ if [ -n "${VERSION_SUFFIX}" ]; then fi fi -run_debian_rules_clean "${SOURCE_DIR}" "${CROSS_ENV}" +BUILD_STATE_DIR="${SOURCE_DIR}/debian/build" +if is_truthy "${INCREMENTAL_BUILD}" && [ -d "${BUILD_STATE_DIR}" ]; then + refresh_control_incremental "${SOURCE_DIR}" "${DEBIAN_DIR}" "${CROSS_ENV}" + invalidate_flavour_stamps "${SOURCE_DIR}/debian/stamps" "${FLAVOR}" +else + is_truthy "${INCREMENTAL_BUILD}" \ + && log "INCREMENTAL_BUILD set but no previous build state found under ${BUILD_STATE_DIR} — doing a full 'debian/rules clean' this first time." + run_debian_rules_clean "${SOURCE_DIR}" "${CROSS_ENV}" +fi # --------------------------------------------------------------------------- # 3. Install build dependencies (if not skipped) diff --git a/scripts/docker-build-kernel.sh b/scripts/docker-build-kernel.sh index 41cc3f9fa6a9c..9cc1e768a8f57 100755 --- a/scripts/docker-build-kernel.sh +++ b/scripts/docker-build-kernel.sh @@ -36,6 +36,10 @@ # as root. # DEBEMAIL Email for changelog entries (default: build-kernel-deb@localhost) # DEBFULLNAME Full name for changelog entries (default: build-kernel-deb.sh) +# INCREMENTAL_BUILD Set to 0/false/no/off to force a full debian/rules +# clean even when prior build state exists (default: 1, +# incremental). See build-kernel-deb.sh for details and +# caveats. # # Output: # Built .deb packages are placed in OUTPUT_DIR (default: ./output relative @@ -48,6 +52,13 @@ log() { printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >&2; } die() { log "ERROR: $*"; exit 1; } hr() { log "$(printf '%0.s─' {1..60})"; } +is_truthy() { + case "${1:-}" in + 1|[tT][rR][uU][eE]|[yY][eE][sS]|[oO][nN]) return 0 ;; + *) return 1 ;; + esac +} + # Resolve the script's absolute directory SCRIPT_ABS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -91,6 +102,11 @@ log " Flavour : ${FLAVOR}" log " Jobs : ${JOBS}" log " Docker image : ${IMAGE}" log " Output dir : ${OUTPUT_DIR}" +if is_truthy "${INCREMENTAL_BUILD:-1}"; then + log " Incremental : enabled" +else + log " Incremental : disabled" +fi hr # Build the local image on demand if not already present (no registry pull — @@ -154,5 +170,6 @@ exec docker run "${docker_flags[@]}" \ -e OUTPUT_DIR="${OUTPUT_DIR}" \ -e DEBEMAIL="${DEBEMAIL:-}" \ -e DEBFULLNAME="${DEBFULLNAME:-}" \ + -e INCREMENTAL_BUILD="${INCREMENTAL_BUILD:-1}" \ "${IMAGE}" \ bash "${SCRIPT_ABS}/build-kernel-deb.sh" "${SOURCE_DIR_ABS}" "${ARCH}" "${FLAVOR}" "${JOBS}" "${VERSION_SUFFIX}" diff --git a/scripts/lib/gen-real-control.sh b/scripts/lib/gen-real-control.sh index 0866c0b5dc6a3..d183484647f19 100644 --- a/scripts/lib/gen-real-control.sh +++ b/scripts/lib/gen-real-control.sh @@ -91,6 +91,41 @@ run_debian_rules_clean() { || die "debian/rules clean did not produce ${source_dir}/debian/control" } +# Incremental-mode counterpart to run_debian_rules_clean(): refreshes +# debian/control and debian/changelog (still required by apt-get build-dep +# and the version-parsing steps later in build-kernel-deb.sh) WITHOUT +# wiping debian/build/ or debian/stamps/, so kbuild's and the stamp +# machinery's own incremental state survives across runs. +refresh_control_incremental() { + local source_dir="$1" debian_dir="$2" cross_env="${3:-}" + + log "INCREMENTAL_BUILD set and previous build state found under ${source_dir}/debian/build — skipping 'debian/rules clean' to preserve it." + + ( cd "${source_dir}" && cp "${debian_dir}/changelog" debian/changelog ) \ + || die "Failed to sync ${debian_dir}/changelog to debian/changelog" + + ( cd "${source_dir}" && env ${cross_env} fakeroot debian/rules debian/control ) \ + || die "Failed to refresh debian/control" + + [ -f "${source_dir}/debian/control" ] \ + || die "debian/rules debian/control did not produce ${source_dir}/debian/control" +} + +# Removes the prepare/build/install stamps for FLAVOR (or both qcom and +# qcom-rt when FLAVOR=all, mirroring build-kernel-deb.sh's own FLAVOR +# contract) so `make` re-enters those recipes on the next build instead of +# treating them as already satisfied. debian/build/build-/ itself +# is left untouched — kbuild's own .cmd-based dependency tracking (not +# make's stamp mtimes) decides which files actually need recompiling. +invalidate_flavour_stamps() { + local stampdir="$1" flavor="$2" f flavors + flavors="${flavor}" + [ "${flavor}" = "all" ] && flavors="qcom qcom-rt" + for f in ${flavors}; do + rm -f "${stampdir}/stamp-prepare-${f}" "${stampdir}/stamp-build-${f}" "${stampdir}/stamp-install-${f}" + done +} + # Packages debian/control lists without a `:native` qualifier but that the # kernel's own build rules invoke directly as build-host tools (e.g. # llvm-config- in debian/rules.d/2-binary-arch.mk) rather than linking From fe68bd75602f4ca672e9d8308cf87db2db8da422 Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Mon, 31 Aug 2026 10:23:29 +0800 Subject: [PATCH 07/10] build-kernel-deb: default JOBS to 8 instead of nproc Higher parallelism scales worse than expected on incremental (mostly-cached) rebuilds, since scheduling overhead outweighs the gains once most translation units are skipped or near-instant. Override explicitly (e.g. JOBS=$(nproc)) for from-scratch builds on many-core machines. Signed-off-by: Guanquan Tian --- scripts/README.md | 2 +- scripts/build-kernel-deb.sh | 7 +++++-- scripts/docker-build-kernel.sh | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index fbfedfc77bf0a..678c649470cee 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -128,7 +128,7 @@ Output packages: `./output/` | `SOURCE_DIR` | `resolute-qcom-devel` | Root of kernel source tree (resolved relative to current working directory) | | `ARCH` | `arm64` | Target architecture: `arm64` | | `FLAVOR` | `qcom` | Kernel flavor: `qcom`, `qcom-rt`, or `all` (builds both `qcom` and `qcom-rt`) | -| `JOBS` | `$(nproc)` | Parallel make jobs | +| `JOBS` | `8` | Parallel make jobs. Higher values scale worse than expected on incremental (mostly-unchanged) rebuilds due to scheduling overhead — override explicitly (e.g. to `$(nproc)`) for a from-scratch build on a many-core machine | | `VERSION_SUFFIX` | (none) | Version suffix (e.g., `+g1a2b3c4` or `+myuser1`). Pass `auto` to generate from git HEAD | ### Environment Variables diff --git a/scripts/build-kernel-deb.sh b/scripts/build-kernel-deb.sh index 2c3007701b9e6..c490423e67d0d 100755 --- a/scripts/build-kernel-deb.sh +++ b/scripts/build-kernel-deb.sh @@ -15,7 +15,10 @@ # gcc-aarch64-linux-gnu); amd64 is not a supported target since # the qcom/qcom-rt flavours are arm64-only. # FLAVOR Kernel flavour: qcom | qcom-rt | all (default: qcom) -# JOBS Parallel make jobs (default: nproc) +# JOBS Parallel make jobs (default: 8; incremental rebuilds with +# few changed files scale worse than expected past this +# due to scheduling overhead — override explicitly for a +# from-scratch build on a many-core machine) # VERSION_SUFFIX Optional string appended to the package/kernel version, # e.g. "+g1a2b3c4" or "+myuser1" (default: none). Pass # "auto" to generate "+g" from SOURCE_DIR's @@ -55,7 +58,7 @@ set -euo pipefail SOURCE_DIR="${1:-.}" ARCH="${2:-arm64}" FLAVOR="${3:-qcom}" -JOBS="${4:-$(nproc)}" +JOBS="${4:-8}" VERSION_SUFFIX="${5:-}" SKIP_BUILD_DEP="${SKIP_BUILD_DEP:-0}" INCREMENTAL_BUILD="${INCREMENTAL_BUILD:-1}" diff --git a/scripts/docker-build-kernel.sh b/scripts/docker-build-kernel.sh index 9cc1e768a8f57..c0b2f14182d57 100755 --- a/scripts/docker-build-kernel.sh +++ b/scripts/docker-build-kernel.sh @@ -18,7 +18,7 @@ # SOURCE_DIR Root of the kernel source tree (relative to current directory, default: resolute-qcom-devel) # ARCH Target Debian architecture: arm64 (default: arm64) # FLAVOR Kernel flavour: qcom | qcom-rt | all (default: qcom) -# JOBS Parallel make jobs (default: nproc) +# JOBS Parallel make jobs (default: 8) # VERSION_SUFFIX Optional version suffix (default: none) # # Environment: @@ -70,7 +70,7 @@ CURRENT_DIR="$(pwd)" SOURCE_DIR="${1:-${SOURCE_DIR:-resolute-qcom-devel}}" ARCH="${2:-${ARCH:-arm64}}" FLAVOR="${3:-${FLAVOR:-qcom}}" -JOBS="${4:-${JOBS:-$(nproc)}}" +JOBS="${4:-${JOBS:-8}}" VERSION_SUFFIX="${5:-${VERSION_SUFFIX:-}}" # Convert SOURCE_DIR (relative to current directory) to an absolute path From 0d7f0fba12e47385d28e046829096356f353697d Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Mon, 31 Aug 2026 01:36:26 +0800 Subject: [PATCH 08/10] Dockerfile.kernel-build: install git for VERSION_SUFFIX=auto support build-kernel-deb.sh's VERSION_SUFFIX=auto path and gen-real-control.sh's is_git_worktree() checks shell out to git, but the build image never installed it, so builds using VERSION_SUFFIX=auto failed inside the container. git is a script-level tool dependency, not a kernel build dependency, so it belongs in the hand-maintained base-tools layer rather than in the dynamically generated extra-tools.txt. Also document that docker-build-kernel.sh only builds the image when missing locally, so this (or any future Dockerfile) change requires removing the stale cached image before it takes effect. Signed-off-by: Guanquan Tian --- scripts/Dockerfile.kernel-build | 7 ++++++- scripts/README.md | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile.kernel-build b/scripts/Dockerfile.kernel-build index f9c6005e5ba71..15d1361f681ef 100644 --- a/scripts/Dockerfile.kernel-build +++ b/scripts/Dockerfile.kernel-build @@ -42,12 +42,17 @@ ENV DEBIAN_FRONTEND=noninteractive # (dpkg-dev). Mirrors docker-pkg-build's Dockerfiles/base-packages.txt, # trimmed to what this kernel build needs. # +# git is needed by build-kernel-deb.sh/gen-real-control.sh's is_git_worktree() +# checks and VERSION_SUFFIX=auto (git rev-parse HEAD) — not a kernel build +# dependency itself, so it lives here rather than in extra-tools.txt (which +# is generated from the kernel source tree's own debian/control). +# # Proxy vars are scoped to this RUN only (not ENV), so they won't leak # into containers started from the built image. RUN http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" \ apt-get update -qq && \ apt-get install -y --no-install-recommends \ - ca-certificates dpkg-dev build-essential debhelper fakeroot devscripts && \ + ca-certificates dpkg-dev build-essential debhelper fakeroot devscripts git && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/scripts/README.md b/scripts/README.md index 678c649470cee..39e4825cc0364 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -263,6 +263,16 @@ amd64 host, for both `build-docker-image.sh` and `docker-build-kernel.sh`. > fallback doesn't affect which architecture the resulting image runs as on > your build host. +> **Note:** `docker-build-kernel.sh` only builds the image if it's missing +> locally — it never rebuilds an already-cached one, even after +> `Dockerfile.kernel-build` changes. If you pull an update to this repo that +> touches the Dockerfile (e.g. a new base tool), remove the stale image so +> it gets rebuilt on the next run: +> +> ```bash +> docker rmi kernel-build-docker:resolute-target- +> ``` + --- ## Create FIT dtb.bin From 874348ac15954d59398abc7f049bd8ffa76d5098 Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Mon, 31 Aug 2026 11:48:07 +0800 Subject: [PATCH 09/10] build-kernel-deb: add DBGSYM flag to build -dbgsym.ddeb packages DBGSYM (default 0) maps to do_dbgsym_package=true/false on the debian/rules invocation, and .ddeb artifacts are now collected into OUTPUT_DIR alongside .deb/.changes/.buildinfo. docker-build-kernel.sh forwards DBGSYM into the container the same way it already does for INCREMENTAL_BUILD. Signed-off-by: Guanquan Tian --- scripts/README.md | 1 + scripts/build-kernel-deb.sh | 26 ++++++++++++++++++-------- scripts/docker-build-kernel.sh | 6 ++++++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index 39e4825cc0364..ec77cb72fc116 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -139,6 +139,7 @@ Output packages: `./output/` | `OUTPUT_DIR` | `./output` (relative to current directory) | Where built `.deb` packages are placed. Set to a fixed path (relative or absolute) if you don't want the output location to depend on which directory you invoke the script from. Created on the host before the container starts, so it's never auto-created by Docker as root | | `VERSION_SUFFIX` | (none) | Optional version suffix for the kernel package (e.g., `+v1.0`, `+myuser1`). Pass `auto` to generate from git HEAD | | `INCREMENTAL_BUILD` | `1` | Set to `0`/`false`/`no`/`off` to force a full `debian/rules clean` (`rm -rf debian/build debian/stamps`) even when prior build state exists (falls back to a full clean automatically on the first build for a given `SOURCE_DIR` regardless). Turn off after switching branches, changing `debian/control`-level Build-Depends, or before a release/CI run | +| `DBGSYM` | `0` | Set to `1`/`true`/`yes`/`on` to also build the unstripped `-dbgsym.ddeb` debug symbol packages (vmlinux + modules with full debug symbols) alongside the `.deb` packages | | `DEBEMAIL` | `build-kernel-deb@localhost` | Email for changelog entries | | `DEBFULLNAME` | `build-kernel-deb.sh` | Full name for changelog entries | | `HTTP_PROXY` | (none) | HTTP proxy URL for Docker image build only (e.g., `http://your-proxy-server:8080`). Only needed on first run if your network cannot access Ubuntu package mirrors directly | diff --git a/scripts/build-kernel-deb.sh b/scripts/build-kernel-deb.sh index c490423e67d0d..f953257f38bc3 100755 --- a/scripts/build-kernel-deb.sh +++ b/scripts/build-kernel-deb.sh @@ -38,6 +38,9 @@ # change to debian/control-level Build-Depends, or # when a guaranteed-clean build is needed (e.g. before # a release/CI run). +# DBGSYM Set to 1/true/yes/on to also build the unstripped +# -dbgsym.ddeb debug symbol packages alongside the +# .deb packages (default: 0, disabled). # # Output: # Built .deb packages are placed in ./output/ relative to the working @@ -62,6 +65,7 @@ JOBS="${4:-8}" VERSION_SUFFIX="${5:-}" SKIP_BUILD_DEP="${SKIP_BUILD_DEP:-0}" INCREMENTAL_BUILD="${INCREMENTAL_BUILD:-1}" +DBGSYM="${DBGSYM:-0}" OUTPUT_DIR="${OUTPUT_DIR:-$(pwd)/output}" # Normalize to an absolute path now, before any `cd` below changes pwd out @@ -113,6 +117,7 @@ log " Jobs : ${JOBS}" log " Output dir : ${OUTPUT_DIR}" log " Version : ${VERSION_SUFFIX:-(none)}" log " Incremental: $(is_truthy "${INCREMENTAL_BUILD}" && echo enabled || echo disabled)" +log " Dbgsym : $(is_truthy "${DBGSYM}" && echo enabled || echo disabled)" hr # --------------------------------------------------------------------------- @@ -251,11 +256,16 @@ fi export DEB_BUILD_OPTIONS="parallel=${JOBS} nocheck" # do_skip_checks=true bypasses the config-policy check (avoids failures when -# optional toolchains like Rust/bindgen are absent). RULES_TARGET is -# unquoted so make receives multiple targets as separate arguments. +# optional toolchains like Rust/bindgen are absent). do_dbgsym_package +# controls the unstripped -dbgsym.ddeb (vmlinux + modules with full debug +# symbols) — same knob and default as the CI build-kernel.yml workflow. +# RULES_TARGET is unquoted so make receives multiple targets as separate +# arguments. +DBGSYM_OPT="do_dbgsym_package=false" +is_truthy "${DBGSYM}" && DBGSYM_OPT="do_dbgsym_package=true" ( cd "${SOURCE_DIR}" - env ${CROSS_ENV} fakeroot debian/rules ${RULES_TARGET} do_skip_checks=true \ + env ${CROSS_ENV} fakeroot debian/rules ${RULES_TARGET} do_skip_checks=true "${DBGSYM_OPT}" \ || die "debian/rules ${RULES_TARGET} failed" ) @@ -265,9 +275,9 @@ export DEB_BUILD_OPTIONS="parallel=${JOBS} nocheck" hr # Clear only the artifact types we produce, not the whole dir. mkdir -p "${OUTPUT_DIR}" -rm -f "${OUTPUT_DIR}"/*.deb "${OUTPUT_DIR}"/*.changes "${OUTPUT_DIR}"/*.buildinfo +rm -f "${OUTPUT_DIR}"/*.deb "${OUTPUT_DIR}"/*.ddeb "${OUTPUT_DIR}"/*.changes "${OUTPUT_DIR}"/*.buildinfo -# Collect .deb files from the parent directory matching this build's version. +# Collect .deb/.ddeb files from the parent directory matching this build's version. PARENT_DIR=$(dirname "$(realpath "${SOURCE_DIR}")") DEB_VERSION="$(dpkg-parsechangelog -l "${SOURCE_DIR}/debian/changelog" -S Version)" \ || die "Failed to read package version from debian/changelog" @@ -283,7 +293,7 @@ while IFS= read -r -d '' f; do log " Collected: $(basename "${f}")" collected=$((collected + 1)) done < <(find "${PARENT_DIR}" -maxdepth 1 -name "*_${DEB_VERSION}_*" \ - \( -name "*.deb" -o -name "*.changes" -o -name "*.buildinfo" \) -print0) + \( -name "*.deb" -o -name "*.ddeb" -o -name "*.changes" -o -name "*.buildinfo" \) -print0) [ "${collected}" -gt 0 ] \ || die "Build finished but no artifacts matching version '${DEB_VERSION}' were found in ${PARENT_DIR}" @@ -292,5 +302,5 @@ hr log "Build complete." log "" log "Output packages:" -ls -lh "${OUTPUT_DIR}"/*.deb 2>/dev/null \ - || log " (no .deb files found — check build log above)" +ls -lh "${OUTPUT_DIR}"/ 2>/dev/null \ + || log " (no output files found — check build log above)" diff --git a/scripts/docker-build-kernel.sh b/scripts/docker-build-kernel.sh index c0b2f14182d57..159c78bdf9f14 100755 --- a/scripts/docker-build-kernel.sh +++ b/scripts/docker-build-kernel.sh @@ -40,6 +40,10 @@ # clean even when prior build state exists (default: 1, # incremental). See build-kernel-deb.sh for details and # caveats. +# DBGSYM Set to 1/true/yes/on to also build the unstripped +# -dbgsym.ddeb debug symbol packages alongside the .deb +# packages (default: 0, disabled). See build-kernel-deb.sh +# for details. # # Output: # Built .deb packages are placed in OUTPUT_DIR (default: ./output relative @@ -107,6 +111,7 @@ if is_truthy "${INCREMENTAL_BUILD:-1}"; then else log " Incremental : disabled" fi +log " Dbgsym : $(is_truthy "${DBGSYM:-0}" && echo enabled || echo disabled)" hr # Build the local image on demand if not already present (no registry pull — @@ -171,5 +176,6 @@ exec docker run "${docker_flags[@]}" \ -e DEBEMAIL="${DEBEMAIL:-}" \ -e DEBFULLNAME="${DEBFULLNAME:-}" \ -e INCREMENTAL_BUILD="${INCREMENTAL_BUILD:-1}" \ + -e DBGSYM="${DBGSYM:-0}" \ "${IMAGE}" \ bash "${SCRIPT_ABS}/build-kernel-deb.sh" "${SOURCE_DIR_ABS}" "${ARCH}" "${FLAVOR}" "${JOBS}" "${VERSION_SUFFIX}" From ddef6917b980482e825aeef487799984fb873dd1 Mon Sep 17 00:00:00 2001 From: Guanquan Tian Date: Mon, 31 Aug 2026 13:08:44 +0800 Subject: [PATCH 10/10] scripts: add -h/--help usage output build-kernel-deb.sh and docker-build-kernel.sh now print their Usage/Arguments/Environment/Output/Notes documentation when called with -h or --help, instead of it only being available as a header comment. Signed-off-by: Guanquan Tian --- scripts/build-kernel-deb.sh | 73 +++++++++++++++++++++++++++++++++- scripts/docker-build-kernel.sh | 54 +++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/scripts/build-kernel-deb.sh b/scripts/build-kernel-deb.sh index f953257f38bc3..473a42819d8d6 100755 --- a/scripts/build-kernel-deb.sh +++ b/scripts/build-kernel-deb.sh @@ -51,13 +51,82 @@ # Notes: # • Supports native (arm64 host) and cross (amd64 host, e.g. via dpkg # cross-architecture + gcc-aarch64-linux-gnu) builds. -# • Needs ~20 GB free disk space. -# • A full build (all flavours) takes 2+ hours; a single flavour ~1 hour. # • Designed to run inside a Docker container with preinstalled dependencies. # • For Docker builds, use the docker-build-kernel.sh wrapper. +# • Running this script directly on a bare host (not via +# docker-build-kernel.sh) requires the host's own apt sources to match +# the Ubuntu release SOURCE_DIR's debian/control targets (e.g. resolute); +# apt-get build-dep will fail to resolve packages on a host running a +# different release (e.g. noble), since the matching package mirror +# for that release won't be configured. set -euo pipefail +usage() { + cat <<'EOF' +Usage: build-kernel-deb.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] [VERSION_SUFFIX] + +Build Ubuntu kernel .deb packages from a Canonical source tree (as checked +out from a series branch). + +Arguments: + SOURCE_DIR Root of the kernel source tree containing debian/ (default: .) + ARCH Target Debian architecture to compile for: arm64 (default: arm64). + Build host may be arm64 (native) or amd64 (cross-compile via + gcc-aarch64-linux-gnu); amd64 is not a supported target since + the qcom/qcom-rt flavours are arm64-only. + FLAVOR Kernel flavour: qcom | qcom-rt | all (default: qcom) + JOBS Parallel make jobs (default: 8; incremental rebuilds with + few changed files scale worse than expected past this + due to scheduling overhead — override explicitly for a + from-scratch build on a many-core machine) + VERSION_SUFFIX Optional string appended to the package/kernel version, + e.g. "+g1a2b3c4" or "+myuser1" (default: none). Pass + "auto" to generate "+g" from SOURCE_DIR's + current HEAD (requires SOURCE_DIR to be a git work tree). + Modifies debian.qcom/changelog, but the script restores it + to HEAD before every run, so this never leaves the tree + dirty. + +Environment: + INCREMENTAL_BUILD Set to 0/false/no/off to force debian/rules clean's + rm -rf debian/build debian/stamps even when prior + build state exists, so the next build is guaranteed + clean (default: 1, incremental — kbuild only + recompiles files that actually changed). Falls back + to a full clean automatically on the first build for + a given SOURCE_DIR. Do not leave enabled across a + change to debian/control-level Build-Depends, or + when a guaranteed-clean build is needed (e.g. before + a release/CI run). + DBGSYM Set to 1/true/yes/on to also build the unstripped + -dbgsym.ddeb debug symbol packages alongside the + .deb packages (default: 0, disabled). + +Output: + Built .deb packages are placed in ./output/ relative to the working + directory from which this script is invoked, unless OUTPUT_DIR is set + in the environment (relative or absolute; normalized to an absolute path + up front), in which case that path is used instead. + +Notes: + • Supports native (arm64 host) and cross (amd64 host, e.g. via dpkg + cross-architecture + gcc-aarch64-linux-gnu) builds. + • Designed to run inside a Docker container with preinstalled dependencies. + • For Docker builds, use the docker-build-kernel.sh wrapper. + • Running this script directly on a bare host (not via + docker-build-kernel.sh) requires the host's own apt sources to match + the Ubuntu release SOURCE_DIR's debian/control targets (e.g. resolute); + apt-get build-dep will fail to resolve packages on a host running a + different release (e.g. noble), since the matching package mirror + for that release won't be configured. +EOF +} + +case "${1:-}" in + -h|--help) usage; exit 0 ;; +esac + SOURCE_DIR="${1:-.}" ARCH="${2:-arm64}" FLAVOR="${3:-qcom}" diff --git a/scripts/docker-build-kernel.sh b/scripts/docker-build-kernel.sh index 159c78bdf9f14..fa4abd23c86c0 100755 --- a/scripts/docker-build-kernel.sh +++ b/scripts/docker-build-kernel.sh @@ -51,6 +51,60 @@ set -euo pipefail +usage() { + cat <<'EOF' +Usage: docker-build-kernel.sh [SOURCE_DIR] [ARCH] [FLAVOR] [JOBS] [VERSION_SUFFIX] + +Build Ubuntu kernel .deb packages inside a Docker container. Wraps +build-kernel-deb.sh and runs it inside a Docker container. Automatically +detects the current architecture and, if the appropriate Docker image +isn't already available locally, builds one via build-docker-image.sh +(using SOURCE_DIR's own debian/control). + +Arguments: positional args override env vars, which override defaults. +SOURCE_DIR is resolved relative to the current working directory. + +Arguments: + SOURCE_DIR Root of the kernel source tree (relative to current directory, default: resolute-qcom-devel) + ARCH Target Debian architecture: arm64 (default: arm64) + FLAVOR Kernel flavour: qcom | qcom-rt | all (default: qcom) + JOBS Parallel make jobs (default: 8) + VERSION_SUFFIX Optional version suffix (default: none) + +Environment: + IMAGE Docker image to use (default: kernel-build-docker:resolute-target-, + built on demand via build-docker-image.sh if not already + present locally). Set this to use a different image + instead — it must already exist locally, since this + script no longer pulls from a registry. + OUTPUT_DIR Where to place built .deb packages (default: ./output + relative to the current directory). Set this to a fixed + absolute path if you don't want the output location to + depend on which directory you invoke this script from. + Created on the host (with your uid/gid) before the + container starts, so it's never auto-created by Docker + as root. + DEBEMAIL Email for changelog entries (default: build-kernel-deb@localhost) + DEBFULLNAME Full name for changelog entries (default: build-kernel-deb.sh) + INCREMENTAL_BUILD Set to 0/false/no/off to force a full debian/rules + clean even when prior build state exists (default: 1, + incremental). See build-kernel-deb.sh for details and + caveats. + DBGSYM Set to 1/true/yes/on to also build the unstripped + -dbgsym.ddeb debug symbol packages alongside the .deb + packages (default: 0, disabled). See build-kernel-deb.sh + for details. + +Output: + Built .deb packages are placed in OUTPUT_DIR (default: ./output relative + to the current directory). +EOF +} + +case "${1:-}" in + -h|--help) usage; exit 0 ;; +esac + # Logging helpers log() { printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >&2; } die() { log "ERROR: $*"; exit 1; }