#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" UPSTREAM_DIR="$ROOT_DIR/../linuxcnc" VENDOR_DIR="$ROOT_DIR/vendor/linuxcnc" MANIFEST_FILE="$ROOT_DIR/tools/source-manifest.txt" OVERLAY_MANIFEST_FILE="$ROOT_DIR/tools/vendor-overlay-manifest.tsv" BUILD_DIR="$ROOT_DIR/build/native" CACHE_KEY_FILE="$BUILD_DIR/vendor-sync.input.sha256" CACHE_OK_FILE="$BUILD_DIR/vendor-sync.ok" if [[ ! -d "$UPSTREAM_DIR" ]]; then echo "missing upstream directory: $UPSTREAM_DIR" >&2 exit 1 fi if [[ ! -d "$VENDOR_DIR" ]]; then echo "missing vendor directory: $VENDOR_DIR" >&2 exit 1 fi if [[ ! -f "$MANIFEST_FILE" ]]; then echo "missing manifest: $MANIFEST_FILE" >&2 exit 1 fi if [[ ! -f "$OVERLAY_MANIFEST_FILE" ]]; then echo "missing overlay manifest: $OVERLAY_MANIFEST_FILE" >&2 exit 1 fi validate_manifest_path() { local src_rel="$1" if [[ "$src_rel" = /* || "$src_rel" == */ || "$src_rel" == *"//"* ]]; then return 1 fi local component local -a path_components IFS=/ read -r -a path_components <<< "$src_rel" for component in "${path_components[@]}"; do if [[ -z "$component" || "$component" == "." || "$component" == ".." ]]; then return 1 fi done } mkdir -p "$BUILD_DIR" vendor_sync_fingerprint() { { git -C "$UPSTREAM_DIR" rev-parse HEAD sha256sum \ "$ROOT_DIR/tools/verify_vendor_sync.sh" \ "$MANIFEST_FILE" \ "$OVERLAY_MANIFEST_FILE" while IFS= read -r src_rel; do [[ -z "$src_rel" ]] && continue [[ "${src_rel:0:1}" == "#" ]] && continue printf '%s\0' "$UPSTREAM_DIR/$src_rel" done < "$MANIFEST_FILE" | xargs -0 -r sha256sum 2>/dev/null || true while IFS= read -r src_rel; do [[ -z "$src_rel" ]] && continue [[ "${src_rel:0:1}" == "#" ]] && continue printf '%s\0' "$VENDOR_DIR/$src_rel" done < "$MANIFEST_FILE" | xargs -0 -r sha256sum 2>/dev/null || true while IFS=$'\t' read -r src_rel _ _; do [[ -z "$src_rel" ]] && continue [[ "${src_rel:0:1}" == "#" ]] && continue printf '%s\0' "$VENDOR_DIR/$src_rel" done < "$OVERLAY_MANIFEST_FILE" | xargs -0 -r sha256sum 2>/dev/null || true find "$VENDOR_DIR" -type f -printf 'vendor-list:%P\n' | sort } | sha256sum | awk '{ print $1 }' } VENDOR_SYNC_FINGERPRINT="$(vendor_sync_fingerprint)" if [[ -f "$CACHE_KEY_FILE" && -f "$CACHE_OK_FILE" ]] && [[ "$(tr -d '[:space:]' < "$CACHE_KEY_FILE")" == "$VENDOR_SYNC_FINGERPRINT" ]] && [[ "$(tr -d '[:space:]' < "$CACHE_OK_FILE")" == "$VENDOR_SYNC_FINGERPRINT" ]]; then echo "vendor sync up to date" exit 0 fi TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT MANIFEST_CLEAN="$TMP_DIR/manifest.clean" MANIFEST_SORTED="$TMP_DIR/manifest.sorted" OVERLAY_CLEAN="$TMP_DIR/overlay.clean" OVERLAY_SORTED="$TMP_DIR/overlay.sorted" EXPECTED_VENDOR_SORTED="$TMP_DIR/expected-vendor.sorted" VENDOR_SORTED="$TMP_DIR/vendor.sorted" : > "$OVERLAY_CLEAN" while IFS= read -r src_rel; do [[ -z "$src_rel" ]] && continue [[ "${src_rel:0:1}" == "#" ]] && continue if ! validate_manifest_path "$src_rel"; then echo "invalid manifest path: $src_rel" >&2 exit 1 fi printf '%s\n' "$src_rel" >> "$MANIFEST_CLEAN" done < "$MANIFEST_FILE" if [[ ! -s "$MANIFEST_CLEAN" ]]; then echo "empty manifest: $MANIFEST_FILE" >&2 exit 1 fi sort "$MANIFEST_CLEAN" > "$MANIFEST_SORTED" if duplicate_entries="$(uniq -d "$MANIFEST_SORTED")" && [[ -n "$duplicate_entries" ]]; then echo "duplicate manifest entries:" >&2 printf '%s\n' "$duplicate_entries" >&2 exit 1 fi while IFS=$'\t' read -r src_rel expected_sha256 provenance extra; do [[ -z "$src_rel" ]] && continue [[ "${src_rel:0:1}" == "#" ]] && continue if ! validate_manifest_path "$src_rel"; then echo "invalid overlay manifest path: $src_rel" >&2 exit 1 fi if [[ ! "$expected_sha256" =~ ^[0-9a-f]{64}$ ]]; then echo "invalid overlay sha256 for: $src_rel" >&2 exit 1 fi if [[ -z "$provenance" || -n "${extra:-}" ]]; then echo "invalid overlay provenance row: $src_rel" >&2 exit 1 fi printf '%s\n' "$src_rel" >> "$OVERLAY_CLEAN" done < "$OVERLAY_MANIFEST_FILE" sort "$OVERLAY_CLEAN" > "$OVERLAY_SORTED" if duplicate_overlays="$(uniq -d "$OVERLAY_SORTED")" && [[ -n "$duplicate_overlays" ]]; then echo "duplicate overlay manifest entries:" >&2 printf '%s\n' "$duplicate_overlays" >&2 exit 1 fi if overlapping_entries="$(comm -12 "$MANIFEST_SORTED" "$OVERLAY_SORTED")" && [[ -n "$overlapping_entries" ]]; then echo "paths cannot be both upstream sources and generated overlays:" >&2 printf '%s\n' "$overlapping_entries" >&2 exit 1 fi sort -u "$MANIFEST_SORTED" "$OVERLAY_SORTED" > "$EXPECTED_VENDOR_SORTED" find "$VENDOR_DIR" -type f | sed "s#^$VENDOR_DIR/##" | sort > "$VENDOR_SORTED" if extra_vendor="$(comm -23 "$VENDOR_SORTED" "$EXPECTED_VENDOR_SORTED")" && [[ -n "$extra_vendor" ]]; then echo "vendor files not listed in manifest:" >&2 printf '%s\n' "$extra_vendor" >&2 exit 1 fi if missing_vendor="$(comm -13 "$VENDOR_SORTED" "$EXPECTED_VENDOR_SORTED")" && [[ -n "$missing_vendor" ]]; then echo "manifest files missing from vendor:" >&2 printf '%s\n' "$missing_vendor" >&2 exit 1 fi while IFS= read -r src_rel; do upstream_file="$UPSTREAM_DIR/$src_rel" vendor_file="$VENDOR_DIR/$src_rel" if [[ ! -f "$upstream_file" ]]; then echo "missing upstream file: $src_rel" >&2 exit 1 fi if ! cmp -s "$upstream_file" "$vendor_file"; then echo "vendor drift from upstream: $src_rel" >&2 exit 1 fi done < "$MANIFEST_SORTED" overlay_count=0 while IFS=$'\t' read -r src_rel expected_sha256 provenance extra; do [[ -z "$src_rel" ]] && continue [[ "${src_rel:0:1}" == "#" ]] && continue vendor_file="$VENDOR_DIR/$src_rel" actual_sha256="$(sha256sum "$vendor_file" | awk '{print $1}')" if [[ "$actual_sha256" != "$expected_sha256" ]]; then echo "generated overlay hash mismatch: $src_rel" >&2 exit 1 fi overlay_count=$((overlay_count + 1)) done < "$OVERLAY_MANIFEST_FILE" printf '%s\n' "$VENDOR_SYNC_FINGERPRINT" > "$CACHE_KEY_FILE" printf '%s\n' "$VENDOR_SYNC_FINGERPRINT" > "$CACHE_OK_FILE" echo "vendor generated overlay validation complete: $overlay_count" echo "vendor sync validation complete"