diff options
| author | Danilo M. <danix@danix.xyz> | 2026-07-09 20:05:47 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-07-09 20:05:47 +0200 |
| commit | 47d31bd1a42355451801253f3118343deeccd2f3 (patch) | |
| tree | 86187181c70127a50de76967d15d642e992234bd | |
| parent | 0dcf7880e53366c83d5d334b39d1d5d1ebb89417 (diff) | |
| download | mkhintfile-47d31bd1a42355451801253f3118343deeccd2f3.tar.gz mkhintfile-47d31bd1a42355451801253f3118343deeccd2f3.zip | |
feat: sha-mode bundled-dep reconcile (Job A)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rwxr-xr-x | mkhint | 80 | ||||
| -rwxr-xr-x | tests/mkhint_test.sh | 120 |
2 files changed, 197 insertions, 3 deletions
@@ -596,8 +596,84 @@ reconcile_bundle_deps() { esac } -# TEMPORARY stub for sha-mode; real body arrives in a later task. -reconcile_bundle_deps_sha() { echo " $1: sha-mode not yet implemented"; return 0; } +# reconcile_bundle_deps_sha <pkg> <hintfile> <ignored> <mode> +# mode = report | apply. Compares each manifest name=path submodule's upstream +# SHA (contents API) against the matching hint DOWNLOAD line's sha, rewrites + +# re-md5s drifted lines. Reports all deps ((current) for unchanged). Returns 2 in +# report mode when there are changes to apply, else 0. apply mode returns 0. +reconcile_bundle_deps_sha() { + local pkg="$1" hint="$2" _ignored="$3" mode="$4" + local rest="${BUNDLE_REST[$pkg]}" + local repo ver_tmpl + read -r repo ver_tmpl rest <<< "$rest" + local cur_ver; cur_ver=$(grep '^VERSION=' "$hint" | sed 's/VERSION="//;s/"$//') + local ref="${ver_tmpl//\{VERSION\}/$cur_ver}" + + local -a urls md5s + mapfile -t urls < <(parse_multiline_var "DOWNLOAD_x86_64" "$hint") + (( ${#urls[@]} == 0 )) && mapfile -t urls < <(parse_multiline_var "DOWNLOAD" "$hint") + mapfile -t md5s < <(parse_multiline_var "MD5SUM_x86_64" "$hint") + (( ${#md5s[@]} == 0 )) && mapfile -t md5s < <(parse_multiline_var "MD5SUM" "$hint") + + local -a new_urls=("${urls[@]}") + local -a changed_idx=() + local -a report_lines=() + local pair name path upstream_sha subrepo i + for pair in $rest; do + name="${pair%%=*}"; path="${pair#*=}" + local resp; resp=$(_fetch_submodule_sha "$repo" "$path" "$ref") || { + report_lines+=(" $name: submodule path not found at $ref"); continue; } + upstream_sha="${resp%%$'\t'*}"; subrepo="${resp#*$'\t'}" + local found=-1 hrepo + for (( i=0; i<${#urls[@]}; i++ )); do + hrepo=$(_url_repo "${urls[$i]}") + [[ "$hrepo" == "$subrepo" ]] && { found=$i; break; } + done + if (( found < 0 )); then + report_lines+=(" $name: no matching DOWNLOAD line (FYI)"); continue + fi + local cur_sha="" + [[ "${urls[$found]}" =~ /archive/([0-9a-f]{40}) ]] && cur_sha="${BASH_REMATCH[1]}" + if [[ "$cur_sha" == "$upstream_sha" ]]; then + report_lines+=(" $name $(printf %.7s "$cur_sha") (current)") + else + new_urls[$found]="${urls[$found]//$cur_sha/$upstream_sha}" + changed_idx+=("$found") + report_lines+=(" $name $(printf %.7s "$cur_sha") -> $(printf %.7s "$upstream_sha")") + fi + done + + if [[ "$mode" == report ]]; then + echo "" + echo "$pkg bundled deps (sha):" + printf '%s\n' "${report_lines[@]}" + (( ${#changed_idx[@]} > 0 )) && return 2 + return 0 + fi + + (( ${#changed_idx[@]} == 0 )) && return 0 + cp "$hint" "${hint}.bak" + 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]}" + fi + done + local var_dl var_md5 + if grep -q '^DOWNLOAD_x86_64=' "$hint"; then var_dl="DOWNLOAD_x86_64"; var_md5="MD5SUM_x86_64" + else var_dl="DOWNLOAD"; var_md5="MD5SUM"; fi + 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{$var=shift;$v=shift} s|^\Q$var\E="[^"]*(?:\\\n[^"]*)*"|$var."=\"".$v."\""|me' "$var_dl" "$new_dl" "$hint" + perl -i -0pe 'BEGIN{$var=shift;$v=shift} s|^\Q$var\E="[^"]*(?:\\\n[^"]*)*"|$var."=\"".$v."\""|me' "$var_md5" "$new_md5v" "$hint" + return 0 +} # _github_token — echo a GitHub token from nvchecker's keyfile, or nothing. # nvchecker.toml has `keyfile = "<path>"`; that file has `github = "<token>"`. diff --git a/tests/mkhint_test.sh b/tests/mkhint_test.sh index 6b01fb5..2df225a 100755 --- a/tests/mkhint_test.sh +++ b/tests/mkhint_test.sh @@ -286,6 +286,19 @@ run_mkhint_fn() { ' _ "$@" } +# run_sha_reconcile <pkg> <hint> <ignored> <mode> — source the patched mkhint, +# load the bundle manifests (so BUNDLE_REST/BUNDLE_MODE are populated), then call +# reconcile_bundle_deps_sha directly. Mirrors run_mkhint_fn but adds the manifest +# load the sha reconcile depends on. +run_sha_reconcile() { + patch_mkhint + MKHINT_NOMAIN=1 bash -c ' + source "'"$PATCHED_MKHINT"'" + load_bundle_manifests + reconcile_bundle_deps_sha "$@" + ' _ "$@" +} + # Mock wget — writes fake content, md5 will be deterministic. URLs containing # deps.txt instead serve a manifest fixture ($MOCK_BASE/manifest_fixture) if # present, so bundled-dep manifest tests can control fetch_manifest's input. @@ -309,7 +322,11 @@ elif [[ "\$url" == *deps.txt* ]]; then exit 1 # simulate manifest fetch failure when no fixture set elif [[ "\$url" == *api.github.com*contents* ]]; then echo "wget-args: \$all_args" >> "$MOCK_BASE/api.log" - if [[ -f "$MOCK_BASE/api_fixture" ]]; then + p="\${url#*/contents/}"; p="\${p%%\\?*}" + key="\$(basename "\$p")" + if [[ -f "$MOCK_BASE/api_\$key.json" ]]; then + cat "$MOCK_BASE/api_\$key.json" > "\$out" + elif [[ -f "$MOCK_BASE/api_fixture" ]]; then cat "$MOCK_BASE/api_fixture" > "\$out" else exit 1 @@ -2286,6 +2303,107 @@ assert_contains "subrepo lc" <(echo "$res") "openvinotoolkit/mlas" assert_contains "auth header sent" "$MOCK_BASE/api.log" "Authorization: Bearer FAKE_TEST_TOKEN_123" rm -f "$MOCK_BASE/api_fixture" +# ── T-SHA1/2: sha-mode reconcile — drift rewritten, current left alone ───────── +# openvino has two submodule deps: mlas (moved upstream) and onnx (unchanged). +emit_openvino_fixtures() { + cat > "$MOCK_HINT/openvino.hint" << 'EOF' +VERSION="2024.4.1" +ARCH="x86_64" +DOWNLOAD="UNSUPPORTED" +MD5SUM="" +DOWNLOAD_x86_64="https://github.com/openvinotoolkit/openvino/archive/2024.4.1/openvino-2024.4.1.tar.gz \ + https://github.com/openvinotoolkit/mlas/archive/d1bc25ec4660cddd87804fcf03b2411b5dfb2e94/mlas-d1bc25ec4660cddd87804fcf03b2411b5dfb2e94.tar.gz \ + https://github.com/onnx/onnx/archive/990217f043af7222348ca8f0301e17fa7b841781/onnx-990217f043af7222348ca8f0301e17fa7b841781.tar.gz" +MD5SUM_x86_64="aaa \ + bbb \ + ccc" +EOF + cat > "$MOCK_BASE/bundle-manifests" << 'EOF' +openvino sha github:openvinotoolkit/openvino {VERSION} mlas=src/plugins/intel_cpu/thirdparty/mlas onnx=thirdparty/onnx/onnx +EOF + cat > "$MOCK_BASE/api_mlas.json" << 'EOF' +{"type":"submodule","sha":"a3f9c0177b21ffffffffffffffffffffffffffff","submodule_git_url":"https://github.com/openvinotoolkit/mlas.git"} +EOF + cat > "$MOCK_BASE/api_onnx.json" << 'EOF' +{"type":"submodule","sha":"990217f043af7222348ca8f0301e17fa7b841781","submodule_git_url":"https://github.com/onnx/onnx.git"} +EOF +} +echo "" +echo "T-SHA1/2: sha-mode reconcile — drift rewritten (mlas), current left (onnx)" +emit_openvino_fixtures +run_sha_reconcile openvino "$MOCK_HINT/openvino.hint" "" apply > "$MOCK_BASE/sha1.out" 2>&1 || true +assert_contains "hint: mlas sha rewritten (path)" "$MOCK_HINT/openvino.hint" "mlas/archive/a3f9c0177b21" +assert_contains "hint: mlas sha rewritten (file)" "$MOCK_HINT/openvino.hint" "mlas-a3f9c0177b21" +assert_contains "hint: onnx sha unchanged" "$MOCK_HINT/openvino.hint" "onnx-990217f043af" +# report mode shows old->new and (current) +emit_openvino_fixtures +out=$(run_sha_reconcile openvino "$MOCK_HINT/openvino.hint" "" report 2>&1) || true +assert_contains "report mlas old->new" <(echo "$out") "d1bc25e -> a3f9c01" +assert_contains "report onnx current" <(echo "$out") "(current)" + +# ── T-SHA3: 404 on one submodule path is reported and skipped ────────────────── +echo "" +echo "T-SHA3: submodule path 404 — reported skipped, other dep still processed" +emit_openvino_fixtures +rm -f "$MOCK_BASE/api_mlas.json" # mlas now 404s (no per-path fixture, no fallback) +rm -f "$MOCK_BASE/api_fixture" +out=$(run_sha_reconcile openvino "$MOCK_HINT/openvino.hint" "" report 2>&1) || true +assert_contains "report mlas not found" <(echo "$out") "mlas: submodule path not found" +assert_contains "report onnx still current" <(echo "$out") "(current)" +emit_openvino_fixtures # restore + +# ── T-SHA5: two submodule paths sharing distinct basenames — no cross-contam ─── +echo "" +echo "T-SHA5: two same-name-different-owner deps take their own path's sha" +cat > "$MOCK_HINT/openvino.hint" << 'EOF' +VERSION="2024.4.1" +ARCH="x86_64" +DOWNLOAD="UNSUPPORTED" +MD5SUM="" +DOWNLOAD_x86_64="https://github.com/openvinotoolkit/openvino/archive/2024.4.1/openvino-2024.4.1.tar.gz \ + https://github.com/openvinotoolkit/oneDNN/archive/1111111111111111111111111111111111111111/oneDNN-1111111111111111111111111111111111111111.tar.gz \ + https://github.com/oneapi-src/oneDNN/archive/2222222222222222222222222222222222222222/oneDNN-2222222222222222222222222222222222222222.tar.gz" +MD5SUM_x86_64="aaa \ + bbb \ + ccc" +EOF +cat > "$MOCK_BASE/bundle-manifests" << 'EOF' +openvino sha github:openvinotoolkit/openvino {VERSION} onednn=src/plugins/intel_cpu/thirdparty/onednn onednn_gpu=src/plugins/intel_gpu/thirdparty/onednn_gpu +EOF +cat > "$MOCK_BASE/api_onednn.json" << 'EOF' +{"type":"submodule","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","submodule_git_url":"https://github.com/openvinotoolkit/oneDNN.git"} +EOF +cat > "$MOCK_BASE/api_onednn_gpu.json" << 'EOF' +{"type":"submodule","sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","submodule_git_url":"https://github.com/oneapi-src/oneDNN.git"} +EOF +run_sha_reconcile openvino "$MOCK_HINT/openvino.hint" "" apply > /dev/null 2>&1 || true +# openvinotoolkit/oneDNN line must carry onednn path's sha (aaaa...) +grep -q "openvinotoolkit/oneDNN/archive/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$MOCK_HINT/openvino.hint" \ + && grep -q "oneapi-src/oneDNN/archive/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "$MOCK_HINT/openvino.hint" \ + && { echo " PASS: each dep took its own path's sha"; (( PASS++ )); } \ + || { echo " FAIL: cross-contamination"; cat "$MOCK_HINT/openvino.hint"; (( FAIL++ )) || true; ERRORS+=("T-SHA5"); } +rm -f "$MOCK_BASE/api_onednn.json" "$MOCK_BASE/api_onednn_gpu.json" + +# ── T-SHA7: manifest dep matching no DOWNLOAD line — FYI, hint unchanged ──────── +echo "" +echo "T-SHA7: manifest dep with no matching hint line — FYI, no line added" +emit_openvino_fixtures +cat > "$MOCK_BASE/bundle-manifests" << 'EOF' +openvino sha github:openvinotoolkit/openvino {VERSION} mlas=src/plugins/intel_cpu/thirdparty/mlas onnx=thirdparty/onnx/onnx foo=some/path/foo +EOF +cat > "$MOCK_BASE/api_foo.json" << 'EOF' +{"type":"submodule","sha":"cccccccccccccccccccccccccccccccccccccccc","submodule_git_url":"https://github.com/nobody/foo.git"} +EOF +before=$(grep -c 'archive/' "$MOCK_HINT/openvino.hint") +out=$(run_sha_reconcile openvino "$MOCK_HINT/openvino.hint" "" report 2>&1) || true +after=$(grep -c 'archive/' "$MOCK_HINT/openvino.hint") +assert_contains "report foo no matching line" <(echo "$out") "foo: no matching DOWNLOAD line" +[[ "$before" == "$after" ]] \ + && { echo " PASS: hint DOWNLOAD line count unchanged"; (( PASS++ )); } \ + || { echo " FAIL: line count changed $before -> $after"; (( FAIL++ )) || true; ERRORS+=("T-SHA7 count"); } +rm -f "$MOCK_BASE/api_foo.json" "$MOCK_HINT/openvino.hint" +# T-SHA4 (--new prints reconcile report) deferred: needs --new wiring (later task). + # ─── SUMMARY ────────────────────────────────────────────────────────────────── teardown |
