|
| 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 | +} |
0 commit comments