Skip to content

Commit 241d760

Browse files
committed
cli: add show-extensions command to list extension hook points
Statically scans the build sources for call_extension_method sites and prints the available extension hook points. Unlike the build-time auto-generated docs, the list is exhaustive regardless of which hooks a given build triggers, and needs no board config or Docker. Default output is the sorted hook names; SHOW_EXTENSIONS=docs emits Markdown with each hook's inline documentation. Suggested in armbian/documentation#859. Assisted-by: Claude:claude-opus-4-8
1 parent d8387c6 commit 241d760

3 files changed

Lines changed: 128 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env bash
2+
#
3+
# SPDX-License-Identifier: GPL-2.0
4+
#
5+
# Copyright (c) 2013-2026 Igor Pecovnik, igor@armbian.com
6+
#
7+
# This file is a part of the Armbian Build Framework
8+
# https://github.com/armbian/build/
9+
10+
function cli_show_extensions_pre_run() {
11+
# Informational only: no host deps, no Docker relaunch, no board config needed; it just reads ${SRC}.
12+
# Skip the log tmpfs mount so a root invocation in an unprivileged container works too (no mount syscall).
13+
declare -g USE_TMPFS="${USE_TMPFS:-no}"
14+
return 0
15+
}
16+
17+
function cli_show_extensions_run() {
18+
# Output format: "list" (default) prints the hook names, one per line, sorted.
19+
# "docs" prints a Markdown document with each hook's inline documentation.
20+
declare format="${SHOW_EXTENSIONS:-list}"
21+
22+
display_alert "Scanning sources for extension hook points" "lib, extensions, config" "info"
23+
24+
# Find every framework file that actually calls a hook, regardless of suffix or directory, so the
25+
# list stays exhaustive even for hooks declared outside lib/ and extensions/ (e.g. config/sources/*.conf).
26+
declare -a hook_files=()
27+
mapfile -t -d '' hook_files < <(grep -r -I -l -Z "call_extension_method" "${SRC}/lib" "${SRC}/extensions" "${SRC}/config" 2> /dev/null)
28+
if [[ ${#hook_files[@]} -eq 0 ]]; then
29+
display_alert "No hook call sites found" "nothing to list" "warn"
30+
return 0
31+
fi
32+
33+
# Statically parse each call site and its inline heredoc (the hook's documentation), the same data the
34+
# build's metadata collector records, but without needing a build to run first. Uses only POSIX awk
35+
# features (no gawk-specific asorti/PROCINFO) so it runs before host deps are installed; sorting and
36+
# formatting happen in the shell pipeline below.
37+
printf '%s\0' "${hook_files[@]}" |
38+
xargs -0 awk '
39+
BEGIN { dq = "\042"; FSEP = "\002"; NLEN = "\001" } # double-quote, field-sep and newline-placeholder bytes
40+
41+
# Collect every double-quoted token from a string into arr[1..n]; returns n.
42+
function extract_quotes(s, arr, i, c, inq, cur, n) {
43+
n = 0; inq = 0; cur = ""
44+
for (i = 1; i <= length(s); i++) {
45+
c = substr(s, i, 1)
46+
if (c == dq) {
47+
if (inq) { arr[++n] = cur; cur = ""; inq = 0 } else { inq = 1 }
48+
} else if (inq) { cur = cur c }
49+
}
50+
return n
51+
}
52+
53+
FNR == 1 { in_heredoc = 0 } # a never-closed heredoc must not bleed across files
54+
55+
{
56+
if (!in_heredoc) {
57+
# A real hook call starts the line (modulo indent): <tab>call_extension_method "name" [compat...] <<- DELIM
58+
# Anchoring on line start skips comments and code that merely mention the function name.
59+
if ($0 ~ /^[ \t]*call_extension_method[ \t]+\042/ && index($0, "<<") > 0) {
60+
call_part = substr($0, 1, index($0, "<<") - 1)
61+
n = extract_quotes(call_part, toks)
62+
if (n >= 1) {
63+
cur_hook = toks[1]
64+
gsub(/\$\{[^}]*\}/, "<branch>", cur_hook) # templated names like ${BRANCH,,} are not literal hooks
65+
compat = ""
66+
for (i = 2; i <= n; i++) compat = compat (compat == "" ? "" : " ") toks[i]
67+
68+
# The heredoc delimiter is the first bare word after "<<", minus "-" and quotes.
69+
rest = substr($0, index($0, "<<") + 2)
70+
sub(/^-/, "", rest); sub(/^[ \t]+/, "", rest)
71+
gsub(/\042/, "", rest); gsub(/\047/, "", rest)
72+
split(rest, da, /[ \t]+/); delim = da[1]
73+
74+
in_heredoc = 1; body = ""
75+
}
76+
}
77+
next
78+
}
79+
80+
# Inside the heredoc body; a line equal to the delimiter (ignoring indent) closes it.
81+
line = $0; trimmed = line; sub(/^[ \t]+/, "", trimmed)
82+
if (trimmed == delim) {
83+
in_heredoc = 0
84+
print cur_hook FSEP compat FSEP body # one record per hook; sorted/formatted downstream
85+
next
86+
}
87+
sub(/^\t+/, "", line) # mimic "<<-" tab stripping
88+
body = (body == "" ? line : body NLEN line)
89+
}
90+
' | LC_ALL=C sort -u | format="${format}" awk -F "\002" '
91+
BEGIN {
92+
if (ENVIRON["format"] == "docs") {
93+
print "# Armbian build system extension hook points"
94+
print "- Generated by '\''./compile.sh show-extensions SHOW_EXTENSIONS=docs'\'' from the build sources."
95+
print "- Hooks are listed alphabetically; the build invokes them in code order, not this order."
96+
print ""
97+
}
98+
}
99+
!seen[$1]++ { # dedup hook points called from more than one site
100+
if (ENVIRON["format"] != "docs") { print $1; next }
101+
nlines = split($3, bl, "\001")
102+
print "### `" $1 "`"
103+
print "> " bl[1]
104+
print ""
105+
for (j = 2; j <= nlines; j++) print bl[j]
106+
if ($2 != "") {
107+
print ""
108+
print "Also known as (for backwards compatibility only):"
109+
nc = split($2, ca, " ")
110+
for (j = 1; j <= nc; j++) print "- `" ca[j] "`"
111+
}
112+
print ""
113+
}
114+
'
115+
}

lib/functions/cli/commands.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ function armbian_register_commands() {
2121

2222
["requirements"]="requirements" # implemented in cli_requirements_pre_run and cli_requirements_run
2323

24+
# List the extension hook points available in the build framework, by statically scanning the sources.
25+
["show-extensions"]="show_extensions" # implemented in cli_show_extensions_pre_run and cli_show_extensions_run
26+
["show-hooks"]="show_extensions" # implemented in cli_show_extensions_pre_run and cli_show_extensions_run
27+
2428
# Given a board/config/exts, dump out the (non-userspace) JSON of configuration
2529
["configdump"]="config_dump_json" # implemented in cli_config_dump_json_pre_run and cli_config_dump_json_run
2630
["config-dump"]="config_dump_json" # implemented in cli_config_dump_json_pre_run and cli_config_dump_json_run

lib/library-functions.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,15 @@ set -o errexit ## set -e : exit the script if any statement returns a non-true
226226
# shellcheck source=lib/functions/cli/cli-requirements.sh
227227
source "${SRC}"/lib/functions/cli/cli-requirements.sh
228228

229+
# no errors tolerated. invoked before each sourced file to make sure.
230+
#set -o pipefail # trace ERR through pipes - will be enabled "soon"
231+
#set -o nounset ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled
232+
set -o errtrace # trace ERR through - enabled
233+
set -o errexit ## set -e : exit the script if any statement returns a non-true return value - enabled
234+
### lib/functions/cli/cli-show-extensions.sh
235+
# shellcheck source=lib/functions/cli/cli-show-extensions.sh
236+
source "${SRC}"/lib/functions/cli/cli-show-extensions.sh
237+
229238
# no errors tolerated. invoked before each sourced file to make sure.
230239
#set -o pipefail # trace ERR through pipes - will be enabled "soon"
231240
#set -o nounset ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled

0 commit comments

Comments
 (0)