NBPR.Buildroot.Finalize (NBPR v0.2.1)

Copy Markdown View Source

Applies Buildroot's target-finalize cleanup to a harvested per-package target tree.

Why this exists

Buildroot doesn't stop packages installing headers, static archives, docs and manpages — it lets them, then deletes the lot in one target-finalize recipe at the end of a full build, stripping every binary in $(TARGET_DIR) on the way past.

NBPR never reaches that step. It runs <pkg>-dirclean, <pkg> and <pkg>-legal-info, then harvests per-package/<pkg>/target/ directly. That's deliberate — target-finalize first rsyncs every package into a shared TARGET_DIR, which would destroy the per-package scoping NBPR.Buildroot.FilesList exists to preserve — but it meant artefacts shipped unstripped binaries and static archives into a rootfs whose own files Buildroot had stripped.

Measured on real artefacts: nbpr_chrony 1815 KB → 423 KB, nbpr_libcap 508 KB → 60 KB, nbpr_pps_tools 103 KB → 37 KB.

So this reproduces the same recipe against one package's harvested tree. Because it runs after the files-list copy, its removals overlap the narrower runtime filter in FilesList (and that filter's shell twin in the container backend): the filter avoids copying the obvious cases, this catches the rest. That filter stays — besides pre-filtering, it's the only thing cleaning staging/, which nothing here touches.

Shape

The whole thing is emitted as one POSIX shell fragment, and every decision that depends on Buildroot's configuration is made in that shell by grepping the resolved .config. That's not squeamishness about Elixir — it's that the config only becomes meaningful after make olddefconfig, and for the container backend the resolved file lives inside the container where Elixir can't reach it. Deciding in shell keeps one definition of "finalize" for both backends instead of one per backend.

What it doesn't do

staging/ is left alone, matching Buildroot — whose staging-finalize does nothing but create a symlink. Headers, .la files and unstripped libraries all survive there by design, because the point of the staging slice is cross-compiling consumer NIFs against the package. What trimming staging does get comes from FilesList, not here.

Stripping

Gated on BR2_STRIP_strip=y, which is what decides whether Buildroot's own STRIPCMD is strip or /bin/true. Buildroot's exclusions are reproduced, and they matter:

  • *.ko — kernel modules stop working if stripped like an executable, and NBPR ships them (kernel_modules: packages).
  • ld-*.so*, libpthread*.so*--strip-debug only, so valgrind and pthread-aware gdb keep working.

BR2_STRIP_EXCLUDE_DIRS / BR2_STRIP_EXCLUDE_FILES are deliberately not implemented. No stock Nerves system sets either, and translating them into find clauses is the kind of code that looks right and silently strips something it shouldn't. Instead, if a system sets one, stripping is skipped entirely with a warning — safe, obvious, and a clear prompt to implement it properly when something actually needs it.

Summary

Functions

Returns a POSIX shell fragment that finalizes target_dir.

Functions

script(target_dir, host_bin_dir, config_path)

@spec script(Path.t(), Path.t(), Path.t()) :: String.t()

Returns a POSIX shell fragment that finalizes target_dir.

host_bin_dir is the per-package host/bin holding the cross *-strip, and config_path the resolved Buildroot .config — both as seen by whoever runs the fragment, so the container backend passes container paths and the shell backend host ones.