mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
bios fingerprint: compute it with cmake -P, not a bash found on PATH
The windows-2022 release job accepted the committed backends in the step shell (Git Bash) and refused them at configure: runtime.cmake ran the bash fingerprint through whichever bash find_program() located, and that environment hashed a byte-identical tree differently. The fingerprint is now tools/bios_emitter_fingerprint.cmake, run with the configuring cmake itself on every platform; the shell script is a thin wrapper around it. Same digest shape as before, so existing stamps stay valid (verified on an LF tree and an autocrlf clone). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -767,9 +767,11 @@ list(APPEND PSXRECOMP_BIOS_GENERATED "${_psxrt_registry_c}")
|
||||
option(PSXRECOMP_BIOS_STALE_FATAL
|
||||
"Fail configure when a linked BIOS stem's generated/ stamp predates the emitter"
|
||||
OFF)
|
||||
# The fingerprint is a CMake script run with THIS cmake, never a bash found on
|
||||
# PATH: on the windows-2022 runner the bash CMake located hashed differently
|
||||
# from the step shell's, and a byte-identical tree read as STALE there only.
|
||||
if(NOT PSXRECOMP_SKIP_BIOS_STALE_CHECK AND _psxrt_bios_linked)
|
||||
find_program(_psxrt_bash NAMES bash)
|
||||
if(_psxrt_bash AND EXISTS "${PSXRECOMP_ROOT}/tools/bios_emitter_fingerprint.sh")
|
||||
if(EXISTS "${PSXRECOMP_ROOT}/tools/bios_emitter_fingerprint.cmake")
|
||||
foreach(_psxrt_chk_stem IN LISTS _psxrt_bios_linked)
|
||||
set(_psxrt_stamp "${PSXRECOMP_ROOT}/generated/${_psxrt_chk_stem}.emitter.sha")
|
||||
set(_psxrt_chk_profile "${PSXRECOMP_ROOT}/bios/${_psxrt_chk_stem}.toml")
|
||||
@@ -777,8 +779,9 @@ if(NOT PSXRECOMP_SKIP_BIOS_STALE_CHECK AND _psxrt_bios_linked)
|
||||
continue()
|
||||
endif()
|
||||
execute_process(
|
||||
COMMAND "${_psxrt_bash}" "${PSXRECOMP_ROOT}/tools/bios_emitter_fingerprint.sh"
|
||||
"${_psxrt_chk_profile}"
|
||||
COMMAND "${CMAKE_COMMAND}" "-DROOT=${PSXRECOMP_ROOT}"
|
||||
"-DPROFILE=${_psxrt_chk_profile}"
|
||||
-P "${PSXRECOMP_ROOT}/tools/bios_emitter_fingerprint.cmake"
|
||||
WORKING_DIRECTORY "${PSXRECOMP_ROOT}"
|
||||
OUTPUT_VARIABLE _psxrt_cur_fp OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE _psxrt_fp_rc ERROR_QUIET)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# bios_emitter_fingerprint.cmake — THE emitter fingerprint, as a CMake script.
|
||||
#
|
||||
# cmake -DPROFILE=bios/<stem>.toml [-DROOT=<framework>] [-DFP_DEBUG=1] \
|
||||
# -P tools/bios_emitter_fingerprint.cmake
|
||||
#
|
||||
# Prints one SHA-256 over every input that shapes the generated BIOS C: the
|
||||
# psxrecomp-bios emitter sources, the cycle headers they bake in, the profile
|
||||
# and its seeds. Not the ROM image: the profile pins its SHA-256 and the
|
||||
# emitter refuses any other image, and CI has no dump.
|
||||
#
|
||||
# WHY CMAKE AND NOT BASH. runtime.cmake compares this against the committed
|
||||
# generated/<stem>.emitter.sha at configure (fatal in release CI). A bash
|
||||
# implementation hashed differently depending on which bash/coreutils CMake
|
||||
# found on a Windows runner (Git Bash in the step shell agreed with Linux; the
|
||||
# bash CMake located did not), so a byte-identical tree read as STALE there
|
||||
# only. cmake is the one tool every configure already has, and
|
||||
# file(READ)+string(SHA256) behave the same everywhere. Carriage returns are
|
||||
# stripped before hashing so a CRLF checkout agrees with an LF one.
|
||||
#
|
||||
# Digest shape (kept identical to the previous bash form so existing stamps
|
||||
# stay valid): per file, "<sha256 of CR-stripped bytes> -\n"; the fingerprint
|
||||
# is the SHA-256 of that concatenated listing.
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
if(NOT ROOT)
|
||||
get_filename_component(ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
endif()
|
||||
if(NOT PROFILE)
|
||||
set(PROFILE "bios/SCPH1001.toml")
|
||||
endif()
|
||||
if(NOT IS_ABSOLUTE "${PROFILE}")
|
||||
set(PROFILE "${ROOT}/${PROFILE}")
|
||||
endif()
|
||||
|
||||
set(_files
|
||||
recompiler/src/full_function_emitter.cpp
|
||||
recompiler/src/full_function_emitter.h
|
||||
recompiler/src/strict_translator.cpp
|
||||
recompiler/src/main_bios.cpp
|
||||
recompiler/src/function_discovery.cpp
|
||||
recompiler/src/bios_address_model.cpp
|
||||
recompiler/src/bios_address_model.h
|
||||
recompiler/src/config_loader.cpp
|
||||
recompiler/src/control_flow.cpp
|
||||
recompiler/src/function_analysis.cpp
|
||||
recompiler/src/mips_decoder.cpp
|
||||
recompiler/src/bios_slice_walker.cpp
|
||||
recompiler/src/basic_block.cpp
|
||||
runtime/include/psx_cyc.h
|
||||
runtime/include/psx_instr_cost.h)
|
||||
set(_paths)
|
||||
foreach(_f IN LISTS _files)
|
||||
list(APPEND _paths "${ROOT}/${_f}")
|
||||
endforeach()
|
||||
|
||||
# Per-profile inputs: the profile itself and the seeds file it names.
|
||||
if(EXISTS "${PROFILE}")
|
||||
list(APPEND _paths "${PROFILE}")
|
||||
file(STRINGS "${PROFILE}" _seed_lines REGEX "^[ \t]*seeds[ \t]*=[ \t]*\"[^\"]*\"")
|
||||
if(_seed_lines)
|
||||
list(GET _seed_lines 0 _seed_line)
|
||||
string(REGEX REPLACE "^[ \t]*seeds[ \t]*=[ \t]*\"([^\"]*)\".*$" "\\1" _seeds "${_seed_line}")
|
||||
if(_seeds)
|
||||
list(APPEND _paths "${ROOT}/${_seeds}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
list(APPEND _paths "${ROOT}/recompiler/seeds/phase2_ghidra_seeds.json")
|
||||
endif()
|
||||
|
||||
set(_listing "")
|
||||
foreach(_p IN LISTS _paths)
|
||||
if(NOT EXISTS "${_p}")
|
||||
continue()
|
||||
endif()
|
||||
file(READ "${_p}" _content)
|
||||
string(REPLACE "\r" "" _content "${_content}")
|
||||
string(SHA256 _d "${_content}")
|
||||
if(FP_DEBUG)
|
||||
file(RELATIVE_PATH _rel "${ROOT}" "${_p}")
|
||||
message("${_d} ${_rel}")
|
||||
endif()
|
||||
string(APPEND _listing "${_d} -\n")
|
||||
endforeach()
|
||||
string(SHA256 _fp "${_listing}")
|
||||
# message() writes to stderr; the digest is the script's stdout contract.
|
||||
execute_process(COMMAND "${CMAKE_COMMAND}" -E echo "${_fp}")
|
||||
@@ -1,80 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# bios_emitter_fingerprint.sh — print a single content hash of every input that
|
||||
# affects the BIOS generated C: the psxrecomp-bios emitter sources, the cycle
|
||||
# model they bake in, and — per BIOS profile — the profile TOML itself, its
|
||||
# seeds file, and the ROM image. regen_bios.sh writes this into
|
||||
# generated/<stem>.emitter.sha after a regen; runtime/runtime.cmake recomputes
|
||||
# it at configure time and warns if it differs — i.e. "you changed an emitter
|
||||
# input but didn't regenerate the BIOS". Hashing the ROM and profile means
|
||||
# swapping the BIOS image or editing its profile trips the staleness warning
|
||||
# too, not just emitter-source edits.
|
||||
# bios_emitter_fingerprint.sh — thin wrapper: the fingerprint is computed by
|
||||
# tools/bios_emitter_fingerprint.cmake (one implementation, run by cmake -P on
|
||||
# every platform; see its header for why a bash implementation could not be
|
||||
# trusted across runners). regen_bios.sh writes the result into
|
||||
# generated/<stem>.emitter.sha; runtime.cmake recomputes it at configure.
|
||||
#
|
||||
# Usage: bios_emitter_fingerprint.sh [profile.toml]
|
||||
# default profile: bios/SCPH1001.toml
|
||||
#
|
||||
# The rom/seeds extraction expects simple `key = "value"` lines, which is how
|
||||
# the tracked profiles are written. Keep the FILES list in sync with what
|
||||
# actually feeds psxrecomp-bios --emit-full.
|
||||
set -euo pipefail
|
||||
# Usage: bios_emitter_fingerprint.sh [profile.toml] (default: bios/SCPH1001.toml)
|
||||
# PSXRECOMP_FP_DEBUG=1 also prints "<digest> <path>" per input to stderr.
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
PROFILE="${1:-bios/SCPH1001.toml}"
|
||||
|
||||
FILES=(
|
||||
recompiler/src/full_function_emitter.cpp
|
||||
recompiler/src/full_function_emitter.h
|
||||
recompiler/src/strict_translator.cpp
|
||||
recompiler/src/main_bios.cpp
|
||||
recompiler/src/function_discovery.cpp
|
||||
recompiler/src/bios_address_model.cpp
|
||||
recompiler/src/bios_address_model.h
|
||||
recompiler/src/config_loader.cpp
|
||||
recompiler/src/control_flow.cpp
|
||||
recompiler/src/function_analysis.cpp
|
||||
recompiler/src/mips_decoder.cpp
|
||||
recompiler/src/bios_slice_walker.cpp
|
||||
recompiler/src/basic_block.cpp
|
||||
runtime/include/psx_cyc.h
|
||||
runtime/include/psx_instr_cost.h
|
||||
)
|
||||
|
||||
# Per-profile inputs: the profile itself and its seeds file. The ROM image is
|
||||
# deliberately NOT hashed: the profile pins its SHA-256 ([program.image]) and
|
||||
# psxrecomp-bios refuses any other image, so the profile already stands for
|
||||
# the ROM -- and the generated C for a retail stem is committed and built by
|
||||
# CI, which has no dump. Hashing the image here made every CI configure read
|
||||
# "stale" for a tree that was byte-identical to the developer's.
|
||||
if [ -f "$PROFILE" ]; then
|
||||
FILES+=("$PROFILE")
|
||||
toml_value() {
|
||||
sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$PROFILE" | head -1
|
||||
}
|
||||
SEEDS="$(toml_value seeds)"
|
||||
[ -n "$SEEDS" ] && FILES+=("$SEEDS")
|
||||
else
|
||||
# Legacy fallback (pre-profile invocations): the historical seeds path.
|
||||
FILES+=(recompiler/seeds/phase2_ghidra_seeds.json)
|
||||
fi
|
||||
|
||||
# Hash only files that exist (list may evolve). sha256sum is in Git Bash/coreutils.
|
||||
present=()
|
||||
for f in "${FILES[@]}"; do [ -f "$f" ] && present+=("$f"); done
|
||||
# Single combined digest over file CONTENTS in the FILES-array order above.
|
||||
# Content-only (stdin mode) on purpose: `sha256sum "$f"` emits "<hash> <path>"
|
||||
# lines, so the digest depended on the profile's PATH SPELLING — regen_bios.sh
|
||||
# records with a relative path while runtime.cmake recomputes with an absolute
|
||||
# one, and every configure read as STALE with a byte-identical tree. The array
|
||||
# order is defined by this script alone, so it is stable across spellings.
|
||||
# CR-stripped: Git for Windows checks text files out with CRLF (core.autocrlf
|
||||
# defaults to true on the windows-* runners), and the same source hashed
|
||||
# differently there than on the developer's LF checkout -- every Windows CI
|
||||
# configure read a byte-identical tree as STALE. Stripping CR is a no-op for an
|
||||
# LF file, so stamps written before this line stay valid.
|
||||
# PSXRECOMP_FP_DEBUG=1 prints "<per-file digest> <path>" lines to stderr, so a
|
||||
# runner that disagrees with a developer's stamp can be diffed input by input.
|
||||
for f in "${present[@]}"; do
|
||||
d="$(tr -d '\r' < "$f" | sha256sum | awk '{print $1}')"
|
||||
if [ -n "${PSXRECOMP_FP_DEBUG:-}" ]; then echo "$d $f" >&2; fi
|
||||
echo "$d -"
|
||||
done | sha256sum | awk '{print $1}'
|
||||
CMAKE_BIN="${CMAKE:-cmake}"
|
||||
command -v "$CMAKE_BIN" >/dev/null 2>&1 || { echo "bios_emitter_fingerprint: cmake not found on PATH" >&2; exit 1; }
|
||||
args=(-DROOT="$ROOT" -DPROFILE="$PROFILE")
|
||||
[ -n "${PSXRECOMP_FP_DEBUG:-}" ] && args+=(-DFP_DEBUG=1)
|
||||
exec "$CMAKE_BIN" "${args[@]}" -P "$ROOT/tools/bios_emitter_fingerprint.cmake"
|
||||
|
||||
Reference in New Issue
Block a user