Merge claude/2

This commit is contained in:
librelad 2026-05-26 21:30:36 +01:00
commit f5391fe807
3 changed files with 1320 additions and 59 deletions

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,9 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" && "$1" == "run" ]]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ARRAYS_DIR="$SCRIPT_DIR/arrays"
SCRIPTS_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
SCRIPTS_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" # …/scripts
INSTALL_DIR="$(dirname "$SCRIPTS_DIR")" # …/install (parent of scripts)
CONTAINERS_DIR="$INSTALL_DIR/containers" # sibling of scripts
OUTPUT="$ARRAYS_DIR/function_manifest.sh"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'
@ -163,32 +165,66 @@ declare -a eager_files
total_files=0
total_fns=0
# Find all .sh under scripts/ (no symlinks, no hidden).
while IFS= read -r -d '' file; do
rel=$(realpath --relative-to="$SCRIPTS_DIR" "$file")
should_skip "$rel" && continue
# fn_to_root parallels fn_to_file: tracks whether the source file lives under
# scripts/ ("scripts") or containers/ ("containers") so the stub emission can
# pick the right ${install_*_dir} prefix. Same for eager_root for LP_EAGER_FILES.
declare -A fn_to_root
declare -A eager_root
total_files=$((total_files + 1))
# Walk one root. $1 = directory, $2 = root label ("scripts" or "containers"),
# $3 = max depth for find (-maxdepth N). The scan also honours the skip-list
# (paths relative to the root, so scripts/ skip entries don't match container
# files and vice versa).
scan_root() {
local root_dir="$1" root_label="$2" depth="$3"
while IFS= read -r -d '' file; do
local rel
rel=$(realpath --relative-to="$root_dir" "$file")
# Skip-list keys are scoped per root. should_skip() only handles the
# scripts/ ones today; containers/ skip-list is inline here.
if [[ "$root_label" == "scripts" ]]; then
should_skip "$rel" && continue
else
# Container-side skips: anything under a resources/ subdir is data
# not code; the existing sourceScanFiles already prunes those.
case "$rel" in
*/resources/*) continue ;;
esac
fi
is_eager=0
while IFS= read -r tag; do
case "$tag" in
fn:*)
name="${tag#fn:}"
if [[ -n "${fn_to_file[$name]:-}" && "${fn_to_file[$name]}" != "$rel" ]]; then
fn_collisions[$name]="${fn_collisions[$name]:-${fn_to_file[$name]}}"$'\t'"$rel"
fi
fn_to_file[$name]="$rel"
total_fns=$((total_fns + 1))
;;
eager:)
is_eager=1
;;
esac
done < <(analyze_file "$file")
total_files=$((total_files + 1))
(( is_eager )) && eager_files+=("$rel")
done < <(find "$SCRIPTS_DIR" -type f -name '*.sh' -print0)
local is_eager=0
while IFS= read -r tag; do
case "$tag" in
fn:*)
local name="${tag#fn:}"
if [[ -n "${fn_to_file[$name]:-}" && "${fn_to_file[$name]}" != "$rel" ]]; then
fn_collisions[$name]="${fn_collisions[$name]:-${fn_to_file[$name]}}"$'\t'"$rel"
fi
fn_to_file[$name]="$rel"
fn_to_root[$name]="$root_label"
total_fns=$((total_fns + 1))
;;
eager:)
is_eager=1
;;
esac
done < <(analyze_file "$file")
if (( is_eager )); then
eager_files+=("$rel")
eager_root["$rel"]="$root_label"
fi
done < <(find "$root_dir" -maxdepth "$depth" -type f -name '*.sh' -print0)
}
# scripts/ — deep walk, the existing behaviour.
scan_root "$SCRIPTS_DIR" scripts 99
# containers/ — match the sourceScanFiles "containers" maxdepth of 3.
if [[ -d "$CONTAINERS_DIR" ]]; then
scan_root "$CONTAINERS_DIR" containers 3
fi
# Emit the manifest.
{
@ -200,33 +236,48 @@ done < <(find "$SCRIPTS_DIR" -type f -name '*.sh' -print0)
printf '# Function name → relative path. Used by the lazy loader (LP_LAZY=1)\n'
printf '# to install an autoload stub for each public function. First call to a\n'
printf '# stub sources the real file, which redefines the function with the real\n'
printf '# body; subsequent calls hit the real one directly.\n'
printf '# body; subsequent calls hit the real one directly. Path is relative to\n'
printf '# install_scripts_dir for "scripts" entries, install_containers_dir for\n'
printf '# "containers" entries — see LP_FN_ROOT below.\n'
printf 'declare -gA LP_FN_MAP=(\n'
# Sort for stable diff output.
while IFS= read -r name; do
printf ' [%s]="%s"\n' "$name" "${fn_to_file[$name]}"
done < <(printf '%s\n' "${!fn_to_file[@]}" | sort)
printf ')\n\n'
printf '# Per-function root selector — "scripts" or "containers". Mirrors\n'
printf '# LP_FN_MAP; used by the loader / debugging tools.\n'
printf 'declare -gA LP_FN_ROOT=(\n'
while IFS= read -r name; do
printf ' [%s]="%s"\n' "$name" "${fn_to_root[$name]}"
done < <(printf '%s\n' "${!fn_to_root[@]}" | sort)
printf ')\n\n'
printf '# Files with top-level side effects (variable assignments, source calls,\n'
printf '# command invocations outside any function). Lazy mode MUST source these\n'
printf '# Files with top-level side effects. Lazy mode MUST source these\n'
printf '# unconditionally — deferring them would skip the side effect, not just\n'
printf '# defer a function definition.\n'
printf '# defer a function definition. Stored as "<root>:<relpath>" so the\n'
printf '# loader picks the right base dir; existing entries without a prefix\n'
printf '# (pre-Phase-5 manifests) default to scripts/.\n'
printf 'LP_EAGER_FILES=(\n'
while IFS= read -r f; do
printf ' "%s"\n' "$f"
# No `local` here — we're inside a `{ … } > FILE` command group, not
# a function. `local` errors out at this scope in bash.
root="${eager_root[$f]:-scripts}"
printf ' "%s:%s"\n' "$root" "$f"
done < <(printf '%s\n' "${eager_files[@]}" | sort -u)
printf ')\n\n'
printf '# Autoload stubs — one per public function. First call sources the\n'
printf '# real file (which redefines this stub with the real body), then\n'
printf '# re-invokes. Sourced inline instead of eval-in-loop because bash\n'
printf '# parses one large file faster than it evals 700 small snippets.\n'
printf '# Only emitted when the manifest is read; behaviour-neutral when the\n'
printf '# loader does not flip into LP_LAZY=1 mode.\n'
printf '# parses one large file faster than it evals snippets at startup.\n'
while IFS= read -r name; do
printf '%s() { source "${install_scripts_dir}%s"; %s "$@"; }\n' \
"$name" "${fn_to_file[$name]}" "$name"
root="${fn_to_root[$name]}"
case "$root" in
containers) base_var='install_containers_dir' ;;
*) base_var='install_scripts_dir' ;;
esac
printf '%s() { source "${%s}%s"; %s "$@"; }\n' \
"$name" "$base_var" "${fn_to_file[$name]}" "$name"
done < <(printf '%s\n' "${!fn_to_file[@]}" | sort)
} > "$OUTPUT"

View File

@ -70,20 +70,32 @@ sourceInitilize()
else
source "$manifest"
fi
# Eager-source the side-effect files. These define vars or run
# commands at top level; lazy stubs would skip those side effects.
local _eager
# Eager-source the side-effect files. Entries are `<root>:<relpath>`
# where root selects the base dir ("scripts" → install_scripts_dir,
# "containers" → install_containers_dir). Pre-Phase-5 manifests
# without a `:` default to scripts/.
local _eager _root _path _base
for _eager in "${LP_EAGER_FILES[@]}"; do
[[ -f "${install_scripts_dir}${_eager}" ]] || continue
if [[ "$_eager" == *:* ]]; then
_root="${_eager%%:*}"
_path="${_eager#*:}"
else
_root="scripts"; _path="$_eager"
fi
case "$_root" in
containers) _base="$install_containers_dir" ;;
*) _base="$install_scripts_dir" ;;
esac
[[ -f "${_base}${_path}" ]] || continue
if [[ "$LP_LOAD_TRACE" == "1" ]]; then
local _t0=$EPOCHREALTIME
source "${install_scripts_dir}${_eager}"
source "${_base}${_path}"
local _t1=$EPOCHREALTIME
local _ms
_ms=$(awk -v a="$_t0" -v b="$_t1" 'BEGIN{printf "%.3f", (b-a)*1000}')
printf '%s\t%s\n' "$_ms" "${_eager} (LAZY-EAGER)" >> "$LP_LOAD_TRACE_FILE"
printf '%s\t%s\n' "$_ms" "${_root}/${_path} (LAZY-EAGER)" >> "$LP_LOAD_TRACE_FILE"
else
source "${install_scripts_dir}${_eager}"
source "${_base}${_path}"
fi
done
else
@ -115,8 +127,21 @@ sourceInitilize()
done
fi
# Loading of all files
# Loading of all files. Three scans:
# libreportal_configs CFG_* vars from configs/* (~180 ms)
# app_configs CFG_<APP>_* vars from containers/* (~37 ms)
# containers Per-app installer functions (~70 ms)
#
# Under LP_LAZY=1 with a present manifest, the container scan is replaced
# by autoload stubs in function_manifest.sh — sourcing every container
# installer eagerly would defeat the lazy optimisation. The two config
# scans still run because bash can't lazy-load variables (Phase 6 will
# tackle this with a precompiled cache file).
sourceScanFiles "libreportal_configs";
sourceScanFiles "app_configs";
sourceScanFiles "containers";
if [[ "$LP_LAZY" == "1" ]] && declare -p LP_FN_MAP >/dev/null 2>&1; then
: # containers covered by autoload stubs; skip the eager scan
else
sourceScanFiles "containers";
fi
}