This commit is contained in:
2026-05-01 01:39:04 +08:00
parent db6e2db502
commit 7167e36bf2
5 changed files with 638 additions and 0 deletions

View File

@@ -0,0 +1,536 @@
# Reproducible Container Build System for ASK Tarball Sources
## Executive summary
The supplied ASK archive is buildable in containers, but in its current form it is not hermetic or reproducible enough for serious CI use. Inspection of the archive shows a Debian-oriented arm64 build that still fetches sources during the build, assumes a GNU/glibc cross toolchain, and expects a separate kernel tree for module builds. That combination is fine for local hacking and bad for supply-chain control.
The clean fix is straightforward: close the build over a `packages/` directory that contains **every** source archive the build needs, pass only archive **paths** as build arguments, verify checksums, replace every `git clone` and `wget` step with tarball extraction plus patch application, and use a multi-stage Dockerfile that exports only artifacts. Dockers own docs explicitly support build arguments for parameterizing instructions, recommend multi-stage builds to keep final outputs small, and provide local exporters so you can pull files out of a build without shipping the entire toolchain image. citeturn9view0turn9view3turn9view5turn9view6turn23view0
The bad news is that a pure Alpine conversion is not free. Alpine uses musl, not glibc, and that changes ABI and runtime behavior. If the final ASK userspace binaries must run unchanged on a glibc-based target, Alpine-only is the wrong default unless you deliberately introduce a glibc sysroot, static-link what you can, or use compatibility mechanisms such as `gcompat` for simple cases. Alpines own docs and the musl docs both make that point indirectly but unmistakably. citeturn13view2turn13view3turn14view0turn13view1turn13view0
## What the supplied ASK archive implies
Inspection of the supplied ASK tarball shows a C and kernel-module build for entity["company","NXP","semiconductor company"] Layerscape hardware. The root Makefile already pins `fmlib` and `fmc` to `lf-6.12.49-2.2.0`, downloads `libnfnetlink-1.0.2` and `libnetfilter_conntrack-1.1.0` during the build, and assumes a separate kernel source tree through `KDIR`. The included `build/setup.sh` is Debian host bootstrap logic; inside a container, that script should be retired, not executed.
For Alpine, the dependency picture is mixed but manageable. Official Alpine packages exist for `libmnl-dev`, `libpcap-dev`, `libxml2-dev`, and `tclap-dev`, with `tclap-dev` living in `community`. The nuisance dependency is `libcli`: Alpines package index only shows `libcli` in edge/testing, not as a stable v3.22 package, while upstream libclis own README documents a plain `make && make install` flow into `/usr/local/lib`. For this project, vendoring libcli as a tarball and building it in a helper stage is the sane choice; enabling edge/testing for one leaf dependency is not. citeturn4view0turn4view1turn14view2turn25search0turn19view0turn24view0turn26search10
There is one more important project-specific constraint: the archive does **not** include the kernel tree that ASKs out-of-tree modules need. So a fully reproducible module build requires one more input archive, such as `packages/linux.tar.xz`, or a deliberate decision to build only `userspace`.
## Recommended build architecture
The right shape is a **closed build context**. Put the ASK tarball, every required dependency tarball, and optionally the matching kernel source tarball in `packages/`. Keep the Docker context small with `.dockerignore`. Verify those archives with `SHA256SUMS`. Set `SOURCE_DATE_EPOCH` so timestamps stop drifting. In CI, pin the base image by digest instead of trusting a moving tag. Dockers best-practices guide is blunt about why: tags move, digests do not, multi-stage builds slim outputs, and `.dockerignore` matters. The `SOURCE_DATE_EPOCH` spec exists precisely to keep timestamps from poisoning reproducibility. citeturn23view0turn22search2turn22search4turn22search1
For ASK specifically, the least painful Alpine route is to build natively for `linux/arm64` with Buildx rather than trying to recreate Debians `crossbuild-essential-arm64` model inside Alpine. Debian has a purpose-built GNU/glibc cross meta-package; Alpines stable equivalent is not that. Alpines native toolchain story is `build-base` on musl, and Dockers multi-platform build support is exactly what makes `--platform=linux/arm64` practical here. citeturn8view7turn13view4turn14view5turn9view4
```mermaid
flowchart TD
A[packages/*.tar.* in build context] --> B[buildx builder stage]
B --> C[extract ASK tarball]
B --> D[extract vendored dependency tarballs]
D --> E[patch and build fmlib, fmc, libnfnetlink, libnetfilter_conntrack, libcli]
C --> F[build ASK userspace and optionally kernel modules]
E --> F
F --> G[dist or artifact directory]
G --> H[scratch artifacts stage]
H --> I[buildx local exporter to out/ask]
```
## Concrete implementation
The wrapper below fixes the syntax problem in the prompt. `docker buildx build` needs an explicit build context, repeated `--build-arg KEY=VALUE` flags, and a `--file` argument. The local exporter is the right default here because ASKs real deliverable is a directory of build artifacts, not a fat runtime image. citeturn9view5turn9view6
**Recommended `.dockerignore`**
```dockerignore
**
!Makefile
!docker/**
!packages/**
```
Docker recommends using `.dockerignore` to exclude irrelevant files from the build context, and that matters here because the whole point is to build from a tightly controlled set of source tarballs. citeturn23view0
**Host-side vendoring script**
```sh
#!/usr/bin/env sh
set -eu
PACKAGES_DIR="${1:-packages}"
ASK_SRC="${ASK_SRC:-/absolute/path/to/ASK.tar.gz}"
NXP_TAG="lf-6.12.49-2.2.0"
mkdir -p "${PACKAGES_DIR}"
install -m 0644 "${ASK_SRC}" "${PACKAGES_DIR}/ASK.tar.gz"
curl -L --fail -o "${PACKAGES_DIR}/fmlib-${NXP_TAG}.tar.gz" \
"https://github.com/nxp-qoriq/fmlib/archive/refs/tags/${NXP_TAG}.tar.gz"
curl -L --fail -o "${PACKAGES_DIR}/fmc-${NXP_TAG}.tar.gz" \
"https://github.com/nxp-qoriq/fmc/archive/refs/tags/${NXP_TAG}.tar.gz"
curl -L --fail -o "${PACKAGES_DIR}/libnfnetlink-1.0.2.tar.bz2" \
"https://www.netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2"
curl -L --fail -o "${PACKAGES_DIR}/libnetfilter_conntrack-1.1.0.tar.xz" \
"https://www.netfilter.org/projects/libnetfilter_conntrack/files/libnetfilter_conntrack-1.1.0.tar.xz"
curl -L --fail -o "${PACKAGES_DIR}/libcli-1.10.7.tar.gz" \
"https://github.com/dparrish/libcli/archive/refs/tags/V1.10.7.tar.gz"
# Optional: add a matching kernel source archive for full module builds.
# install -m 0644 /path/to/linux.tar.xz "${PACKAGES_DIR}/linux.tar.xz"
(
cd "${PACKAGES_DIR}"
find . -maxdepth 1 -type f \
\( -name '*.tar.gz' -o -name '*.tar.xz' -o -name '*.tar.bz2' \) \
-print0 | sort -z | xargs -0 sha256sum > SHA256SUMS
)
```
If local repacking is part of your process, normalize ownership, ordering, and mtimes and set `SOURCE_DATE_EPOCH`; otherwise you are leaking host-local timestamps into your supposedly reproducible input archive. citeturn22search1turn22search4
**Root wrapper `Makefile`**
```make
.RECIPEPREFIX := >
PLATFORM ?= linux/arm64
ALPINE_VERSION ?= 3.22
BUILD_TARGET ?= dist
OUT_DIR ?= out/ask
IMAGE ?= ask-build:local
ASK_TAR ?= packages/ASK.tar.gz
FMLIB_TAR ?= packages/fmlib-lf-6.12.49-2.2.0.tar.gz
FMC_TAR ?= packages/fmc-lf-6.12.49-2.2.0.tar.gz
LIBNFNETLINK_TAR ?= packages/libnfnetlink-1.0.2.tar.bz2
LIBNFCT_TAR ?= packages/libnetfilter_conntrack-1.1.0.tar.xz
LIBCLI_TAR ?= packages/libcli-1.10.7.tar.gz
KERNEL_TAR ?= packages/linux.tar.xz
SOURCE_DATE_EPOCH ?= 1704067200
JOBS ?= 0
USERSPACE_CFLAGS ?=
USERSPACE_LDFLAGS ?=
.PHONY: ASK ASK_IMAGE
ASK:
> docker buildx build \
> --platform="$(PLATFORM)" \
> --file docker/ask.Dockerfile \
> --build-arg "ALPINE_VERSION=$(ALPINE_VERSION)" \
> --build-arg "ASK_TAR=$(ASK_TAR)" \
> --build-arg "FMLIB_TAR=$(FMLIB_TAR)" \
> --build-arg "FMC_TAR=$(FMC_TAR)" \
> --build-arg "LIBNFNETLINK_TAR=$(LIBNFNETLINK_TAR)" \
> --build-arg "LIBNFCT_TAR=$(LIBNFCT_TAR)" \
> --build-arg "LIBCLI_TAR=$(LIBCLI_TAR)" \
> --build-arg "KERNEL_TAR=$(KERNEL_TAR)" \
> --build-arg "BUILD_TARGET=$(BUILD_TARGET)" \
> --build-arg "SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)" \
> --build-arg "JOBS=$(JOBS)" \
> --build-arg "USERSPACE_CFLAGS=$(USERSPACE_CFLAGS)" \
> --build-arg "USERSPACE_LDFLAGS=$(USERSPACE_LDFLAGS)" \
> --target artifacts \
> --output "type=local,dest=$(OUT_DIR)" \
> .
ASK_IMAGE:
> docker buildx build \
> --platform="$(PLATFORM)" \
> --file docker/ask.Dockerfile \
> --build-arg "ALPINE_VERSION=$(ALPINE_VERSION)" \
> --build-arg "ASK_TAR=$(ASK_TAR)" \
> --build-arg "FMLIB_TAR=$(FMLIB_TAR)" \
> --build-arg "FMC_TAR=$(FMC_TAR)" \
> --build-arg "LIBNFNETLINK_TAR=$(LIBNFNETLINK_TAR)" \
> --build-arg "LIBNFCT_TAR=$(LIBNFCT_TAR)" \
> --build-arg "LIBCLI_TAR=$(LIBCLI_TAR)" \
> --build-arg "KERNEL_TAR=$(KERNEL_TAR)" \
> --build-arg "BUILD_TARGET=$(BUILD_TARGET)" \
> --build-arg "SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)" \
> --build-arg "JOBS=$(JOBS)" \
> --build-arg "USERSPACE_CFLAGS=$(USERSPACE_CFLAGS)" \
> --build-arg "USERSPACE_LDFLAGS=$(USERSPACE_LDFLAGS)" \
> --load \
> --tag "$(IMAGE)" \
> .
```
**`docker/overrides/toolchain.mk`**
```make
CROSS_COMPILE ?=
ARCH ?= arm64
PLATFORM ?= LS1043A
CC ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)gcc,gcc)
CXX ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)g++,g++)
AR ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)ar,ar)
STRIP ?= $(if $(CROSS_COMPILE),$(CROSS_COMPILE)strip,strip)
HOST ?= $(shell $(CC) -dumpmachine 2>/dev/null || echo aarch64-alpine-linux-musl)
KDIR ?= /opt/kernel
```
**`docker/overrides/Makefile`**
This override intentionally removes the upstream host-setup/network-fetch behavior and keeps only the build targets that matter for containerized CI.
```make
.RECIPEPREFIX := >
include build/toolchain.mk
include build/sources.mk
DIST := $(CURDIR)/dist
SRCDIR := $(CURDIR)/sources
PATCHES := $(CURDIR)/patches
VENDOR_DIR ?= /vendor/packages
HOST ?= $(shell $(CC) -dumpmachine 2>/dev/null || echo aarch64-alpine-linux-musl)
JOBS ?= $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
USERSPACE_CFLAGS ?=
USERSPACE_LDFLAGS ?=
FMLIB_DIR := $(SRCDIR)/fmlib
FMC_DIR := $(SRCDIR)/fmc/source
LIBFCI_DIR := $(CURDIR)/fci/lib
SYSROOT := $(SRCDIR)/sysroot
ABM_DIR := $(CURDIR)/auto_bridge
FMLIB_TAR ?= $(VENDOR_DIR)/fmlib-$(NXP_TAG).tar.gz
FMC_TAR ?= $(VENDOR_DIR)/fmc-$(NXP_TAG).tar.gz
LIBNFNETLINK_TAR ?= $(VENDOR_DIR)/libnfnetlink-$(LIBNFNETLINK_VER).tar.bz2
LIBNFCT_TAR ?= $(VENDOR_DIR)/libnetfilter_conntrack-$(LIBNFCT_VER).tar.xz
KBUILD_ARGS := CROSS_COMPILE=$(CROSS_COMPILE) ARCH=$(ARCH)
CDX_ARGS := $(KBUILD_ARGS) KERNELDIR=$(KDIR) PLATFORM=$(PLATFORM)
FCI_ARGS := $(KBUILD_ARGS) KERNEL_SOURCE=$(KDIR) BOARD_ARCH=$(ARCH) \
KBUILD_EXTRA_SYMBOLS=$(CURDIR)/cdx/Module.symvers
ABM_ARGS := $(KBUILD_ARGS) KERNEL_SOURCE=$(KDIR) PLATFORM=$(PLATFORM)
S := $(SRCDIR)/.stamps
$(shell mkdir -p $(S))
define extract_strip1
rm -rf $(2) && mkdir -p $(2) && tar -xf $(1) --strip-components=1 -C $(2)
endef
.PHONY: all sources modules userspace cdx fci auto_bridge fmc cmm dpa_app dist clean clean-all
all: modules userspace
sources: $(S)/fmlib $(S)/fmc $(S)/libfci $(S)/libnfnetlink $(S)/libnfct
$(S)/fmlib:
> @echo "==> fmlib: extract, patch, build"
> test -f $(FMLIB_TAR)
> $(call extract_strip1,$(FMLIB_TAR),$(FMLIB_DIR))
> cd $(FMLIB_DIR) && patch -p1 -i $(PATCHES)/fmlib/01-mono-ask-extensions.patch
> $(MAKE) -j$(JOBS) -C $(FMLIB_DIR) CROSS_COMPILE=$(CROSS_COMPILE) KERNEL_SRC=$(KDIR) libfm-arm.a
> ln -sf libfm-arm.a $(FMLIB_DIR)/libfm.a
> touch $@
$(S)/fmc:
> @echo "==> fmc: extract, patch, build"
> test -f $(FMC_TAR)
> rm -rf $(SRCDIR)/fmc
> mkdir -p $(SRCDIR)/fmc
> tar -xf $(FMC_TAR) -C $(SRCDIR)/fmc --strip-components=1
> cd $(FMC_DIR) && patch -p1 -i $(PATCHES)/fmc/01-mono-ask-extensions.patch
> $(MAKE) -j$(JOBS) -C $(FMC_DIR) \
> CC=$(CC) \
> CFLAGS="$(USERSPACE_CFLAGS)" \
> LDFLAGS="$(USERSPACE_LDFLAGS)" \
> TCLAP_HEADER_PATH=/usr/include \
> LIBXML2_HEADER_PATH=/usr/include/libxml2 \
> SYSROOT=$(SYSROOT)
> touch $@
$(S)/libnfnetlink:
> @echo "==> libnfnetlink: extract, patch, install into sysroot"
> test -f $(LIBNFNETLINK_TAR)
> rm -rf $(SRCDIR)/libnfnetlink-$(LIBNFNETLINK_VER)
> mkdir -p $(SRCDIR) $(SYSROOT)
> tar -xf $(LIBNFNETLINK_TAR) -C $(SRCDIR)
> cd $(SRCDIR)/libnfnetlink-$(LIBNFNETLINK_VER) && \
> patch -p1 -i $(PATCHES)/libnfnetlink/0001-libnfnetlink-fix-for-ARM64-and-clang.patch && \
> ./configure --host=$(HOST) --prefix=$(SYSROOT) --disable-shared --enable-static && \
> $(MAKE) -j$(JOBS) && $(MAKE) install
> touch $@
$(S)/libnfct: $(S)/libnfnetlink
> @echo "==> libnetfilter_conntrack: extract, patch, install into sysroot"
> test -f $(LIBNFCT_TAR)
> rm -rf $(SRCDIR)/libnetfilter_conntrack-$(LIBNFCT_VER)
> mkdir -p $(SRCDIR) $(SYSROOT)
> tar -xf $(LIBNFCT_TAR) -C $(SRCDIR)
> cd $(SRCDIR)/libnetfilter_conntrack-$(LIBNFCT_VER) && \
> patch -p1 -i $(PATCHES)/libnetfilter_conntrack/0001-libnetfilter_conntrack-fix-for-ARM64-and-clang.patch && \
> ./configure --host=$(HOST) --prefix=$(SYSROOT) --with-libnfnetlink=$(SYSROOT) --disable-shared --enable-static && \
> $(MAKE) -j$(JOBS) && $(MAKE) install
> touch $@
$(S)/libfci: $(S)/fmlib $(S)/libnfnetlink $(S)/libnfct
> @echo "==> libfci"
> $(MAKE) -C fci/lib CC=$(CC) AR=$(AR)
> touch $@
modules: cdx fci auto_bridge
userspace: fmc cmm dpa_app
cdx:
> $(MAKE) -C cdx $(CDX_ARGS)
fci: $(S)/libfci
> $(MAKE) -C fci $(FCI_ARGS)
auto_bridge:
> $(MAKE) -C auto_bridge $(ABM_ARGS)
fmc: $(S)/fmc
cmm: $(S)/libfci $(S)/libnfnetlink $(S)/libnfct
> $(MAKE) -C cmm \
> CC=$(CC) \
> LIBFCI_DIR=$(LIBFCI_DIR) \
> ABM_DIR=$(ABM_DIR) \
> SYSROOT=$(SYSROOT) \
> CFLAGS="$(USERSPACE_CFLAGS)" \
> LDFLAGS="$(USERSPACE_LDFLAGS)"
dpa_app: $(S)/fmc
> $(MAKE) -C dpa_app \
> CC=$(CC) \
> CFLAGS="-DDPAA_DEBUG_ENABLE -DNCSW_LINUX $(USERSPACE_CFLAGS) -I$(FMC_DIR) -I$(FMC_DIR)/inc/integrations/drivers/netcfg" \
> LDFLAGS="-lpthread -lcli -lxml2 -lstdc++ $(USERSPACE_LDFLAGS)"
dist: all
> rm -rf $(DIST)
> mkdir -p $(DIST)
> cp -a $(CURDIR)/cmm/src/cmm $(DIST)/
> cp -a $(CURDIR)/dpa_app/dpa_app $(DIST)/
> cp -a $(CURDIR)/cdx/cdx.ko $(DIST)/
> cp -a $(CURDIR)/fci/fci.ko $(DIST)/
> cp -a $(CURDIR)/auto_bridge/auto_bridge.ko $(DIST)/
> cp -a $(SRCDIR)/fmc/source/fmc $(DIST)/
> cp -a scripts/init/* $(DIST)/
clean:
> $(MAKE) -C auto_bridge clean || true
> $(MAKE) -C cdx clean || true
> $(MAKE) -C cmm clean || true
> $(MAKE) -C dpa_app clean || true
> $(MAKE) -C fci clean || true
> $(MAKE) -C fci/lib clean || true
> rm -rf $(DIST)
clean-all: clean
> rm -rf $(SRCDIR)
```
**`docker/ask.Dockerfile`**
This Dockerfile deliberately enables Alpine `community`, because Alpine documents that `community` is not enabled by default in many configurations, and ASK needs `tclap-dev`, which is in that repository. citeturn26search10turn25search0
```dockerfile
# syntax=docker/dockerfile:1.7
ARG ALPINE_VERSION=3.22
FROM alpine:${ALPINE_VERSION} AS builder
ARG ALPINE_VERSION
ARG ASK_TAR=packages/ASK.tar.gz
ARG FMLIB_TAR=packages/fmlib-lf-6.12.49-2.2.0.tar.gz
ARG FMC_TAR=packages/fmc-lf-6.12.49-2.2.0.tar.gz
ARG LIBNFNETLINK_TAR=packages/libnfnetlink-1.0.2.tar.bz2
ARG LIBNFCT_TAR=packages/libnetfilter_conntrack-1.1.0.tar.xz
ARG LIBCLI_TAR=packages/libcli-1.10.7.tar.gz
ARG KERNEL_TAR=packages/linux.tar.xz
ARG BUILD_TARGET=dist
ARG SOURCE_DATE_EPOCH=1704067200
ARG JOBS=0
ARG USERSPACE_CFLAGS=
ARG USERSPACE_LDFLAGS=
WORKDIR /work
RUN set -eux; \
printf '%s\n' \
"https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/main" \
"https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/community" \
> /etc/apk/repositories; \
apk add --no-cache \
bash \
bc \
bison \
build-base \
bzip2 \
coreutils \
file \
findutils \
flex \
gawk \
libmnl-dev \
libpcap-dev \
libxml2-dev \
linux-headers \
openssl \
openssl-dev \
patch \
perl \
pkgconf \
python3 \
tar \
tclap-dev \
xz \
zlib-dev
COPY packages/ /vendor/packages/
COPY docker/overrides/ /docker-overrides/
RUN set -eux; \
if [ -f /vendor/packages/SHA256SUMS ]; then \
cd /vendor/packages && sha256sum -c SHA256SUMS; \
fi
RUN set -eux; \
test -f "/vendor/${ASK_TAR}"; \
mkdir -p /work/src; \
tar -xf "/vendor/${ASK_TAR}" -C /work/src; \
test -d /work/src/ASK; \
rm -rf /work/src/ASK/.git; \
install -m 0644 /docker-overrides/Makefile /work/src/ASK/Makefile; \
install -m 0644 /docker-overrides/toolchain.mk /work/src/ASK/build/toolchain.mk; \
if [ ! -f /work/src/ASK/cmm/src/version.h ]; then \
printf '/* Auto-generated */\n#ifndef VERSION_H\n#define VERSION_H\n#define CMM_VERSION "%s"\n#endif\n' "tarball" > /work/src/ASK/cmm/src/version.h; \
fi
RUN set -eux; \
test -f "/vendor/${LIBCLI_TAR}"; \
mkdir -p /tmp/libcli; \
tar -xf "/vendor/${LIBCLI_TAR}" --strip-components=1 -C /tmp/libcli; \
make -C /tmp/libcli; \
make -C /tmp/libcli install; \
rm -rf /tmp/libcli
RUN set -eux; \
case "${BUILD_TARGET}" in \
all|modules|kernel|dist) \
test -f "/vendor/${KERNEL_TAR}" || { echo "KERNEL_TAR is required for BUILD_TARGET=${BUILD_TARGET}"; exit 2; }; \
mkdir -p /opt/kernel; \
tar -xf "/vendor/${KERNEL_TAR}" --strip-components=1 -C /opt/kernel; \
;; \
*) : ;; \
esac
ENV LC_ALL=C
ENV TZ=UTC
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
RUN set -eux; \
if [ "${JOBS}" = "0" ]; then JOBS="$(getconf _NPROCESSORS_ONLN)"; fi; \
make -C /work/src/ASK \
VENDOR_DIR=/vendor/packages \
FMLIB_TAR="/vendor/${FMLIB_TAR}" \
FMC_TAR="/vendor/${FMC_TAR}" \
LIBNFNETLINK_TAR="/vendor/${LIBNFNETLINK_TAR}" \
LIBNFCT_TAR="/vendor/${LIBNFCT_TAR}" \
KDIR=/opt/kernel \
JOBS="${JOBS}" \
USERSPACE_CFLAGS="${USERSPACE_CFLAGS}" \
USERSPACE_LDFLAGS="${USERSPACE_LDFLAGS}" \
"${BUILD_TARGET}"
RUN set -eux; \
mkdir -p /out; \
if [ -d /work/src/ASK/dist ]; then \
cp -a /work/src/ASK/dist/. /out/; \
else \
[ -f /work/src/ASK/cmm/src/cmm ] && cp /work/src/ASK/cmm/src/cmm /out/ || true; \
[ -f /work/src/ASK/dpa_app/dpa_app ] && cp /work/src/ASK/dpa_app/dpa_app /out/ || true; \
[ -f /work/src/ASK/sources/fmc/source/fmc ] && cp /work/src/ASK/sources/fmc/source/fmc /out/ || true; \
[ -f /work/src/ASK/cdx/cdx.ko ] && cp /work/src/ASK/cdx/cdx.ko /out/ || true; \
[ -f /work/src/ASK/fci/fci.ko ] && cp /work/src/ASK/fci/fci.ko /out/ || true; \
[ -f /work/src/ASK/auto_bridge/auto_bridge.ko ] && cp /work/src/ASK/auto_bridge/auto_bridge.ko /out/ || true; \
fi
FROM scratch AS artifacts
COPY --from=builder /out/ /
```
The Dockerfile above uses `COPY packages/` plus explicit `tar -xf` because that is the less magical pattern once you have **multiple optional archives** such as `KERNEL_TAR`, checksum verification, and tarballs whose extracted top-level directory names you do not want to trust blindly. Dockers docs are clear that `ADD` can auto-unpack local tar archives, but they are equally clear that `COPY` is the basic, explicit copy primitive. In practice, explicit wins here. citeturn23view0turn10view0turn10view1turn10view4
The normal invocation is `make ASK BUILD_TARGET=dist KERNEL_TAR=packages/linux.tar.xz` for a full module build, or `make ASK BUILD_TARGET=userspace` when only the source/dependency tarballs are available and the kernel tree is not. The artifact-export path is the better default; the image-loading target is only useful if you actually want a local OCI image as an intermediate. citeturn9view5turn9view6
**Short `ARG + ADD` pattern and a secret-safe variant**
If all you need is the shortest path-based pattern, Docker supports it:
```dockerfile
ARG ASK_TAR=packages/ASK.tar.gz
ADD ${ASK_TAR} /work/src/
```
That works because local `ADD` sources are relative to the build context and local tar archives are unpacked automatically. But Docker also warns that build args are not secret channels. So if the archive is confidential, use a BuildKit secret mount instead:
```dockerfile
# build command:
# docker buildx build --secret id=ask,src=packages/ASK.tar.gz -f docker/ask.Dockerfile .
RUN --mount=type=secret,id=ask,target=/tmp/ASK.tar.gz \
mkdir -p /work/src && tar -xf /tmp/ASK.tar.gz -C /work/src
```
That is the secure answer. Build args should carry a **path or selector**, not secret bytes. citeturn10view0turn10view2turn9view0turn9view1
## Debian and Alpine package mapping
The table below is the practical package/command map for the common build tools requested, plus the one ASK-specific exception that matters in real life.
| Need | Debian Trixie | Alpine 3.22 | Practical note |
|---|---|---|---|
| Meta toolchain bundle | `apt-get install -y build-essential` | `apk add --no-cache build-base` | Rough equivalents for native builds |
| arm64 cross bundle | `apt-get install -y crossbuild-essential-arm64` | no single stable equivalent | On Alpine, use `buildx --platform=linux/arm64` or bring your own GNU sysroot |
| C compiler | `apt-get install -y gcc` | `apk add --no-cache gcc` | `build-base` already includes it |
| C++ compiler | `apt-get install -y g++` | `apk add --no-cache g++` | `build-base` already includes it |
| `make` | `apt-get install -y make` | `apk add --no-cache make` | `build-base` already includes it |
| CMake | `apt-get install -y cmake` | `apk add --no-cache cmake` | Direct name match |
| pkg-config tooling | `apt-get install -y pkgconf` | `apk add --no-cache pkgconf` | On Debian, `pkg-config` is now effectively the pkgconf world too |
| OpenSSL headers/libs | `apt-get install -y libssl-dev` | `apk add --no-cache openssl-dev` | Direct development-package equivalents |
| zlib headers/libs | `apt-get install -y zlib1g-dev` | `apk add --no-cache zlib-dev` | Direct development-package equivalents |
| C++ runtime | runtime usually via `libstdc++6`; headers via `g++`/`libstdc++-14-dev` | `apk add --no-cache libstdc++` | Alpine splits runtime from the compiler meta-package |
| libc headers | `apt-get install -y libc6-dev` | `apk add --no-cache musl-dev` | Alpine exposes `libc-dev` through musl |
| ASK-specific TCLAP | `apt-get install -y libtclap-dev` | `apk add --no-cache tclap-dev` | Needs Alpine `community` |
| ASK-specific libcli | `apt-get install -y libcli-dev` | vendor tarball | Stable Alpine v3.22 does not have a normal `libcli-dev` package |
These mappings are grounded in Debians package pages and Alpines official package index. The important details are: `build-base` bundles `gcc`, `g++`, `make`, `patch`, and `libc-dev`; `musl-dev` provides the `libc-dev` role on Alpine; `pkgconf` is the package you actually install for pkg-config tooling; and Debians `crossbuild-essential-arm64` does not have a one-package Alpine twin. citeturn5search0turn8view7turn7view0turn8view0turn8view2turn5search1turn5search9turn8view3turn8view4turn8view5turn8view6turn14view5turn13view4turn4view2turn4view3turn14view1turn14view3turn14view4
For ASK specifically, the two Alpine deltas worth remembering are `tclap-dev` in `community` and the absence of stable `libcli`, which is why the helper-stage libcli build is worth doing. citeturn25search0turn19view0turn24view0turn26search10
## Musl compatibility, troubleshooting, and assumptions
This is where most Alpine ports actually fail.
- **Do not trust glibc-specific `#ifdef` logic.** The musl FAQ explicitly calls out hardcoded glibc assumptions and wrong `__GLIBC__` checks as common failure causes. Fix the preprocessor logic first, not last. The same FAQ also calls out GNU `getopt` expectations, iconv/UCS2 assumptions, `off_t` width assumptions, and too-small default thread stacks as recurring causes of runtime failures. citeturn13view1
- **Musl locale behavior is not glibc locale behavior.** Alpines own musl page says musl does not implement most of the locale features that glibc implements. If your code relies on glibc locale internals, stop pretending Alpine is a drop-in replacement. citeturn13view2
- **Plugin unload/reload semantics differ.** musls dynamic loader keeps libraries loaded for the life of the process and treats `dlclose` as a no-op. If your software depends on glibc-style unload/reload behavior, that is a real portability bug, not a packaging bug. citeturn13view0
- **Static versus dynamic needs a hard-headed choice.** `-static-libgcc` and `-static-libstdc++` are good pragmatic flags when the problem is just the GCC/C++ runtime. They are not magic. Full static linking only works cleanly when every dependency is available as a static archive and your deployment model actually benefits from it.
- **`gcompat` is for simple runtime cases, not for wishful thinking.** Alpine documents `gcompat` as a glibc compatibility layer for musl systems, and the package provides the relevant `libc6-compat`/loader compatibility pieces. For more complex glibc applications, Alpines own docs point you toward a glibc chroot/container instead. citeturn13view3turn14view0
- **ASK-specific practical fix:** because ASKs source already contains at least one musl-friendly guard around `execinfo`/`backtrace`, a native Alpine build is plausible for the build itself. The unresolved question is not “can it compile?” It is “what libc must the resulting userspace binaries target on the final device?”
Where the upstream project is genuinely unspecified, the pattern stays the same and only the builder image family changes: C/C++/Make/CMake/autotools usually start from `alpine`, Go from `golang:*-alpine`, Python from `python:*-alpine`, and Node from `node:*-alpine`, all with the same multi-stage split. The exception rule is blunt: the moment the project depends on glibc-only binaries, ABI-sensitive vendor libraries, or packaging ecosystems that assume glibc, switch back to a glibc builder instead of burning time fighting musl. Dockers own guidance is to use trusted minimal images and multi-stage builds; Alpines own docs make clear that musl/glibc compatibility is extra engineering work, not something the base image solves for you. citeturn23view0turn9view3turn13view2turn13view3
**Open questions / limitations**
The supplied ASK tarball did not include the matching kernel source tree, so a fully reproducible module build still needs one more input archive. The deploy targets libc requirement was also unspecified; that matters a lot, because a musl-linked `dpa_app` or `cmm` is not automatically a drop-in replacement for a glibc target userland. Finally, no existing Debian Dockerfiles were present in the supplied archive, so the Alpine conversion above is a concrete replacement design derived from the archives real build logic, not a literal line-by-line rewrite of preexisting Dockerfiles.