#!/bin/bash
# RUSTC_WRAPPER that records a fingerprint of every crate rustc produces.
# Diffing the two logs of two rbk builds names the *first* crate that differs,
# which is the rust equivalent of autoprovenance and much cheaper than
# bisecting with rebuilds.
#
# usage inside a build:
#   export RUSTCWRAP_LOG=/home/abuild/rustcwrap   # must survive the build
#   export RUSTC_WRAPPER=/path/to/rustcwrap
#   <normal build>
# then, after both builds:
#   diff -ru buildA/rustcwrap buildB/rustcwrap
#
# The log is one file per crate so that parallel rustc calls cannot
# interleave. Each file holds:
#   metadata <-C metadata value>       - changes if inputs/flags/env changed
#   flags    <normalized rustc flags>
#   <sha256> <output file basename>    - one line per emitted artifact
#
# Rule of thumb when reading the diff:
#   only 'metadata' differs      -> an env var or path reached rustc, look at
#                                   the build script output of that crate
#   only some sha256 differ      -> that crate's own codegen is unstable
#   the first differing crate in dependency order is the culprit; everything
#   downstream of it differs as a consequence.

rustc="$1" ; shift
: ${RUSTCWRAP_LOG:=/tmp/rustcwrap}

crate= outdir= extra= meta=
prev=
for a in "$@" ; do
    case "$prev" in
        --crate-name) crate="$a" ;;
        --out-dir) outdir="$a" ;;
    esac
    case "$a" in
        metadata=*) meta="${a#metadata=}" ;;
        extra-filename=*) extra="${a#extra-filename=}" ;;
    esac
    prev="$a"
done

"$rustc" "$@"
ret=$?

record=1
# --print=cfg and friends have no output dir and nothing to fingerprint
[ $ret = 0 ] && [ -n "$crate" ] && [ -n "$outdir" ] && [ -d "$outdir" ] || record=
# Build scripts run rustc too. autocfg probes $OUT_DIR with a randomly named
# crate, so every probe would add a log file that only exists in one of the two
# builds. Those are not crate artifacts - skip the whole build-script scratch.
case "$outdir" in */build/*/out|*/build/*/out/*) record= ;; esac

if [ -n "$record" ] ; then
    mkdir -p "$RUSTCWRAP_LOG"
    f="$RUSTCWRAP_LOG/$crate$extra"
    {
        echo "metadata $meta"
        # keep the flags readable but drop what legitimately differs:
        # absolute paths, the metadata hash and the -C extra-filename suffix.
        # An empty $meta would make sed reuse the previous regex, so guard it.
        echo "flags    $(printf '%s\n' "$@" |
            grep -v '^/' |
            sed -e "s/${meta:-\\\$^}/HASH/g" -e 's!=/[^ ]*!=PATH!' |
            tr '\n' ' ')"
        # Artifacts of *this* crate only. cargo shares one out-dir between all
        # units, so matching loosely would hash every other crate's output too.
        # Final cdylib/bin links carry no -C extra-filename, hence the fallback.
        if [ -n "$extra" ] ; then
            pat="*$extra*"
        else
            pat="$crate.*"
        fi
        for o in "$outdir"/$pat "$outdir"/lib"$crate".* ; do
            [ -f "$o" ] || continue
            case "${o##*/}" in
                rustc??????|rmeta??????|*.tmp) continue ;;  # rustc scratch
            esac
            echo "$(sha256sum < "$o" | cut -d' ' -f1) ${o##*/}"
        done | sort -k2
    } > "$f" 2>/dev/null
fi
exit $ret
