aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-07 18:50:33 +0200
committerDanilo M. <danix@danix.xyz>2026-07-07 18:50:33 +0200
commit4dfd3d7bc3ffe48f9483b4c5667d1f0bbc0f1325 (patch)
tree395c167bc9d7d05b4404e968f48e691af4e969f6
parent84904cb9dba93b7466e7847efdf6dfa48541a31a (diff)
downloadmkhintfile-4dfd3d7bc3ffe48f9483b4c5667d1f0bbc0f1325.tar.gz
mkhintfile-4dfd3d7bc3ffe48f9483b4c5667d1f0bbc0f1325.zip
feat: reconcile_bundle_deps core (report/apply)
Match each extra DOWNLOAD line to the manifest, rewrite changed lines and recompute their md5 in apply mode, report only in report mode, and print the manifest-only-deps FYI. Primary line untouched. Also guard the main "$@" call at the bottom of mkhint behind a BASH_SOURCE check so tests can source the whole script (needed to reach parse_multiline_var/build_multiline_value/download_file alongside the new function) without triggering the no-args CLI path, which previously called exit 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rwxr-xr-xmkhint108
-rwxr-xr-xtests/mkhint_test.sh42
2 files changed, 149 insertions, 1 deletions
diff --git a/mkhint b/mkhint
index 544bdee..d064a64 100755
--- a/mkhint
+++ b/mkhint
@@ -525,6 +525,107 @@ fetch_manifest() {
[[ -s "$out" ]] || return 1
printf '%s\n' "$out"
}
+
+# reconcile_bundle_deps <pkg> <hintfile> <manifest_url> <mode>
+# mode = report (print only) | apply (rewrite matched changed lines + md5).
+# Reconciles the DOWNLOAD line's extra URLs (index >= 1) against the manifest.
+# The primary URL (index 0) is never touched here. Guarded for set -e.
+reconcile_bundle_deps() {
+ local pkg="$1" hint="$2" murl="$3" mode="$4"
+
+ # Display label for a dep URL: _url_stem is basename-only, so for a github
+ # "archive/vX.Y.Z.tar.gz" tarball (no repo name in the filename) it just
+ # returns the bare version — fall back to the repo name in that case.
+ local _dep_label
+ _dep_label() {
+ local s; s=$(_url_stem "$1")
+ if [[ "$s" =~ ^[vV]?[0-9] ]]; then
+ local r; r=$(_url_repo "$1")
+ [[ -n "$r" ]] && { printf '%s\n' "${r#*/}"; return; }
+ fi
+ printf '%s\n' "$s"
+ }
+
+ local mfile
+ if ! mfile=$(fetch_manifest "$murl"); then
+ echo " $pkg: manifest unavailable ($murl) — bundled deps left as-is (retry with --force)"
+ return 0
+ fi
+ local -a manifest_urls
+ mapfile -t manifest_urls < <(parse_manifest "$mfile")
+ rm -f "$mfile"
+ if [[ ${#manifest_urls[@]} -eq 0 ]]; then
+ echo " $pkg: manifest empty/unrecognized — bundled deps left as-is"
+ return 0
+ fi
+ local mlist; printf -v mlist '%s\n' "${manifest_urls[@]}"
+
+ local -a urls
+ mapfile -t urls < <(parse_multiline_var "DOWNLOAD" "$hint")
+ (( ${#urls[@]} <= 1 )) && { echo " $pkg: no bundled deps in DOWNLOAD"; return 0; }
+ local -a md5s
+ mapfile -t md5s < <(parse_multiline_var "MD5SUM" "$hint")
+
+ # Track which manifest URLs got matched (for the FYI of unmatched ones).
+ local -A matched_manifest=()
+ local -a new_urls=("${urls[@]}")
+ local -a changed_idx=() changed_from=() changed_to=()
+ local i m
+ for (( i=1; i<${#urls[@]}; i++ )); do
+ m=$(match_dep_url "${urls[$i]}" "$mlist") || m=""
+ if [[ -z "$m" ]]; then
+ echo " $pkg: $(_dep_label "${urls[$i]}") — no manifest match (left as-is)"
+ continue
+ fi
+ matched_manifest["$m"]=1
+ if [[ "$m" != "${urls[$i]}" ]]; then
+ changed_idx+=("$i"); changed_from+=("${urls[$i]}"); changed_to+=("$m")
+ new_urls[$i]="$m"
+ fi
+ done
+
+ # Manifest-only deps FYI (manifest URLs never matched by any hint line).
+ local -a extra=()
+ for m in "${manifest_urls[@]}"; do
+ [[ -z "${matched_manifest[$m]:-}" ]] && extra+=("$(_dep_label "$m")")
+ done
+ if [[ ${#extra[@]} -gt 0 ]]; then
+ echo "$pkg: manifest has ${#extra[@]} deps not in hint:"
+ printf '%s\n' "${extra[@]}"
+ fi
+
+ if [[ ${#changed_idx[@]} -eq 0 ]]; then
+ echo " $pkg: bundled deps all current"
+ return 0
+ fi
+
+ echo ""
+ echo "$pkg bundled deps changed upstream:"
+ for (( i=0; i<${#changed_idx[@]}; i++ )); do
+ printf ' %s -> %s\n' "$(_dep_label "${changed_from[$i]}")" "$(_dep_label "${changed_to[$i]}")"
+ done
+
+ [[ "$mode" == report ]] && return 0
+
+ # apply mode: recompute md5 only for changed lines, rewrite DOWNLOAD+MD5SUM.
+ local -a new_md5s=("${md5s[@]}")
+ local idx md5
+ for idx in "${changed_idx[@]}"; do
+ echo "Downloading (bundled): ${new_urls[$idx]}"
+ if md5=$(download_file "${new_urls[$idx]}"); then
+ new_md5s[$idx]="$md5"
+ else
+ echo " download failed for ${new_urls[$idx]} — left as-is"
+ new_urls[$idx]="${urls[$idx]}" # revert this line
+ fi
+ done
+
+ local new_dl new_md5v
+ new_dl=$(build_multiline_value new_urls); new_dl="${new_dl#\"}"; new_dl="${new_dl%\"}"
+ new_md5v=$(build_multiline_value new_md5s); new_md5v="${new_md5v#\"}"; new_md5v="${new_md5v%\"}"
+ perl -i -0pe 'BEGIN{$v=shift} s|^DOWNLOAD="[^"]*(?:\\\n[^"]*)*"|DOWNLOAD="$v"|m' "$new_dl" "$hint"
+ perl -i -0pe 'BEGIN{$v=shift} s|^MD5SUM="[^"]*(?:\\\n[^"]*)*"|MD5SUM="$v"|m' "$new_md5v" "$hint"
+}
# ── end bundled-dep manifest handling ─────────────────────────────────────────
# Create new hint file
@@ -1313,4 +1414,9 @@ main() {
esac
}
-main "$@"
+# Only run main when executed directly; allow tests to source this file to
+# reach individual functions without triggering the CLI (and its `exit 1`
+# for a no-args invocation).
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+ main "$@"
+fi
diff --git a/tests/mkhint_test.sh b/tests/mkhint_test.sh
index e9e50ac..f736de1 100755
--- a/tests/mkhint_test.sh
+++ b/tests/mkhint_test.sh
@@ -1552,6 +1552,48 @@ mpath=$(bash -c "TMP_DIR='$MOCK_TMP'; PATH=\"$MOCK_BASE/bin:\$PATH\"; $BM_SRC; f
|| { echo " FAIL: fetch_manifest path='$mpath'"; (( FAIL++ )); ERRORS+=("T-BM7"); }
rm -f "$MOCK_BASE/manifest_fixture"
+# ── T-BM8: reconcile report mode leaves the hint unchanged ───────────────────
+echo ""
+echo "T-BM8: reconcile_bundle_deps report mode: prints, does not rewrite"
+cat > "$MOCK_BASE/manifest_fixture" << 'EOF'
+NVIM_URL https://github.com/neovim/neovim/archive/v0.13.0.tar.gz
+NVIM_SHA256 a
+TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.26.8.tar.gz
+TREESITTER_SHA256 b
+WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v36.0.6.tar.gz
+WASMTIME_SHA256 c
+EOF
+cat > "$MOCK_HINT/nvim.hint" << 'EOF'
+VERSION="0.13.0"
+DOWNLOAD="https://github.com/neovim/neovim/archive/v0.13.0/neovim-0.13.0.tar.gz \
+ https://github.com/tree-sitter/tree-sitter/archive/v0.26.7/tree-sitter-0.26.7.tar.gz"
+MD5SUM="aaa \
+ bbb"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+ARCH="x86_64"
+EOF
+cp "$MOCK_HINT/nvim.hint" "$MOCK_BASE/nvim.before"
+BM_SRC='source <(sed -n "/# ── bundled-dep manifest handling/,/# ── end bundled-dep/p" '"$SCRIPT"')'
+rep=$(bash -c "TMP_DIR='$MOCK_TMP'; PATH=\"$MOCK_BASE/bin:\$PATH\"; source '$SCRIPT' 2>/dev/null; reconcile_bundle_deps nvim '$MOCK_HINT/nvim.hint' 'https://x/deps.txt' report" 2>&1) || true
+echo "$rep" | grep -q 'tree-sitter' \
+ && echo "$rep" | grep -q 'wasmtime' \
+ && diff -q "$MOCK_HINT/nvim.hint" "$MOCK_BASE/nvim.before" >/dev/null \
+ && { echo " PASS: report mode no rewrite"; (( PASS++ )); } \
+ || { echo " FAIL: report mode. out=$rep"; (( FAIL++ )); ERRORS+=("T-BM8"); }
+
+# ── T-BM9: reconcile apply mode rewrites the changed dep + recomputes md5 ─────
+echo ""
+echo "T-BM9: reconcile_bundle_deps apply mode rewrites tree-sitter to v0.26.8"
+bash -c "TMP_DIR='$MOCK_TMP'; PATH=\"$MOCK_BASE/bin:\$PATH\"; source '$SCRIPT' 2>/dev/null; reconcile_bundle_deps nvim '$MOCK_HINT/nvim.hint' 'https://x/deps.txt' apply" >/dev/null 2>&1 || true
+grep -q 'tree-sitter/archive/v0.26.8' "$MOCK_HINT/nvim.hint" \
+ && ! grep -q 'v0.26.7' "$MOCK_HINT/nvim.hint" \
+ && ! grep -q 'bbb' "$MOCK_HINT/nvim.hint" \
+ && grep -q 'neovim-0.13.0' "$MOCK_HINT/nvim.hint" \
+ && { echo " PASS: apply rewrote dep + md5, primary line untouched"; (( PASS++ )); } \
+ || { echo " FAIL: apply mode:"; cat "$MOCK_HINT/nvim.hint"; (( FAIL++ )); ERRORS+=("T-BM9"); }
+rm -f "$MOCK_BASE/manifest_fixture"
+
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
teardown