Files
wehub-resource-sync 9f2e1cd2af
Ruby / Ruby 4.0.5 (push) Failing after 5m13s
Ruby / Ruby 3.3.4 (push) Failing after 5m43s
Ruby / Ruby 2.6.8 (push) Failing after 16m2s
chore: import upstream snapshot with attribution
2026-07-13 12:33:07 +08:00

330 lines
11 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# build.sh — compile .sh.cc templates into standalone shell scripts
#
# Usage:
# bash scripts/build/build.sh # build all, output to scripts/
# bash scripts/build/build.sh install.sh # build one specific output file
# bash scripts/build/build.sh --diff # build to tmp/, diff against git HEAD
#
# Template syntax (inside .sh.cc files):
# @include lib/foo.sh — inline the contents of scripts/build/lib/foo.sh
# (strips comment header lines starting with "# ")
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SRC_DIR="$SCRIPT_DIR/src"
LIB_DIR="$SCRIPT_DIR" # @include paths relative to build/
OUT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" # scripts/
TMP_DIR="$OUT_DIR/tmp" # scripts/tmp/ (diff staging area)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${BLUE}${NC} $1"; }
success() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; }
error() { echo -e "${RED}${NC} $1"; }
section() { echo -e "\n${CYAN}${NC} $1"; }
# --------------------------------------------------------------------------
# Dependency checker — validates @include order in .sh.cc files
#
# Each lib declares in its header:
# # Depends-On: colors.sh os.sh (requires these libs to be included first)
# # Requires-Vars: $FOO $BAR (requires these vars set by prior libs)
# # Sets-Vars: $FOO $BAR (vars this lib sets, for downstream checks)
#
# check_include_deps <lib_path> <included_libs_so_far...>
# Called each time an @include is processed. Exits 1 if deps are violated.
# --------------------------------------------------------------------------
# Parse "# Key: val1 val2 ..." from a lib header, print space-separated values
_parse_header_field() {
local lib_file="$1"
local field="$2" # e.g. "Depends-On" or "Requires-Vars" or "Sets-Vars"
local raw
raw=$(grep -m1 "^# ${field}:" "$lib_file" 2>/dev/null | sed "s/^# ${field}:[[:space:]]*//" | tr -d '\r')
# "(none)" means empty
[ "$raw" = "(none)" ] && echo "" || echo "$raw"
}
# Build a set of all vars set by the listed libs
_vars_set_by_libs() {
local lib_dir="$1"; shift
local libs=("$@")
for lib in "${libs[@]}"; do
# libs are stored as "lib/foo.sh" — resolve relative to LIB_DIR parent
local lib_file="$lib_dir/$lib"
[ -f "$lib_file" ] || continue
_parse_header_field "$lib_file" "Sets-Vars"
done
}
check_include_deps() {
local lib_path="$1"; shift # e.g. "lib/network.sh"
local included=("$@") # libs included so far (e.g. "lib/colors.sh" "lib/os.sh")
local lib_file="$LIB_DIR/$lib_path"
[ -f "$lib_file" ] || return 0 # missing file caught by resolve_include
local lib_name; lib_name=$(basename "$lib_path")
local ok=true
# --- Check Depends-On ---
local depends_on
depends_on=$(_parse_header_field "$lib_file" "Depends-On")
for dep in $depends_on; do
local dep_path="lib/$dep"
local found=false
for inc in "${included[@]}"; do
[ "$inc" = "$dep_path" ] && found=true && break
done
if [ "$found" = false ]; then
error "[$lib_name] Depends-On '$dep' but it was not @included before this line"
ok=false
fi
done
# --- Check Requires-Vars ---
local requires_vars
requires_vars=$(_parse_header_field "$lib_file" "Requires-Vars")
# Collect all vars set by already-included libs
local sets_raw
sets_raw=$(_vars_set_by_libs "$LIB_DIR" "${included[@]}")
local sets_list
sets_list=$(echo "$sets_raw" | tr ' ' '\n' | grep '^\$' | sort -u)
for var in $requires_vars; do
[ "$var" = "(none)" ] && continue
if ! echo "$sets_list" | grep -qxF "$var"; then
error "[$lib_name] Requires-Vars '$var' but no prior @include Sets it"
ok=false
fi
done
[ "$ok" = true ] || exit 1
}
resolve_include() {
local lib_file="$LIB_DIR/$1"
if [ ! -f "$lib_file" ]; then
error "Include not found: $1 (looked in $LIB_DIR)"
exit 1
fi
local past_header=false
while IFS= read -r line || [ -n "$line" ]; do
if [ "$past_header" = false ]; then
local is_comment=""
[[ "$line" =~ ^#[[:space:]] ]] && is_comment=1 || true
[ "$line" = "#" ] && is_comment=1 || true
if [ -n "$is_comment" ]; then
continue
else
past_header=true
fi
fi
echo "$line"
done < "$lib_file"
}
# --------------------------------------------------------------------------
# render_template <src_file> <out_file>
# --------------------------------------------------------------------------
render_template() {
local src="$1"
local out="$2"
local tmp_file="${out}.tmp"
local included_libs=() # track @include order for dep checking
: > "$tmp_file"
while IFS= read -r line || [ -n "$line" ]; do
local include_path=""
[[ "$line" =~ ^@include[[:space:]]+(.+)$ ]] && include_path="${BASH_REMATCH[1]}" || true
if [ -n "$include_path" ]; then
check_include_deps "$include_path" "${included_libs[@]}"
included_libs+=("$include_path")
echo "" >> "$tmp_file"
echo "# ---[ @include ${include_path} ]---" >> "$tmp_file"
resolve_include "$include_path" >> "$tmp_file"
echo "" >> "$tmp_file"
else
echo "$line" >> "$tmp_file"
fi
done < "$src"
mv "$tmp_file" "$out"
chmod +x "$out"
}
# --------------------------------------------------------------------------
# build_one <src_cc_file> <output_dir>
# --------------------------------------------------------------------------
build_one() {
local src="$1"
local out_dir="$2"
local name out
name=$(basename "$src" .cc) # e.g. install.sh
out="$out_dir/$name"
info "Building $name ..."
render_template "$src" "$out"
success "→ $out"
}
# --------------------------------------------------------------------------
# cmd_build — normal build, output to scripts/
# --------------------------------------------------------------------------
cmd_build() {
local targets=("$@")
echo ""
echo "╔═══════════════════════════════════════════════╗"
echo "║ 🔨 Shell Script Builder (.sh.cc → .sh) ║"
echo "╚═══════════════════════════════════════════════╝"
echo ""
if [ ${#targets[@]} -gt 0 ]; then
for target in "${targets[@]}"; do
local src="$SRC_DIR/${target}.cc"
[ -f "$src" ] || { error "Source not found: $src"; exit 1; }
build_one "$src" "$OUT_DIR"
done
else
local found=0
for src in "$SRC_DIR"/*.sh.cc; do
[ -f "$src" ] || continue
build_one "$src" "$OUT_DIR"
found=$((found + 1))
done
[ "$found" -eq 0 ] && warn "No .sh.cc files found in $SRC_DIR"
fi
echo ""
success "Build complete. Output → $OUT_DIR"
echo ""
}
# --------------------------------------------------------------------------
# cmd_diff — build to tmp/, diff each file against git HEAD version
# --------------------------------------------------------------------------
cmd_diff() {
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 🔍 Diff: build output vs git HEAD (scripts/*.sh) ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
# Require git
command -v git >/dev/null 2>&1 || { error "git not found"; exit 1; }
# Find repo root (scripts/ is one level above build/)
local repo_root
repo_root=$(cd "$OUT_DIR" && git rev-parse --show-toplevel 2>/dev/null) || {
error "Not inside a git repository"
exit 1
}
# Build into tmp/
mkdir -p "$TMP_DIR"
info "Building all templates into tmp/ ..."
echo ""
for src in "$SRC_DIR"/*.sh.cc; do
[ -f "$src" ] || continue
build_one "$src" "$TMP_DIR"
done
# Diff each generated file against git HEAD
echo ""
section "Comparing tmp/ output against git HEAD ..."
echo ""
local any_diff=false
for new_file in "$TMP_DIR"/*.sh; do
[ -f "$new_file" ] || continue
local name; name=$(basename "$new_file")
local rel_path="scripts/$name"
# Get HEAD version from git
local head_content
head_content=$(cd "$repo_root" && git show "HEAD:${rel_path}" 2>/dev/null) || {
warn "$name — not found in git HEAD (new file)"
continue
}
# Write HEAD version to a temp file for diff
local head_file="$TMP_DIR/${name}.HEAD"
echo "$head_content" > "$head_file"
# Run diff
local diff_output
diff_output=$(diff -u "$head_file" "$new_file" 2>/dev/null) || true
if [ -z "$diff_output" ]; then
success "$name — identical to HEAD"
else
any_diff=true
echo -e "${YELLOW}${NC} ${YELLOW}$name — has differences:${NC}"
echo ""
# Colorize diff output: + lines green, - lines red, @@ cyan
echo "$diff_output" | while IFS= read -r dline; do
case "$dline" in
"+++"*|"---"*) echo -e "${BLUE}${dline}${NC}" ;;
"@@"*) echo -e "${CYAN}${dline}${NC}" ;;
"+"*) echo -e "${GREEN}${dline}${NC}" ;;
"-"*) echo -e "${RED}${dline}${NC}" ;;
*) echo "$dline" ;;
esac
done
echo ""
fi
rm -f "$head_file"
done
# Clean up tmp/
rm -rf "$TMP_DIR"
echo ""
if [ "$any_diff" = true ]; then
warn "Some files differ from git HEAD — review the diff above."
else
success "All outputs are identical to git HEAD."
fi
echo ""
}
# --------------------------------------------------------------------------
# Main — dispatch on first argument
# --------------------------------------------------------------------------
main() {
case "${1:-}" in
--diff)
cmd_diff
;;
--help|-h)
echo "Usage:"
echo " bash build.sh # build all .sh.cc → scripts/"
echo " bash build.sh install.sh # build one file"
echo " bash build.sh --diff # build to tmp/, diff vs git HEAD"
;;
*)
cmd_build "$@"
;;
esac
}
main "$@"