#!/usr/bin/env bash
# scripts/quality/reproducible-build.sh
#
# Build the project twice with the same toolchain + flags + sources, and
# verify the two outputs are byte-identical (after stripping ABI noise).
#
# Why: conformallab++ is header-only, so the binaries we ship are just
# the test executables.  If the same source + same toolchain produces
# different bytes, something non-deterministic snuck in:
#   * a `__DATE__` / `__TIME__` macro in the code
#   * an absolute path leaked into a string literal
#   * iteration over an unordered container of items
#   * a parallel build with non-deterministic linking order
#
# Local-only.  Two full builds at ~3 min each ≈ 6 min wall time.
#
# Usage:
#   bash scripts/quality/reproducible-build.sh
#
# Exit codes:
#   0  the two builds produce byte-identical executables
#   1  there is at least one differing byte; prints which executable(s)
#   2  prerequisite missing

set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"

DIR_A="build-repro-A"
DIR_B="build-repro-B"

# Identical-input check: nuke any state from a previous run.
rm -rf "$DIR_A" "$DIR_B"

# Use a fixed timezone + SOURCE_DATE_EPOCH so any time-based macros
# yield identical strings in both builds.  Without this, even a perfect
# build pipeline disagrees if __TIME__ slips in.
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-1700000000}"
export TZ=UTC
export LC_ALL=C

echo "========================================"
echo "  Reproducible-build check"
echo "  build A:  $DIR_A"
echo "  build B:  $DIR_B"
echo "  SOURCE_DATE_EPOCH:  $SOURCE_DATE_EPOCH"
echo "========================================"
echo

build_once () {
    local dir="$1"
    cmake -S code -B "$dir" -DCMAKE_BUILD_TYPE=Release -Wno-dev >/dev/null
    # Single-threaded build → deterministic link order.
    cmake --build "$dir" --target conformallab_tests -j1 >"$dir/build.log" 2>&1
}

echo "── Build A ─────────────────────────────────"
build_once "$DIR_A"
echo "    done."

echo "── Build B ─────────────────────────────────"
build_once "$DIR_B"
echo "    done."

# ── Compare ─────────────────────────────────────────────────────────────────
# Test executables live at:
#   build-repro-*/conformallab_tests       (single combined fast test)
#   build-repro-*/test_*                  (older per-suite executables)
# Compare every regular file under each build dir that ends in
# `_tests` or starts with `test_`.

echo
echo "── Diff ────────────────────────────────────"
mismatches=""
for path_a in "$DIR_A"/conformallab_tests "$DIR_A"/test_*; do
    [ -f "$path_a" ] || continue
    rel="${path_a#${DIR_A}/}"
    path_b="$DIR_B/$rel"
    if [ ! -f "$path_b" ]; then
        echo "    MISS $rel  (only in build A)"
        mismatches="$mismatches  $rel (missing in build B)
"
        continue
    fi
    if cmp -s "$path_a" "$path_b"; then
        echo "    OK   $rel"
    else
        a_sha=$(shasum -a 256 "$path_a" | cut -d' ' -f1)
        b_sha=$(shasum -a 256 "$path_b" | cut -d' ' -f1)
        echo "    DIFF $rel"
        echo "         A  $a_sha"
        echo "         B  $b_sha"
        mismatches="$mismatches  $rel
"
    fi
done

echo
if [ -n "$mismatches" ]; then
    echo "FAIL: the build is not byte-reproducible."
    echo
    echo "Differing files:"
    printf "%s" "$mismatches"
    echo
    echo "Common causes:"
    echo "  * __DATE__ / __TIME__ macros baked into the binary"
    echo "  * absolute build path embedded in a debug-info string"
    echo "  * a parallel-link race (-j > 1) — but we already use -j1 here"
    echo "  * a header generated from a non-deterministic source"
    echo
    echo "Debug recipe:"
    echo "  diff <(strings $DIR_A/$rel) <(strings $DIR_B/$rel) | head -20"
    exit 1
fi

echo "OK: every test executable is byte-identical between the two builds."
exit 0
