Skip to content

Commit 45bddbd

Browse files
Merge pull request #13 from Awesome-Embedded-Learning-Studio/feat/forge-progress
feat: using buildmeter to beautify the building process
2 parents 707c08e + 29624fd commit 45bddbd

9 files changed

Lines changed: 159 additions & 13 deletions

File tree

.gitmodules

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
# Only rkbin is a real git submodule (closed DDR/TPL/SPL blobs — pinned at a
2-
# known-good commit). linux + uboot are fetched-clone trees under third_party/src/
3-
# (gitignored, managed by scripts/fetch-deps.sh against pins/), NOT submodules —
4-
# they carry our patch series, so a gitlink would drift. `ignore = dirty` keeps
5-
# rkbin build dirties from polluting the superproject status.
1+
# Real git submodules (pinned at known-good commits; init via forge.sh setup →
2+
# `git submodule update --init`, idempotent across all entries):
3+
# - rkbin: closed DDR/TPL/SPL blobs (Rockchip).
4+
# - buildmeter: our standalone build-progress-meter Python package
5+
# (github.com/Awesome-Embedded-Learning-Studio/buildmeter); scripts/lib/progress.sh
6+
# invokes it via $FORGE_PROGRESS_PY (formerly scripts/forge/progress.py).
7+
# linux + uboot are fetched-clone trees under third_party/src/ (gitignored,
8+
# managed by scripts/fetch-deps.sh against pins/), NOT submodules — they carry
9+
# our patch series, so a gitlink would drift. `ignore = dirty` keeps build
10+
# dirties from polluting the superproject status.
611
[submodule "third_party/rkbin"]
712
path = third_party/rkbin
813
url = https://github.com/rockchip-linux/rkbin.git
@@ -12,3 +17,7 @@
1217
# clone of a vendor SDK (e.g. 正点原子/ALIENTEK) used ONLY as a reference for
1318
# sdk-diff + board-DT/config extraction (an active extraction pool, not a build
1419
# input). Moved from third_party/ on 2026-06-20. See reference/README.md.
20+
[submodule "third_party/buildmeter"]
21+
path = third_party/buildmeter
22+
url = https://github.com/Awesome-Embedded-Learning-Studio/buildmeter
23+
ignore = dirty

README.en.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ bash scripts/forge.sh assemble --sd # build an SD-card image (RKFW; this boar
7777
bash scripts/forge.sh clean --full # clean rebuild
7878
```
7979

80+
> **Build progress:** `forge build` shows live progress ([buildmeter](https://github.com/Awesome-Embedded-Learning-Studio/buildmeter)) — a bordered color Panel if `rich` is installed, a zero-dependency ANSI bar otherwise. `FORGE_PROGRESS=0` disables it.
81+
8082
> **zsh users:** always invoke as `bash scripts/forge.sh ...` — the lib scripts rely on `BASH_SOURCE`, which is empty under zsh.
8183
8284
Flashing and on-board boot are in [QUICK_START.md](QUICK_START.md) and [document/tutorial/boot/](document/tutorial/boot/).

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ bash scripts/forge.sh assemble --sd # 出 SD 卡镜像(RKFW,本板 ROM
7777
bash scripts/forge.sh clean --full # 干净重建
7878
```
7979

80+
> **build 进度条**`forge build` 跑时显示实时进度([buildmeter](https://github.com/Awesome-Embedded-Learning-Studio/buildmeter))—— 装了 `rich` 走华丽边框 Panel,没装走零依赖 ANSI bar。`FORGE_PROGRESS=0` 关闭。
81+
8082
> **zsh 用户**:始终用 `bash scripts/forge.sh ...` 调用——lib 脚本依赖 `BASH_SOURCE`,在 zsh 下为空。
8183
8284
烧录与上板引导见 [QUICK_START.md](QUICK_START.md)[document/tutorial/boot/](document/tutorial/boot/)

scripts/build-initramfs.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ source "${_SCRIPT_DIR}/lib/env.sh" # PROJECT_ROOT + BRINGUP + SRC_DIR
3535
source "${_SCRIPT_DIR}/lib/log.sh"
3636
# shellcheck disable=SC1091
3737
source "${_SCRIPT_DIR}/lib/toolchain.sh" # CROSS_COMPILE / ARCH / TOOLCHAIN_BIN_DIR
38+
# shellcheck disable=SC1091
39+
source "${_SCRIPT_DIR}/lib/progress.sh" # forge_progress_run (live build progress when TTY)
3840

3941
OUT_CPIO="${BRINGUP}/fit/initramfs.cpio.gz"
4042
CLEAN=0
@@ -99,7 +101,12 @@ build_busybox() {
99101
sed -i 's/^# CONFIG_STATIC is not set$/CONFIG_STATIC=y/' .config
100102
grep -q 'CONFIG_STATIC=y' .config || echo 'CONFIG_STATIC=y' >> .config
101103
log_info "building static busybox (gcc 15.2)…"
102-
make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" >/dev/null
104+
if [[ -t 1 ]] && [[ "${FORGE_PROGRESS:-1}" == "1" ]] && command -v python3 >/dev/null 2>&1; then
105+
# TTY: progress bar (busybox is kbuild-style CC lines → kernel parser).
106+
forge_progress_run kernel make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)"
107+
else
108+
make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" >/dev/null
109+
fi
103110
)
104111
[[ -x "$bb" ]] || die "busybox build produced no binary ($BB_SRC)"
105112
file "$bb" | grep -q 'statically linked' || die "busybox is NOT static (CONFIG_STATIC=y?)"

scripts/build-linux.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ source "${_SCRIPT_DIR}/lib/env.sh" # _PROJECT_ROOT + LINUX_DIR/BOARD_CFG (co
2828
source "${_SCRIPT_DIR}/lib/log.sh"
2929
# shellcheck disable=SC1091
3030
source "${_SCRIPT_DIR}/lib/toolchain.sh"
31+
# shellcheck disable=SC1091
32+
source "${_SCRIPT_DIR}/lib/progress.sh" # forge_progress_run (live build progress when TTY)
3133

3234
APPLY=0; JUST_DTB=0
3335
while [[ $# -gt 0 ]]; do
@@ -73,7 +75,7 @@ if [[ "$JUST_DTB" == 1 ]]; then
7375
log_ok "dtb → arch/arm/boot/dts/rockchip/rk3506b-aes.dtb"
7476
else
7577
log_info "building zImage + dtbs …"
76-
make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" zImage dtbs
78+
forge_progress_run kernel make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" zImage dtbs
7779
make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" rockchip/rk3506b-aes.dtb
7880
log_ok "zImage → arch/arm/boot/zImage ; dtb → arch/arm/boot/dts/rockchip/rk3506b-aes.dtb"
7981

@@ -90,7 +92,7 @@ else
9092
if [[ ! -f drivers/net/wireless/realtek/rtl8733bu/8733bu.ko ]]; then
9193
[[ -f Module.symvers ]] || cp vmlinux.symvers Module.symvers
9294
log_info "building rtl8733bu.ko (in-tree module.ko target; missing — full-rebuild case)"
93-
make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" drivers/net/wireless/realtek/rtl8733bu/8733bu.ko
95+
forge_progress_run kernel make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" drivers/net/wireless/realtek/rtl8733bu/8733bu.ko
9496
log_ok "rtl8733bu.ko → drivers/net/wireless/realtek/rtl8733bu/8733bu.ko"
9597
else
9698
log_info "rtl8733bu.ko present (skip module build)"

scripts/build-rootfs.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ source "${_SCRIPT_DIR}/lib/log.sh"
2727
source "${_SCRIPT_DIR}/lib/host.sh" # forge_warn_windows_path / forge_clean_path
2828
# shellcheck disable=SC1091
2929
source "${_SCRIPT_DIR}/lib/toolchain.sh" # TOOLCHAIN_BIN_DIR / CROSS_COMPILE (toolchain SoT)
30+
# shellcheck disable=SC1091
31+
source "${_SCRIPT_DIR}/lib/progress.sh" # forge_progress_run (live build progress when TTY)
3032

3133
RECONFIGURE=0; CLEAN=0
3234
while [[ $# -gt 0 ]]; do
@@ -65,7 +67,7 @@ log_info "toolchain (from toolchain.conf): $BR2_TC_PATH"
6567
# WSL: buildroot dependencies.mk rejects PATH entries with spaces (/mnt/c/...).
6668
# forge_clean_path strips them; run make under the cleaned PATH.
6769
log_info "make (PATH cleaned of /mnt + whitespace, BR2_TOOLCHAIN_EXTERNAL_PATH from toolchain.conf)"
68-
PATH="$(forge_clean_path)" make BR2_TOOLCHAIN_EXTERNAL_PATH="$BR2_TC_PATH"
70+
PATH="$(forge_clean_path)" forge_progress_run buildroot make BR2_TOOLCHAIN_EXTERNAL_PATH="$BR2_TC_PATH"
6971

7072
ROOTFS_TAR="$BUILDROOT/output/images/rootfs.tar"
7173
[[ -f "$ROOTFS_TAR" ]] || die "buildroot produced no rootfs.tar"

scripts/build-uboot.sh

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ source "${_SCRIPT_DIR}/lib/env.sh" # _PROJECT_ROOT + UBOOT_DIR/OUT_DIR
4242
source "${_SCRIPT_DIR}/lib/log.sh"
4343
# shellcheck disable=SC1091
4444
source "${_SCRIPT_DIR}/lib/toolchain.sh"
45+
# shellcheck disable=SC1091
46+
source "${_SCRIPT_DIR}/lib/progress.sh" # FORGE_PROGRESS_PY + forge_progress_run
4547

4648
UBOOT_DIR_LOCAL="$UBOOT_DIR"; CLEAN=0; VARIANT="nand"
4749
while [[ $# -gt 0 ]]; do
@@ -119,10 +121,40 @@ log_info "make -j$(nproc) (binman combined-image failure tolerated; real dts/com
119121
# failure dies hard. (The old version piped through grep + `|| true` + only
120122
# checked `[[ -e u-boot.dtb ]]`, which PASSED on a stale artifact — silently
121123
# swallowing a dts parse error. See git history.)
122-
BINMAN_NOISE='BINMAN |simple-bin|rockchip-tpl|external blob|external TPL|faked external|images are invalid|Error 103|binman_stamp|/binman/|rockchip-linux/rkbin'
123-
( cd "$BUILD_DIR" && make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" ) > "$BUILD_LOG" 2>&1 || true
124-
# show progress minus the tolerated binman noise (so the console isn't flooded)
125-
grep -vE "$BINMAN_NOISE" "$BUILD_LOG" || true
124+
BINMAN_NOISE='BINMAN |simple-bin|rockchip-tpl|ROCKCHIP_TPL=|binary and build with|One possible source|Required binary blob|See the documentation|external blob|external TPL|faked external|images are invalid|Error 103|binman_stamp|/binman/|rockchip-linux/rkbin|ddr\.bin'
125+
UB_MAKE=( make ARCH="$ARCH" CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" )
126+
if [[ -t 1 ]] && [[ "${FORGE_PROGRESS:-1}" == "1" ]] && [[ -f "$FORGE_PROGRESS_PY" ]] && command -v python3 >/dev/null 2>&1; then
127+
# TTY + progress: bar over the make output (kernel parser — U-Boot is kbuild),
128+
# tee to BUILD_LOG so the real-error gate below still has the full log.
129+
# BINMAN_NOISE → --ignore-errors so the tolerated binman failures (the whole
130+
# blob-missing block: Error 103 / images are invalid / Required binary blob /
131+
# ROCKCHIP_TPL= / "binary and build with" / "One possible source" / ddr.bin)
132+
# neither triggers a false error dump nor shows on the live raw line.
133+
printf '[INFO] counting build units (make -n)…\n' >&2
134+
# `{ make -k -n || true; }` — -k keeps the dry-run enumerating past sub-make
135+
# errors (else it truncates → undercount); || true swallows the non-zero exit
136+
# so pipefail doesn't zero the total (same as lib/progress.sh's pre-scan).
137+
# buildmeter --count-only stderr is NOT silenced — on a TTY it shows the
138+
# pre-scan spinner; it only writes stderr on a TTY (CI unaffected). The
139+
# make-side ( ... ) 2>/dev/null still swallows make -n noise.
140+
UB_TOTAL=$( { ( cd "$BUILD_DIR" && "${UB_MAKE[@]}" -k -n ) 2>/dev/null || true; } \
141+
| python3 "$FORGE_PROGRESS_PY" --count-only kernel || true )
142+
UB_BUF=""
143+
command -v stdbuf >/dev/null 2>&1 && UB_BUF="stdbuf -oL"
144+
if [[ "$UB_TOTAL" -gt 0 ]] 2>/dev/null; then
145+
( cd "$BUILD_DIR" && $UB_BUF "${UB_MAKE[@]}" ) 2>&1 | tee "$BUILD_LOG" \
146+
| python3 "$FORGE_PROGRESS_PY" kernel --total "$UB_TOTAL" --log "$BUILD_LOG" \
147+
--ignore-errors "$BINMAN_NOISE" || true
148+
else
149+
( cd "$BUILD_DIR" && $UB_BUF "${UB_MAKE[@]}" ) 2>&1 | tee "$BUILD_LOG" \
150+
| python3 "$FORGE_PROGRESS_PY" kernel --log "$BUILD_LOG" \
151+
--ignore-errors "$BINMAN_NOISE" || true
152+
fi
153+
else
154+
# non-TTY / disabled: capture to log + show non-noise (original flow)
155+
( cd "$BUILD_DIR" && "${UB_MAKE[@]}" ) > "$BUILD_LOG" 2>&1 || true
156+
grep -vE "$BINMAN_NOISE" "$BUILD_LOG" || true
157+
fi
126158

127159
# Real-error gate: dtc (FATAL/Lexical/Syntax error), gcc (error:), or ld
128160
# (undefined reference). These never appear in the tolerated binman noise, so a

scripts/lib/progress.sh

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# lib/progress.sh — pipe a long `make` through buildmeter (third_party/buildmeter,
2+
# the standalone progress-meter package — formerly scripts/forge/progress.py) when
3+
# interactive, so a 72-minute build shows a live progress bar instead of an
4+
# endless scroll of CC/LD lines.
5+
#
6+
# Falls through to plain `make` (no pipe) when ANY of these holds:
7+
# - stdout isn't a TTY (CI, log redirect, forge stage capture) — plain output
8+
# - FORGE_PROGRESS=0 is set in the environment — plain output
9+
# - progress.py or python3 is missing — plain output
10+
# - the `make -n` pre-scan fails — indeterminate bar
11+
#
12+
# Usage (from a build script that has _SCRIPT_DIR set):
13+
# source "${_SCRIPT_DIR}/lib/progress.sh"
14+
# forge_progress_run kernel make ARCH=$ARCH CROSS_COMPILE=$CC -j$(nproc) zImage dtbs
15+
# forge_progress_run buildroot make BR2_TOOLCHAIN_EXTERNAL_PATH=$TC
16+
#
17+
# Pre-scan: runs `<make-cmd> -n` once first to count the build units (the
18+
# denominator for % + ETA). On a clean build this enumerates every step; on an
19+
# incremental build, only the steps about to run — either way it matches what
20+
# the real build will do. Disable with FORGE_PROGRESS_PRESCAN=0 (then the bar
21+
# runs indeterminate: count + rate + elapsed, no %).
22+
#
23+
# Exit code: returns the make command's exit (PIPESTATUS[0]), so `set -e` /
24+
# `pipefail` in the caller still catch real build failures. The progress.py
25+
# consumer is best-effort and never changes the build's success/failure.
26+
27+
# Self-locate so this lib doesn't depend on the caller having set _SCRIPT_DIR
28+
# (matches lib/toolchain.sh's BASH_SOURCE pattern; safe under `set -u`).
29+
_PROGRESS_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)
30+
# Points at buildmeter's script-style CLI entry (third_party/buildmeter
31+
# submodule). cli.py self-fixes sys.path so `python3 "$FORGE_PROGRESS_PY"` works
32+
# without PYTHONPATH. Same flag contract as the old progress.py (kind / --total /
33+
# --count-only / --log / --ignore-errors), so callers (build-uboot.sh's custom
34+
# block) need no business-logic change.
35+
FORGE_PROGRESS_PY="${_PROGRESS_LIB_DIR}/../../third_party/buildmeter/src/buildmeter/cli.py"
36+
37+
forge_progress_run() {
38+
local kind="$1"; shift
39+
local progress_py="$FORGE_PROGRESS_PY"
40+
41+
# Non-interactive or disabled → plain make, preserve exit.
42+
if [[ "${FORGE_PROGRESS:-1}" != "1" ]] || [[ ! -t 1 ]] \
43+
|| [[ ! -f "$progress_py" ]] || ! command -v python3 >/dev/null 2>&1; then
44+
"$@"
45+
return $?
46+
fi
47+
48+
# stdbuf -oL makes `make` line-buffer its stdout. make block-buffers when
49+
# stdout is a pipe (which it is here), and would starve the bar until EOF —
50+
# the bar would only appear at the very end. Fall back to plain if stdbuf
51+
# isn't available (coreutils; virtually always present on Linux).
52+
local buf=""
53+
if command -v stdbuf >/dev/null 2>&1; then buf="stdbuf -oL"; fi
54+
55+
# Pre-scan: dry-run for the denominator. Emit a status line first so the
56+
# silent dry-run (can take tens of seconds on a big tree) doesn't look like a hang.
57+
# `{ make -k -n || true; }` — two dry-run hazards swallowed:
58+
# - `-k` (keep-going): the dry-run aborts early on errors that only happen in
59+
# -n (e.g. kernel tools/objtool sub-make fails because libsubcmd isn't built
60+
# in a dry-run), truncating the CC enumeration → undercount → bar overflows
61+
# past 100%. -k makes make enumerate the rest despite the error.
62+
# - `|| true`: that same sub-make error makes `make -n` exit non-zero; swallow
63+
# it so the caller's `set -o pipefail` doesn't fail the pipe + zero the total.
64+
local total=0
65+
if [[ "${FORGE_PROGRESS_PRESCAN:-1}" == "1" ]]; then
66+
printf '[INFO] counting build units (make -n)…\n' >&2
67+
# NOTE: buildmeter --count-only stderr is NOT silenced here — on a TTY it
68+
# renders the pre-scan spinner (`⠙ scanning dry-run (make -n)…`), giving the
69+
# 15-30s dry-run a heartbeat. It only writes stderr on a TTY, so CI / pipe
70+
# / non-interactive is unaffected. The make-side 2>/dev/null above still
71+
# swallows make -n's own noise.
72+
total=$({ "$@" -k -n 2>/dev/null || true; } \
73+
| python3 "$progress_py" --count-only "$kind") || total=0
74+
if [[ "$total" -le 0 ]]; then
75+
printf '[INFO] pre-scan returned 0 — running indeterminate (no %% bar)\n' >&2
76+
fi
77+
fi
78+
79+
# Tee the full make output to a per-build log under /tmp (preserved for
80+
# reference / debugging), pipe through progress.py for the live bar.
81+
local logf="/tmp/forge-${kind}-${BASHPID:-$$}.log"
82+
printf '[INFO] full build log → %s\n' >&2 "$logf"
83+
if [[ "$total" -gt 0 ]] 2>/dev/null; then
84+
$buf "$@" 2>&1 | tee "$logf" | python3 "$progress_py" "$kind" --total "$total" --log "$logf"
85+
else
86+
$buf "$@" 2>&1 | tee "$logf" | python3 "$progress_py" "$kind" --log "$logf"
87+
fi
88+
return "${PIPESTATUS[0]}"
89+
}

third_party/buildmeter

Submodule buildmeter added at acec0db

0 commit comments

Comments
 (0)