diff --git a/THIRD_PARTY_ATTRIBUTION.md b/THIRD_PARTY_ATTRIBUTION.md index 0b1b85d2..b1dd3cc3 100644 --- a/THIRD_PARTY_ATTRIBUTION.md +++ b/THIRD_PARTY_ATTRIBUTION.md @@ -13,6 +13,22 @@ Normal runtime builds stage it automatically so players supply only a disc; `bios/OpenBIOS.LICENSE` always rides alongside the shipped image. +## libchdr — CHD disc-image decompressor + +[libchdr](https://github.com/rtissera/libchdr) by Romain Tisserand and +contributors, licensed **BSD-3-Clause** (notice: `LICENSE.txt` inside the +archive). Vendored as the pinned source archive +`third_party/libchdr-.tar.gz`; the commit, digest, and upstream URL are +recorded in `third_party/deps.manifest`, and `runtime/chd_dependency.cmake` +verifies the archive against that digest before building it. It is compiled +into the runtime as a static library, so the BSD notice must ship with any +binary that links it. + +The archive also carries libchdr's own bundled decompressors — Zstandard +(BSD-3-Clause / GPL-2.0 dual), LZMA SDK (public domain), and miniz (MIT) — +built from the same pinned tree; `WITH_SYSTEM_ZLIB`/`WITH_SYSTEM_ZSTD` are +forced OFF so the disc decoder cannot change with the host's packages. + ## TinyCC (TCC) — toolchain-free overlay compiler shipped to players [TinyCC](https://bellard.org/tcc/) by Fabrice Bellard and contributors, licensed diff --git a/cmake/psx_dependency_archive.cmake b/cmake/psx_dependency_archive.cmake new file mode 100644 index 00000000..124d0f7d --- /dev/null +++ b/cmake/psx_dependency_archive.cmake @@ -0,0 +1,166 @@ +include_guard(GLOBAL) + +# --------------------------------------------------------------------------- +# Offline-capable resolution of the FetchContent dependencies this tree pulls. +# +# A shipped game is configured and built on the *player's* machine, and that +# machine frequently cannot reach github.com: corporate proxy, TLS-inspecting +# firewall, or no network at all. A dependency fetched unconditionally turns +# that into a failed setup with a CMake stack trace nobody can act on, so every +# URL dependency here resolves in three steps: +# +# 1. a pre-extracted source tree — FETCHCONTENT_SOURCE_DIR_, or the +# dependency's documented env override — and nothing is downloaded; +# 2. a vendored archive in third_party/, verified against the same SHA256 the +# upstream pin carries; +# 3. the upstream URL. +# +# third_party/deps.manifest is THE pin record. Both this file and +# tools/ci/vendor_deps.sh read it, so a vendored archive cannot drift from the +# URL/hash the build would otherwise have downloaded. +# +# -DPSX_DEPS_OFFLINE=ON (or PSX_DEPS_OFFLINE=1 in the environment) turns step 3 +# into a hard error that names the missing archive, instead of a download that +# stalls behind a firewall and then fails deep inside a FetchContent subbuild +# where the real cause is three stack frames up. +# --------------------------------------------------------------------------- + +if(NOT DEFINED PSX_THIRD_PARTY_DIR) + get_filename_component(_psx_third_party_default + "${CMAKE_CURRENT_LIST_DIR}/../third_party" ABSOLUTE) + set(PSX_THIRD_PARTY_DIR "${_psx_third_party_default}" CACHE PATH + "Directory holding vendored dependency archives (third_party/)") + unset(_psx_third_party_default) +endif() + +set(_psx_deps_offline_default OFF) +if(NOT "$ENV{PSX_DEPS_OFFLINE}" STREQUAL "" AND + NOT "$ENV{PSX_DEPS_OFFLINE}" STREQUAL "0") + set(_psx_deps_offline_default ON) +endif() +set(PSX_DEPS_OFFLINE "${_psx_deps_offline_default}" CACHE BOOL + "Never download a dependency: require a vendored archive or a pre-extracted source tree") +unset(_psx_deps_offline_default) + +# Look up one row of third_party/deps.manifest. +function(_psx_dependency_pin name out_file out_sha out_url) + set(_manifest "${PSX_THIRD_PARTY_DIR}/deps.manifest") + if(NOT EXISTS "${_manifest}") + message(FATAL_ERROR + "psxrecomp: dependency manifest not found: ${_manifest}\n" + " Point -DPSX_THIRD_PARTY_DIR=… at the framework's third_party/ " + "directory.") + endif() + file(STRINGS "${_manifest}" _lines) + foreach(_line IN LISTS _lines) + string(STRIP "${_line}" _line) + if(_line STREQUAL "" OR _line MATCHES "^#") + continue() + endif() + string(REGEX MATCHALL "[^ \t]+" _fields "${_line}") + list(LENGTH _fields _field_count) + if(NOT _field_count EQUAL 4) + message(FATAL_ERROR + "psxrecomp: malformed row in ${_manifest} " + "(want 'name file sha256 url'): ${_line}") + endif() + list(GET _fields 0 _row_name) + if(_row_name STREQUAL "${name}") + list(GET _fields 1 _row_file) + list(GET _fields 2 _row_sha) + list(GET _fields 3 _row_url) + set(${out_file} "${_row_file}" PARENT_SCOPE) + set(${out_sha} "${_row_sha}" PARENT_SCOPE) + set(${out_url} "${_row_url}" PARENT_SCOPE) + return() + endif() + endforeach() + message(FATAL_ERROR "psxrecomp: no '${name}' row in ${_manifest}") +endfunction() + +# psxrecomp_dependency_archive( OUT_URL OUT_HASH +# [SOURCE_DIR ]) +# +# Sets OUT_URL to the vendored archive when third_party/ holds it, else to the +# upstream URL (fatal under PSX_DEPS_OFFLINE). OUT_HASH is the URL_HASH string +# for FetchContent_Declare and is the same either way — a vendored archive is +# verified exactly as strictly as a downloaded one. +# +# Pass the result of psxrecomp_dependency_source_dir() as SOURCE_DIR. When it is +# non-empty FetchContent uses that tree and ignores the URL, so the declaration +# still needs to be well-formed but reports nothing about a download that will +# not happen. +function(psxrecomp_dependency_archive name) + cmake_parse_arguments(_psx_dep "" "OUT_URL;OUT_HASH;SOURCE_DIR" "" ${ARGN}) + if(NOT _psx_dep_OUT_URL OR NOT _psx_dep_OUT_HASH) + message(FATAL_ERROR + "psxrecomp_dependency_archive(${name}): OUT_URL and OUT_HASH are required") + endif() + set(_psx_dep_SILENT OFF) + if(NOT "${_psx_dep_SOURCE_DIR}" STREQUAL "") + set(_psx_dep_SILENT ON) + endif() + + _psx_dependency_pin("${name}" _file _sha _url) + set(${_psx_dep_OUT_HASH} "SHA256=${_sha}" PARENT_SCOPE) + + set(_vendored "${PSX_THIRD_PARTY_DIR}/${_file}") + if(EXISTS "${_vendored}") + if(NOT _psx_dep_SILENT) + message(STATUS + "psxrecomp: ${name} from vendored archive third_party/${_file}") + endif() + set(${_psx_dep_OUT_URL} "${_vendored}" PARENT_SCOPE) + return() + endif() + + string(TOUPPER "${name}" _uc_name) + if(PSX_DEPS_OFFLINE) + message(FATAL_ERROR + "psxrecomp: PSX_DEPS_OFFLINE is ON but ${name} is not vendored.\n" + " expected archive: ${_vendored}\n" + " stage it with: tools/ci/vendor_deps.sh ${name}\n" + " or point the build at an already-extracted tree with " + "-DFETCHCONTENT_SOURCE_DIR_${_uc_name}=") + endif() + + if(NOT _psx_dep_SILENT) + message(STATUS + "psxrecomp: ${name} not vendored — will download ${_url} " + "(tools/ci/vendor_deps.sh ${name} makes this build offline-capable)") + endif() + set(${_psx_dep_OUT_URL} "${_url}" PARENT_SCOPE) +endfunction() + +# psxrecomp_dependency_source_dir( OUT [ENV ] [MARKER ]) +# +# Honours an already-extracted source tree, from -DFETCHCONTENT_SOURCE_DIR_ +# or from the named environment variable (which is promoted into the cache var so +# FetchContent picks it up). OUT is set to the directory, or "" when none applies. +function(psxrecomp_dependency_source_dir name) + cmake_parse_arguments(_psx_src "" "OUT;ENV;MARKER" "" ${ARGN}) + if(NOT _psx_src_OUT) + message(FATAL_ERROR + "psxrecomp_dependency_source_dir(${name}): OUT is required") + endif() + if(NOT _psx_src_MARKER) + set(_psx_src_MARKER "CMakeLists.txt") + endif() + + string(TOUPPER "${name}" _uc_name) + set(_dir "") + if(NOT "${FETCHCONTENT_SOURCE_DIR_${_uc_name}}" STREQUAL "" AND + EXISTS "${FETCHCONTENT_SOURCE_DIR_${_uc_name}}/${_psx_src_MARKER}") + set(_dir "${FETCHCONTENT_SOURCE_DIR_${_uc_name}}") + elseif(_psx_src_ENV AND NOT "$ENV{${_psx_src_ENV}}" STREQUAL "" AND + EXISTS "$ENV{${_psx_src_ENV}}/${_psx_src_MARKER}") + set(_dir "$ENV{${_psx_src_ENV}}") + set(FETCHCONTENT_SOURCE_DIR_${_uc_name} "${_dir}" CACHE PATH + "Pre-fetched ${name} source (skip download)" FORCE) + endif() + + if(_dir) + message(STATUS "psxrecomp: using pre-fetched ${name} source ${_dir}") + endif() + set(${_psx_src_OUT} "${_dir}" PARENT_SCOPE) +endfunction() diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 2709a63e..e1a7f54a 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -153,6 +153,8 @@ On Windows with MSVC or plain MinGW makefiles, swap `-G Ninja` for your generato | `PSX_DEBUG_TOOLS` | ON for Debug/RelWithDebInfo, OFF for Release | TCP debug server + heartbeat + per-block recording | | `PSX_SDL_BACKEND` | `SDL3` | Host backend. Set `SDL2` explicitly for compatibility or A/B testing. | | `PSX_SDL3_FETCH` | ON | Fetch the pinned SDL3 release when a compatible system SDL3 package is unavailable. Set OFF for offline/system-only builds. | +| `PSX_DEPS_OFFLINE` | OFF (env `PSX_DEPS_OFFLINE=1` flips it) | Never download a dependency. A dependency that is not vendored in `third_party/` and has no pre-extracted source tree becomes a hard error naming the missing archive, instead of a fetch that stalls behind a proxy. | +| `PSX_THIRD_PARTY_DIR` | `/third_party` | Where vendored dependency archives are looked up (`deps.manifest` lives there). | | `PSX_STATIC_RUNTIME` | ON for MinGW Release | Self-contained exe (statically links SDL + libgcc/libstdc++) | | `PSX_RECOMP_UI` | ON | Wire a downstream game's pinned recomp-ui launcher; set OFF for headless/generated builds | | `PSX_ENABLE_VULKAN` | **ON** | Build the experimental Vulkan renderer when the SDK tools are present (skipped if not). Pass `OFF` to exclude it outright. | @@ -232,6 +234,27 @@ these files without it). If you hit this on a framework source file, please open an issue with your `gcc -v` / `as --version` — the build should apply the flag for you. +**`Build step for psx_libchdr failed: 1`, or any `FetchContent` populate +failure.** The build could not download a pinned dependency archive — no +network, a corporate proxy, or a TLS-inspecting firewall between you and +github.com. libchdr is the first dependency the runtime resolves, so it is +usually the one that reports the problem. + +The pinned libchdr archive is vendored at `third_party/`, so an up-to-date +framework checkout does not download it at all. If you are on an older +checkout, or you need SDL3/zlib offline too, stage them once from a machine +with access and copy `third_party/` across: + +```sh +psxrecomp/tools/ci/vendor_deps.sh # stage every pinned archive +psxrecomp/tools/ci/vendor_deps.sh --check # verify; never downloads +``` + +Then configure with `-DPSX_DEPS_OFFLINE=ON` so any remaining download attempt +fails immediately with the name of the archive to stage, rather than deep +inside a FetchContent subbuild. An already-extracted tree works too: +`-DFETCHCONTENT_SOURCE_DIR_PSX_LIBCHDR=`. + **`SDL3 3.4+ was not found`.** The default build normally downloads the pinned release. Check network access, install a system SDL3 package and provide `SDL3_DIR`, or re-enable `-DPSX_SDL3_FETCH=ON`. diff --git a/runtime/chd_dependency.cmake b/runtime/chd_dependency.cmake index da2fb683..0a1793b9 100644 --- a/runtime/chd_dependency.cmake +++ b/runtime/chd_dependency.cmake @@ -1,6 +1,7 @@ include_guard(GLOBAL) include(FetchContent) +include("${CMAKE_CURRENT_LIST_DIR}/../cmake/psx_dependency_archive.cmake") # libchdr is the small, BSD-licensed decompressor used by MAME-compatible CHD # images. Pin the exact source archive (and its digest) so generated games do @@ -21,10 +22,26 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) DOWNLOAD_EXTRACT_TIMESTAMP TRUE) endif() +# This include is unconditional — there is no find_package path and no +# toolchain pack that ships libchdr — so a player building a released game with +# no route to github.com used to fail here, in a FetchContent subbuild, with no +# usable diagnosis. The pinned archive is vendored at third_party/ for exactly +# that reason; the upstream URL stays as the fallback for a stripped checkout. +psxrecomp_dependency_source_dir(psx_libchdr + ENV PSX_LIBCHDR_SOURCE_DIR + OUT _psx_libchdr_src) +psxrecomp_dependency_archive(psx_libchdr + SOURCE_DIR "${_psx_libchdr_src}" + OUT_URL _psx_libchdr_url OUT_HASH _psx_libchdr_hash) + FetchContent_Declare(psx_libchdr URL - "https://github.com/rtissera/libchdr/archive/6cde5348eb118da3baf94f75a69577a005a484fd.tar.gz" + "${_psx_libchdr_url}" URL_HASH - "SHA256=a222b1f1c842f988c2aad51e5f56ba94a423ed87ccab71f1abdb757caa7444f5" + "${_psx_libchdr_hash}" ${_psx_libchdr_timestamp_args}) FetchContent_MakeAvailable(psx_libchdr) + +unset(_psx_libchdr_src) +unset(_psx_libchdr_url) +unset(_psx_libchdr_hash) diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 0a47ee5f..94c0f5fa 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -8,6 +8,7 @@ if(NOT DEFINED PSXRECOMP_ROOT) get_filename_component(PSXRECOMP_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) endif() +include("${PSXRECOMP_ROOT}/cmake/psx_dependency_archive.cmake") include("${PSXRECOMP_ROOT}/runtime/chd_dependency.cmake") # Default to an optimized build. The recompiled game is a huge (~270 MB) block of @@ -166,30 +167,24 @@ if(_psx_sdl_backend STREQUAL "SDL3") # CI (tools/ci/prefetch_sdl3.sh) pre-extracts with curl --http1.1 to # avoid intermittent GitHub HTTP/2 REFUSED_STREAM failures from # CMake's file(DOWNLOAD). Prefer that tree when present. - set(_psx_sdl3_src "") - if(DEFINED FETCHCONTENT_SOURCE_DIR_SDL3 AND - NOT FETCHCONTENT_SOURCE_DIR_SDL3 STREQUAL "" AND - EXISTS "${FETCHCONTENT_SOURCE_DIR_SDL3}/CMakeLists.txt") - set(_psx_sdl3_src "${FETCHCONTENT_SOURCE_DIR_SDL3}") - elseif(DEFINED ENV{PSX_SDL3_SOURCE_DIR} AND - NOT "$ENV{PSX_SDL3_SOURCE_DIR}" STREQUAL "" AND - EXISTS "$ENV{PSX_SDL3_SOURCE_DIR}/CMakeLists.txt") - set(_psx_sdl3_src "$ENV{PSX_SDL3_SOURCE_DIR}") - set(FETCHCONTENT_SOURCE_DIR_SDL3 "${_psx_sdl3_src}" CACHE PATH - "Pre-fetched SDL3 source (skip download)" FORCE) - endif() - if(_psx_sdl3_src) - message(STATUS - "psxrecomp: using pre-fetched SDL3 source ${_psx_sdl3_src}") - endif() + psxrecomp_dependency_source_dir(SDL3 + ENV PSX_SDL3_SOURCE_DIR + OUT _psx_sdl3_src) + # Pin (and any vendored archive) come from third_party/deps.manifest, so + # an air-gapped tree staged by tools/ci/vendor_deps.sh needs no download. + psxrecomp_dependency_archive(SDL3 + SOURCE_DIR "${_psx_sdl3_src}" + OUT_URL _psx_sdl3_url OUT_HASH _psx_sdl3_hash) FetchContent_Declare(SDL3 URL - "https://github.com/libsdl-org/SDL/releases/download/release-3.4.10/SDL3-3.4.10.tar.gz" + "${_psx_sdl3_url}" URL_HASH - "SHA256=12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785" + "${_psx_sdl3_hash}" ${_psx_sdl3_timestamp_args}) FetchContent_MakeAvailable(SDL3) unset(_psx_sdl3_src) + unset(_psx_sdl3_url) + unset(_psx_sdl3_hash) endif() if(NOT TARGET SDL3::SDL3) message(FATAL_ERROR @@ -729,11 +724,17 @@ function(psxrecomp_ensure_zlib) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) list(APPEND _psx_zlib_timestamp_args DOWNLOAD_EXTRACT_TIMESTAMP TRUE) endif() + psxrecomp_dependency_source_dir(psx_zlib + ENV PSX_ZLIB_SOURCE_DIR + OUT _psx_zlib_src) + psxrecomp_dependency_archive(psx_zlib + SOURCE_DIR "${_psx_zlib_src}" + OUT_URL _psx_zlib_url OUT_HASH _psx_zlib_hash) FetchContent_Declare(psx_zlib URL - "https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz" + "${_psx_zlib_url}" URL_HASH - "SHA256=9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23" + "${_psx_zlib_hash}" ${_psx_zlib_timestamp_args}) FetchContent_MakeAvailable(psx_zlib) # madler/zlib builds zlibstatic even when a shared zlib target exists. diff --git a/third_party/.gitignore b/third_party/.gitignore new file mode 100644 index 00000000..3704f0ca --- /dev/null +++ b/third_party/.gitignore @@ -0,0 +1,9 @@ +# Vendored archives are opt-in. Only libchdr — the one dependency the build +# cannot satisfy locally — is committed; tools/ci/vendor_deps.sh stages the +# rest here on demand for air-gapped builds, and those must not be committed +# (SDL3 alone is ~15 MB). +*.tar.gz +*.tar.xz +*.tar.bz2 +*.zip +!libchdr-*.tar.gz diff --git a/third_party/README.md b/third_party/README.md new file mode 100644 index 00000000..346d1ef8 --- /dev/null +++ b/third_party/README.md @@ -0,0 +1,42 @@ +# third_party/ — vendored dependency archives + +Source archives the build would otherwise download, staged here so a checkout +can configure with no network at all. + +`deps.manifest` is the single pin record — name, archive file, SHA256, upstream +URL. Both `cmake/psx_dependency_archive.cmake` and `tools/ci/vendor_deps.sh` +read it, so a vendored archive can never drift from the pin the build would +have fetched. A vendored archive is verified against the same `URL_HASH` as a +downloaded one; nothing here is trusted because it is local. + +## What is committed, and why only that + +Only **libchdr** (`libchdr-.tar.gz`, ~520 KB, BSD-3-Clause — see +`../THIRD_PARTY_ATTRIBUTION.md`). + +`runtime/runtime.cmake` includes `chd_dependency.cmake` unconditionally, and +libchdr has no `find_package` path and ships in no toolchain pack — so it is +both the *first* network access a build makes and the only one with no local +fallback. A player building a released game behind a firewall used to fail +there, inside a FetchContent subbuild, before reaching any other dependency. + +SDL3 (~15 MB) and zlib resolve from a system package or a toolchain pack first +and are **not** committed; `.gitignore` keeps them out. Stage them on demand: + +```sh +tools/ci/vendor_deps.sh # every dependency in the manifest +tools/ci/vendor_deps.sh SDL3 # just one +tools/ci/vendor_deps.sh --check # verify what is staged; never downloads +``` + +Then configure with `-DPSX_DEPS_OFFLINE=ON` to turn any remaining download +attempt into a hard error that names the missing archive, instead of a fetch +that stalls behind a proxy and fails three CMake frames deep. + +## Adding a dependency + +Add the row to `deps.manifest`, run `tools/ci/vendor_deps.sh ` to stage +and verify the archive, and resolve it in CMake through +`psxrecomp_dependency_archive()` / `psxrecomp_dependency_source_dir()` rather +than a literal `URL` — a literal URL is a build that cannot go offline. Commit +the archive only if the dependency has no local fallback and is small. diff --git a/third_party/deps.manifest b/third_party/deps.manifest new file mode 100644 index 00000000..7642814d --- /dev/null +++ b/third_party/deps.manifest @@ -0,0 +1,23 @@ +# psxrecomp vendored-dependency pins. +# +# THE single record of every source archive the build may fetch. Both +# cmake/psx_dependency_archive.cmake and tools/ci/vendor_deps.sh read this +# file, so a vendored archive under third_party/ can never drift from the +# URL/hash the build would otherwise download. +# +# Columns, whitespace-separated: +# name FetchContent name used in the CMake files +# file archive name under third_party/ (vendored form) +# sha256 digest of that archive; also the URL_HASH of the upstream pin +# url upstream download location +# +# Only psx_libchdr is committed: it is the one dependency with no local +# fallback (no find_package path, no toolchain-pack copy), so a build with no +# network dies at it. SDL3 and zlib resolve from a system package or a +# cmake-clang-v1 toolchain pack first; run tools/ci/vendor_deps.sh to stage +# them here as well for a fully air-gapped tree. +# +# name file sha256 url +psx_libchdr libchdr-6cde5348eb118da3baf94f75a69577a005a484fd.tar.gz a222b1f1c842f988c2aad51e5f56ba94a423ed87ccab71f1abdb757caa7444f5 https://github.com/rtissera/libchdr/archive/6cde5348eb118da3baf94f75a69577a005a484fd.tar.gz +SDL3 SDL3-3.4.10.tar.gz 12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 https://github.com/libsdl-org/SDL/releases/download/release-3.4.10/SDL3-3.4.10.tar.gz +psx_zlib zlib-1.3.1.tar.gz 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23 https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz diff --git a/third_party/libchdr-6cde5348eb118da3baf94f75a69577a005a484fd.tar.gz b/third_party/libchdr-6cde5348eb118da3baf94f75a69577a005a484fd.tar.gz new file mode 100644 index 00000000..a0329095 Binary files /dev/null and b/third_party/libchdr-6cde5348eb118da3baf94f75a69577a005a484fd.tar.gz differ diff --git a/tools/ci/vendor_deps.sh b/tools/ci/vendor_deps.sh new file mode 100755 index 00000000..0588de28 --- /dev/null +++ b/tools/ci/vendor_deps.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# Stage the pinned dependency archives into third_party/ so a build needs no +# network. Reads third_party/deps.manifest — the same pin record +# cmake/psx_dependency_archive.cmake reads — so what lands here is byte-identical +# to what the build would otherwise have downloaded. +# +# Usage: +# tools/ci/vendor_deps.sh # stage every dependency +# tools/ci/vendor_deps.sh SDL3 psx_zlib # stage only these +# tools/ci/vendor_deps.sh --check # verify what is staged; never download +# tools/ci/vendor_deps.sh --force # re-download even if present +# +# After a full run, configure with -DPSX_DEPS_OFFLINE=ON to make any remaining +# download attempt a hard error rather than a silent trip to github.com. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +THIRD_PARTY="${ROOT}/third_party" +MANIFEST="${THIRD_PARTY}/deps.manifest" + +CHECK_ONLY=0 +FORCE=0 +WANTED=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --check) CHECK_ONLY=1; shift ;; + --force) FORCE=1; shift ;; + -h|--help) + sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + -*) + echo "error: unknown option: $1" >&2 + exit 2 + ;; + *) WANTED+=("$1"); shift ;; + esac +done + +if [[ ! -f "${MANIFEST}" ]]; then + echo "error: missing manifest: ${MANIFEST}" >&2 + exit 1 +fi + +sha256_of() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + echo "error: need sha256sum or shasum to verify archives" >&2 + exit 1 + fi +} + +wanted() { + [[ ${#WANTED[@]} -eq 0 ]] && return 0 + local n + for n in "${WANTED[@]}"; do + [[ "$n" == "$1" ]] && return 0 + done + return 1 +} + +download() { + # Prefer HTTP/1.1: GitHub release assets intermittently REFUSED_STREAM under + # HTTP/2, which is the same failure prefetch_sdl3.sh works around. + local url="$1" dest="$2" + if command -v curl >/dev/null 2>&1; then + curl -fsSL --http1.1 --retry 5 --retry-all-errors --retry-delay 2 \ + -o "${dest}" "${url}" + elif command -v wget >/dev/null 2>&1; then + wget -q -O "${dest}" "${url}" + else + echo "error: need curl or wget to download ${url}" >&2 + exit 1 + fi +} + +mkdir -p "${THIRD_PARTY}" + +seen=0 +fail=0 +matched=() + +while read -r name file sha url _rest; do + case "${name}" in ""|\#*) continue ;; esac + if [[ -z "${file}" || -z "${sha}" || -z "${url}" ]]; then + echo "error: malformed manifest row: ${name} ${file} ${sha} ${url}" >&2 + fail=1 + continue + fi + wanted "${name}" || continue + matched+=("${name}") + seen=$((seen + 1)) + + target="${THIRD_PARTY}/${file}" + + if [[ -f "${target}" && "${FORCE}" -eq 0 ]]; then + have="$(sha256_of "${target}")" + if [[ "${have}" == "${sha}" ]]; then + echo "ok: ${name} — third_party/${file}" + continue + fi + echo "error: ${name} — third_party/${file} digest mismatch" >&2 + echo " manifest: ${sha}" >&2 + echo " on disk: ${have}" >&2 + if [[ "${CHECK_ONLY}" -eq 1 ]]; then + fail=1 + continue + fi + echo " re-downloading" >&2 + elif [[ "${CHECK_ONLY}" -eq 1 ]]; then + echo "missing: ${name} — third_party/${file}" >&2 + fail=1 + continue + fi + + echo "fetching: ${name} <- ${url}" + tmp="$(mktemp "${THIRD_PARTY}/.${file}.XXXXXX")" + # shellcheck disable=SC2064 + trap "rm -f '${tmp}'" EXIT + download "${url}" "${tmp}" + have="$(sha256_of "${tmp}")" + if [[ "${have}" != "${sha}" ]]; then + echo "error: ${name} digest mismatch after download" >&2 + echo " want: ${sha}" >&2 + echo " got: ${have}" >&2 + rm -f "${tmp}" + trap - EXIT + fail=1 + continue + fi + mv -f "${tmp}" "${target}" + chmod 0644 "${target}" + trap - EXIT + echo "ok: ${name} — third_party/${file}" +done <"${MANIFEST}" + +if [[ ${#WANTED[@]} -gt 0 ]]; then + for n in "${WANTED[@]}"; do + found=0 + for m in "${matched[@]:-}"; do + [[ "$m" == "$n" ]] && found=1 + done + if [[ "${found}" -eq 0 ]]; then + echo "error: no '${n}' row in ${MANIFEST}" >&2 + fail=1 + fi + done +fi + +if [[ "${seen}" -eq 0 ]]; then + echo "error: nothing to do — no manifest rows selected" >&2 + exit 1 +fi + +if [[ "${fail}" -ne 0 ]]; then + exit 1 +fi + +if [[ "${CHECK_ONLY}" -eq 1 ]]; then + echo "all requested dependencies are vendored and verified." +else + echo "vendored ${seen} dependenc$([[ ${seen} -eq 1 ]] && echo y || echo ies) into third_party/." + echo "configure with -DPSX_DEPS_OFFLINE=ON to forbid any further download." +fi