diff --git a/.gitignore b/.gitignore index a5309e6..b8c96e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build*/ +out/ diff --git a/README.md b/README.md index 51fc7c2..1dd6ea4 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,29 @@ ctest --test-dir build - Linux, macOS and Windows. The link's transport differs per OS (`docs/LINK_TRANSPORTS.md`); the protocol above it does not. +### Build locally + +To build a runner for this machine and hand it to a port or app as an explicit +runner (`--runner`, `RETRO_CORE_RUNNER`): + +```sh +scripts/build-local.sh # Linux, macOS +scripts\build-local.ps1 # Windows (finds Visual Studio itself) +``` + +It builds Release (`--debug` for Debug), copies `retro-core-runner` and +`LICENSE` into `out/local//`, checks the copy's `--version`, and +prints `RETRO_CORE_RUNNER=` as its last line. SDL3 is used if +found (`--sdl3-prefix DIR` to name one, `--no-sdl` to go without); the output +says whether `--gl` is available. A shared SDL3 from outside the system +library directories is copied beside the runner. The runner says `version dev` +and its commit, with `-dirty` when tracked files are modified. `--test` runs +ctest; `--help` lists the rest. This is not a release build: see +`docs/RELEASES.md`. + +Only the Linux path of `build-local.sh` has been run (2026-09-26). Its macOS +path and `build-local.ps1` are written but unrun. + A host pulls this in with `add_subdirectory()` and links `retro_rcore`, `retro_core_support` and `retro_corelink`. A host that already found SDL3 can pass `-DRETRO_RUNTIME_SDL3_TARGET=`. diff --git a/docs/RELEASES.md b/docs/RELEASES.md index 89cca16..a169724 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -17,7 +17,10 @@ Run the `release` workflow from the Actions tab (manual only): | `publish` | Off: a dry run that builds, checks and uploads workflow artifacts only. | Each platform is built by `scripts/package-release.sh`, which a person can also -run. It fails the build, rather than warning, when: +run. (For a runner to develop against, not to ship, use +`scripts/build-local.sh` / `.ps1` instead: README, "Build locally". It needs no +version, SDL3 prefix or 7z, says `version dev`, and has none of the gates +below.) It fails the build, rather than warning, when: - the runner cannot lend a GL context (SDL3 missing); - the runner imports anything but the C library (SDL3, libstdc++ and libgcc diff --git a/scripts/build-local.ps1 b/scripts/build-local.ps1 new file mode 100644 index 0000000..03b7a54 --- /dev/null +++ b/scripts/build-local.ps1 @@ -0,0 +1,249 @@ +# Build retro-core-runner for THIS Windows machine, for local development: the +# runner a developer hands a port or app as an explicit runner (--runner, +# RETRO_CORE_RUNNER). Linux and macOS use scripts/build-local.sh; the two take +# the same options and print the same things. +# +# Not a release: scripts/package-release.sh is the release path, and its gates +# do not apply here. Kept from it: Release by default, the static MSVC runtime +# (/MT, no CRT DLLs to find), the version stamped as "dev" and the commit read +# from git (with -dirty when tracked files are modified). +# +# MSVC: when cl.exe is not on PATH, Visual Studio is found with vswhere and its +# developer environment entered for this script. Ninja is used when it is on +# PATH (Visual Studio ships one), else the Visual Studio generator. +# +# SDL3 is optional, found the way CMakeLists.txt finds it. Without it the +# runner cannot lend a GL context (no --gl); the output says which build you +# got. A shared SDL3.dll is copied beside the runner, where Windows finds it. +# +# The runner is checked before its path is printed: its --version must run and +# report "game_package 1". The LAST line on stdout is always +# RETRO_CORE_RUNNER= +# +# usage: scripts\build-local.ps1 [-Debug] [-Out DIR] [-Build DIR] +# [-Sdl3Prefix DIR | -NoSdl] [-Jobs N] [-Test] [-Help] +# +# Written 2026-09-26 and NOT YET RUN: no Windows machine was available. Whoever +# first runs it replaces this note with what they ran and saw. +param( + [switch]$Debug, + [string]$Out = "", + [string]$Build = "", + [string]$Sdl3Prefix = "", + [switch]$NoSdl, + [int]$Jobs = 0, + [switch]$Test, + [switch]$Help +) +$ErrorActionPreference = "Stop" + +function Die([string]$msg) { [Console]::Error.WriteLine("build-local: $msg"); exit 1 } +function Say([string]$msg) { [Console]::Error.WriteLine("build-local: $msg") } +# Native tools: output to the console (stderr), never into this script's +# stdout. Their stderr must not become a terminating error (Windows PowerShell). +function Invoke-Native([string]$what, [string]$exe, [string[]]$argv) { + $ErrorActionPreference = "Continue" + & $exe @argv 2>&1 | ForEach-Object { [Console]::Error.WriteLine("$_") } + if ($LASTEXITCODE -ne 0) { Die "$what failed (exit $LASTEXITCODE)" } +} + +if ($Help) { + @" +usage: scripts\build-local.ps1 [options] + +Build retro-core-runner for this machine (local development, not a release) +and print its path last, as RETRO_CORE_RUNNER=. + + -Debug Debug build (default: Release) + -Out DIR where the runner is copied + (default: \out\local\\) + -Build DIR CMake build directory + (default: \build-local, or build-local-debug) + -Sdl3Prefix DIR use the SDL3 installed under DIR (it must be found) + -NoSdl build without SDL3 (the runner then has no --gl) + -Jobs N parallel build jobs (default: all CPUs) + -Test run the test suite (ctest) after building + -Help this text + + is windows-x86_64 or windows-arm64. +Relative paths are taken against the current directory. +"@ + exit 0 +} +if ($PSVersionTable.PSEdition -eq "Core" -and -not $IsWindows) { + Die "this script builds on Windows; use scripts/build-local.sh on Linux and macOS" +} +if ($Sdl3Prefix -and $NoSdl) { Die "-Sdl3Prefix and -NoSdl contradict each other" } +if ($Jobs -lt 0) { Die "-Jobs: want a positive number, got $Jobs" } + +$Config = if ($Debug) { "Debug" } else { "Release" } +$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).ProviderPath + +$hostArch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE } +switch ($hostArch) { + "AMD64" { $Arch = "x86_64"; $VsArch = "amd64"; $VsTools = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"; $VsPlatform = "x64" } + "ARM64" { $Arch = "arm64"; $VsArch = "arm64"; $VsTools = "Microsoft.VisualStudio.Component.VC.Tools.ARM64"; $VsPlatform = "ARM64" } + default { Die "$hostArch`: no platform name for this architecture" } +} +$Platform = "windows-$Arch" + +# Paths from the command line are relative to where the script was run. +function Get-AbsDir([string]$p) { + $full = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine((Get-Location).ProviderPath, $p)) + New-Item -ItemType Directory -Force -Path $full | Out-Null + return $full.TrimEnd('\', '/') +} +if (-not $Build) { $Build = Join-Path $Root $(if ($Debug) { "build-local-debug" } else { "build-local" }) } +$Build = Get-AbsDir $Build +if (-not $Out) { $Out = Join-Path $Root "out\local\$Platform" } +$Out = Get-AbsDir $Out +if ($Out -eq $Build) { Die "-Out and -Build must differ" } +if ($Sdl3Prefix) { + if (-not (Test-Path -PathType Container $Sdl3Prefix)) { Die "-Sdl3Prefix ${Sdl3Prefix}: no such directory" } + $Sdl3Prefix = (Resolve-Path $Sdl3Prefix).ProviderPath +} +if ($Jobs -eq 0) { $Jobs = [Environment]::ProcessorCount } + +# ---- MSVC: enter Visual Studio's developer environment if cl.exe is missing -- +if (-not (Get-Command cl.exe -ErrorAction SilentlyContinue)) { + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { Die "cl.exe is not on PATH and vswhere.exe was not found (install Visual Studio with C++)" } + $vs = & $vswhere -latest -products * -requires $VsTools -property installationPath + if (-not $vs) { Die "no Visual Studio with the C++ tools ($VsTools) found by vswhere" } + $devShell = Join-Path $vs "Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + if (-not (Test-Path $devShell)) { Die "$devShell not found (Visual Studio 2019 16.2 or newer is needed)" } + Say "entering the developer environment of $vs ($VsArch)" + Import-Module $devShell + Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation ` + -DevCmdArguments "-arch=$VsArch -host_arch=$VsArch -no_logo" | Out-Null + if (-not (Get-Command cl.exe -ErrorAction SilentlyContinue)) { Die "cl.exe is still not on PATH after entering $vs" } +} +if (-not (Get-Command cmake -ErrorAction SilentlyContinue)) { Die "cmake not found" } + +# ---- version stamp: "dev", and the commit with -dirty if it has changes ------ +$Commit = "" +$ErrorActionPreference = "Continue" +if (Get-Command git -ErrorAction SilentlyContinue) { + $Commit = (& git -C $Root rev-parse HEAD 2>$null) + if ($LASTEXITCODE -ne 0) { $Commit = "" } + elseif ($Commit) { + # Tracked files only, as git describe --dirty counts them. + & git -C $Root diff --quiet HEAD -- 2>$null + if ($LASTEXITCODE -ne 0) { $Commit += "-dirty" } + } +} +$ErrorActionPreference = "Stop" + +# ---- configure ---------------------------------------------------------------- +$cmakeArgs = @( + "-S", $Root, "-B", $Build, + "-DRETRO_RUNTIME_VERSION=dev", + "-DRETRO_RUNTIME_COMMIT=$Commit", # empty: CMake reads git itself, else "unknown" + # As the release does: /MT, so no CRT DLLs to find at run time. + "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`$<`$:Debug>" +) +$Ninja = [bool](Get-Command ninja -ErrorAction SilentlyContinue) +if ($Ninja) { + $cmakeArgs += @("-G", "Ninja", "-DCMAKE_BUILD_TYPE=$Config", "-DCMAKE_C_COMPILER=cl", "-DCMAKE_CXX_COMPILER=cl") +} else { + $cmakeArgs += @("-A", $VsPlatform) # the newest Visual Studio generator +} +if ($NoSdl) { + $cmakeArgs += @("-DCMAKE_DISABLE_FIND_PACKAGE_SDL3=ON", "-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=ON") +} else { + $cmakeArgs += @("-DCMAKE_DISABLE_FIND_PACKAGE_SDL3=OFF", "-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=OFF") + if ($Sdl3Prefix) { + $cmakeArgs += "-DCMAKE_PREFIX_PATH=$Sdl3Prefix" + $cfg = Get-ChildItem -Path $Sdl3Prefix -Recurse -Filter SDL3Config.cmake -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($cfg) { $cmakeArgs += "-DSDL3_DIR=$($cfg.DirectoryName)" } + } +} + +# A build directory configured with other SDL choices, or another generator, +# keeps them in its cache: start it afresh when the choices change. +$stamp = Join-Path $Build "build-local.args" +$argText = ($cmakeArgs -join "`n") +if ((Test-Path $stamp) -and ((Get-Content -Raw $stamp).TrimEnd() -ne $argText)) { + Say "configuration changed since the last build in $Build; reconfiguring from scratch" + Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $Build "CMakeCache.txt") + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $Build "CMakeFiles") +} + +Say "$Platform, $Config, build $Build" +Invoke-Native "cmake configure" "cmake" $cmakeArgs +Set-Content -Path $stamp -Value $argText +Invoke-Native "cmake build" "cmake" @("--build", $Build, "--config", $Config, "--parallel", "$Jobs") + +if ($Test) { + Invoke-Native "ctest" "ctest" @("--test-dir", $Build, "-C", $Config, "--output-on-failure", "--parallel", "$Jobs") +} + +# ---- install into a stage, then copy flat into -Out ---------------------------- +$stage = Join-Path $Build "build-local-stage" +Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $stage +Invoke-Native "cmake install" "cmake" @("--install", $Build, "--config", $Config, "--prefix", $stage) +$Runner = Join-Path $Out "retro-core-runner.exe" +Copy-Item -Force (Join-Path $stage "bin\retro-core-runner.exe") $Runner +Copy-Item -Force (Join-Path $Root "LICENSE") (Join-Path $Out "LICENSE") + +# ---- SDL3: none, static, or SDL3.dll copied beside the runner ------------------- +Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $Out "SDL3.dll") # from an earlier build +$SdlNote = "" +$deps = @() +if (Get-Command dumpbin -ErrorAction SilentlyContinue) { + $deps = @(& dumpbin -nologo -dependents $Runner) +} else { + Say "dumpbin not found: cannot tell whether the runner imports SDL3.dll (the --version check below still runs it)" +} +if ($deps -match '^\s*SDL3\.dll\s*$') { + # Where the linked SDL3 lives: -Sdl3Prefix, else the prefix CMake found it in, + # else PATH. A development package holds one per architecture (lib\x64, ...). + $roots = @() + if ($Sdl3Prefix) { $roots += $Sdl3Prefix } + $cache = Join-Path $Build "CMakeCache.txt" + $dirLine = Select-String -Path $cache -Pattern '^SDL3_DIR:[A-Z]+=(.+)$' -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($dirLine) { + $d = $dirLine.Matches[0].Groups[1].Value + $roots += $d + foreach ($i in 1..3) { $d = Split-Path -Parent $d; if ($d) { $roots += $d } } + } + $candidates = @() + foreach ($r in $roots) { + if (Test-Path $r) { + $candidates += Get-ChildItem -Path $r -Recurse -Filter SDL3.dll -ErrorAction SilentlyContinue | + ForEach-Object { $_.FullName } + } + } + $onPath = Get-Command SDL3.dll -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($onPath) { $candidates += $onPath.Source } + $archDir = if ($Arch -eq "arm64") { '\\arm64\\' } else { '\\x64\\' } + $dll = ($candidates | Where-Object { $_ -match $archDir } | Select-Object -First 1) + if (-not $dll) { $dll = ($candidates | Where-Object { $_ -notmatch '\\(x86|x64|arm64)\\' } | Select-Object -First 1) } + if (-not $dll) { Die "retro-core-runner needs SDL3.dll and it was not found (pass -Sdl3Prefix, or -NoSdl)" } + Copy-Item -Force $dll (Join-Path $Out "SDL3.dll") + $SdlNote = "shared, copied beside the runner (SDL3.dll, from $dll)" +} + +# ---- check the copy, then print where it is ------------------------------------ +$report = @(& $Runner --version) +if ($LASTEXITCODE -ne 0) { Die "$Runner --version failed (exit $LASTEXITCODE)" } +$report = $report | ForEach-Object { "$_".TrimEnd("`r") } +function Get-Field([string]$key) { + $line = $report | Where-Object { $_ -like "$key *" } | Select-Object -First 1 + if ($line) { return $line.Substring($key.Length + 1) } else { return "" } +} +if (-not ($report -contains "game_package 1")) { Die "$Runner --version has no 'game_package 1' line" } +if ((Get-Field "version") -ne "dev") { Die "$Runner says version '$(Get-Field version)', expected dev" } +if ($Commit -and (Get-Field "commit") -ne $Commit) { Die "$Runner says commit '$(Get-Field commit)', expected $Commit" } +if ((Get-Field "gl") -eq "1") { + if (-not $SdlNote) { $SdlNote = "linked statically" } + $SdlNote = "SDL3 $SdlNote; --gl available" +} else { + if ($Sdl3Prefix) { Die "-Sdl3Prefix $Sdl3Prefix was given, but SDL3 was not found there" } + $SdlNote = "no SDL3; --gl (lending a GL context) unavailable" +} + +Write-Output "build-local: $Platform $Config runner in $Out" +Write-Output "build-local: $SdlNote" +$report | Write-Output +Write-Output "RETRO_CORE_RUNNER=$Runner" diff --git a/scripts/build-local.sh b/scripts/build-local.sh new file mode 100755 index 0000000..43b99f0 --- /dev/null +++ b/scripts/build-local.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# Build retro-core-runner for THIS machine, for local development: the runner a +# developer hands a port or app as an explicit runner (--runner, +# RETRO_CORE_RUNNER). Linux and macOS; scripts/build-local.ps1 is Windows. +# +# Not a release. scripts/package-release.sh is the release path and its gates +# (pinned static SDL3, import allowlist, universal Mac binary, archive checks) +# do not apply here. What is kept from it, where it makes sense locally: Release +# by default, a static C++ runtime on Linux, the version stamped as "dev" and +# the commit read from git (with -dirty when tracked files are modified). +# +# SDL3 is optional, found the way CMakeLists.txt finds it (find_package, then +# pkg-config). Without it the runner cannot lend a GL context (no --gl); the +# output says which build you got. A shared SDL3 from outside the system +# library directories is copied beside the runner, which finds it through its +# RUNPATH ($ORIGIN on Linux, @loader_path on macOS). +# +# The runner is checked before its path is printed: its --version must run and +# report "game_package 1". The LAST line on stdout is always +# RETRO_CORE_RUNNER= +# +# usage: scripts/build-local.sh [--debug] [--out DIR] [--build DIR] +# [--sdl3-prefix DIR | --no-sdl] [--jobs N] [--test] [--help] +set -euo pipefail + +die() { echo "build-local: $*" >&2; exit 1; } +say() { echo "build-local: $*" >&2; } + +usage() { + cat <<'EOF' +usage: scripts/build-local.sh [options] + +Build retro-core-runner for this machine (local development, not a release) +and print its path last, as RETRO_CORE_RUNNER=. + + --debug Debug build (default: Release) + --out DIR where the runner is copied + (default: /out/local//) + --build DIR CMake build directory + (default: /build-local, or build-local-debug) + --sdl3-prefix DIR use the SDL3 installed under DIR (it must be found) + --no-sdl build without SDL3 (the runner then has no --gl) + --jobs N parallel build jobs (default: all CPUs) + --test run the test suite (ctest) after building + --help this text + + is linux-x86_64, linux-arm64, macos-x86_64 or macos-arm64. +Relative paths are taken against the current directory. +EOF +} + +CONFIG=Release OUT="" BUILD="" SDL3_PREFIX="" NO_SDL=0 JOBS="" RUN_TESTS=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --debug) CONFIG=Debug; shift ;; + --out) [[ $# -ge 2 ]] || die "--out needs a directory"; OUT="$2"; shift 2 ;; + --build) [[ $# -ge 2 ]] || die "--build needs a directory"; BUILD="$2"; shift 2 ;; + --sdl3-prefix) [[ $# -ge 2 ]] || die "--sdl3-prefix needs a directory"; SDL3_PREFIX="$2"; shift 2 ;; + --no-sdl) NO_SDL=1; shift ;; + --jobs) [[ $# -ge 2 ]] || die "--jobs needs a number"; JOBS="$2"; shift 2 ;; + --test) RUN_TESTS=1; shift ;; + -h|--help) usage; exit 0 ;; + *) die "unknown argument '$1' (see --help)" ;; + esac +done +[[ -z "${SDL3_PREFIX}" || "${NO_SDL}" == 0 ]] || die "--sdl3-prefix and --no-sdl contradict each other" +[[ -z "${JOBS}" || "${JOBS}" =~ ^[1-9][0-9]*$ ]] || die "--jobs: want a positive number, got '${JOBS}'" + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +case "$(uname -s)" in + Linux) OS=linux ;; + Darwin) OS=macos ;; + *) die "$(uname -s): this script builds on Linux and macOS; use scripts/build-local.ps1 on Windows" ;; +esac +case "$(uname -m)" in + x86_64|amd64) ARCH=x86_64 ;; + aarch64|arm64) ARCH=arm64 ;; + *) die "$(uname -m): no platform name for this architecture" ;; +esac +PLATFORM="${OS}-${ARCH}" + +# Paths from the command line are relative to where the script was run. +abspath() { mkdir -p "$1" && (cd "$1" && pwd); } +if [[ -z "${BUILD}" ]]; then + BUILD="${ROOT}/build-local" + if [[ "${CONFIG}" == Debug ]]; then BUILD+="-debug"; fi +fi +BUILD="$(abspath "${BUILD}")" +OUT="$(abspath "${OUT:-${ROOT}/out/local/${PLATFORM}}")" +[[ "${OUT}" != "${BUILD}" ]] || die "--out and --build must differ" +if [[ -n "${SDL3_PREFIX}" ]]; then + [[ -d "${SDL3_PREFIX}" ]] || die "--sdl3-prefix ${SDL3_PREFIX}: no such directory" + SDL3_PREFIX="$(cd "${SDL3_PREFIX}" && pwd)" +fi +if [[ -z "${JOBS}" ]]; then + JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)" +fi + +command -v cmake >/dev/null || die "cmake not found" + +# ---- version stamp: "dev", and the commit with -dirty if it has changes --- +COMMIT="" +if git -C "${ROOT}" rev-parse --git-dir >/dev/null 2>&1; then + COMMIT="$(git -C "${ROOT}" rev-parse HEAD)" + # Tracked files only, as git describe --dirty counts them. + git -C "${ROOT}" diff --quiet HEAD -- 2>/dev/null || COMMIT+="-dirty" +fi + +# ---- configure -------------------------------------------------------------- +cmake_args=( + -S "${ROOT}" -B "${BUILD}" + "-DCMAKE_BUILD_TYPE=${CONFIG}" + "-DRETRO_RUNTIME_VERSION=dev" + "-DRETRO_RUNTIME_COMMIT=${COMMIT}" # empty: CMake reads git itself, else "unknown" +) +if command -v ninja >/dev/null; then cmake_args+=(-G Ninja); fi +case "${OS}" in + # As the release does: no libstdc++/libgcc_s to find at run time. + linux) cmake_args+=("-DCMAKE_EXE_LINKER_FLAGS=-static-libstdc++ -static-libgcc" + "-DCMAKE_INSTALL_RPATH=\$ORIGIN") ;; + macos) cmake_args+=("-DCMAKE_INSTALL_RPATH=@loader_path") ;; +esac +if [[ "${NO_SDL}" == 1 ]]; then + cmake_args+=(-DCMAKE_DISABLE_FIND_PACKAGE_SDL3=ON -DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=ON) +else + cmake_args+=(-DCMAKE_DISABLE_FIND_PACKAGE_SDL3=OFF -DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=OFF) + if [[ -n "${SDL3_PREFIX}" ]]; then + cmake_args+=("-DCMAKE_PREFIX_PATH=${SDL3_PREFIX}") + SDL3_CONFIG="$(find "${SDL3_PREFIX}" -name SDL3Config.cmake -print -quit)" + [[ -z "${SDL3_CONFIG}" ]] || cmake_args+=("-DSDL3_DIR=$(dirname "${SDL3_CONFIG}")") + fi +fi + +# A build directory configured with other SDL choices, or another generator, +# keeps them in its cache: start it afresh when the choices change. +STAMP="${BUILD}/build-local.args" +if [[ -f "${STAMP}" && "$(cat "${STAMP}")" != "$(printf '%s\n' "${cmake_args[@]}")" ]]; then + say "configuration changed since the last build in ${BUILD}; reconfiguring from scratch" + rm -rf "${BUILD}/CMakeCache.txt" "${BUILD}/CMakeFiles" +fi + +say "${PLATFORM}, ${CONFIG}, build ${BUILD}" +cmake "${cmake_args[@]}" >&2 +printf '%s\n' "${cmake_args[@]}" > "${STAMP}" +cmake --build "${BUILD}" --parallel "${JOBS}" >&2 + +if [[ "${RUN_TESTS}" == 1 ]]; then + ctest --test-dir "${BUILD}" --output-on-failure --parallel "${JOBS}" >&2 +fi + +# ---- install into a stage (sets the RUNPATH), then copy flat into --out ----- +STAGE="${BUILD}/build-local-stage" +rm -rf "${STAGE}" +cmake --install "${BUILD}" --prefix "${STAGE}" >/dev/null +RUNNER="${OUT}/retro-core-runner" +cp "${STAGE}/bin/retro-core-runner" "${RUNNER}" +chmod 0755 "${RUNNER}" +cp "${ROOT}/LICENSE" "${OUT}/LICENSE" + +# ---- SDL3: none, static, system, or shared and copied beside the runner ----- +# The build-tree binary still carries the build RPATH, so its SDL3 resolves to +# the library it was linked against. +BUILT="${BUILD}/retro-core-runner" +SDL_NOTE="" +sdl_lib="" +rm -f "${OUT}"/libSDL3.so* "${OUT}"/libSDL3*.dylib # from an earlier build +case "${OS}" in + linux) + sdl_lib="$(ldd "${BUILT}" 2>/dev/null | awk '$1 ~ /^libSDL3\.so/ {print $1 " " $3; exit}')" + if [[ -n "${sdl_lib}" ]]; then + soname="${sdl_lib%% *}" path="${sdl_lib#* }" + [[ -f "${path}" ]] || die "retro-core-runner needs ${soname}, which does not resolve" + case "${path}" in + /lib/*|/lib64/*|/usr/lib/*|/usr/lib64/*) + SDL_NOTE="shared, from the system (${path})" ;; + *) + cp -L "${path}" "${OUT}/${soname}" + SDL_NOTE="shared, copied beside the runner (${soname}, from ${path})" ;; + esac + fi + ;; + macos) + sdl_lib="$(otool -L "${BUILT}" | awk '/libSDL3/ {print $1; exit}')" + if [[ -n "${sdl_lib}" ]]; then + name="$(basename "${sdl_lib}")" path="" + if [[ "${sdl_lib}" == @rpath/* ]]; then + while read -r dir; do + if [[ -f "${dir}/${sdl_lib#@rpath/}" ]]; then path="${dir}/${sdl_lib#@rpath/}"; break; fi + done < <(otool -l "${BUILT}" | awk '/LC_RPATH/ {f=1} f && $1 == "path" {print $2; f=0}') + else + path="${sdl_lib}" + fi + [[ -n "${path}" && -f "${path}" ]] || die "retro-core-runner needs ${sdl_lib}, which does not resolve" + case "${path}" in + /usr/lib/*|/System/*|/opt/homebrew/*|/usr/local/*|/opt/local/*) + SDL_NOTE="shared, from the system or a package manager (${path})" ;; + *) + cp -L "${path}" "${OUT}/${name}" + if [[ "${sdl_lib}" != @rpath/* ]]; then + install_name_tool -change "${sdl_lib}" "@rpath/${name}" "${RUNNER}" + fi + SDL_NOTE="shared, copied beside the runner (${name}, from ${path})" ;; + esac + fi + # An edited Mach-O must be signed again (ad hoc) or arm64 refuses to run it. + codesign --force --sign - "${RUNNER}" >/dev/null 2>&1 || true + ;; +esac + +# ---- check the copy, then print where it is --------------------------------- +report="$("${RUNNER}" --version)" || die "${RUNNER} --version failed" +field() { sed -n "s/^$1 //p" <<<"${report}"; } +grep -qx 'game_package 1' <<<"${report}" || + die "${RUNNER} --version has no 'game_package 1' line" +[[ "$(field version)" == dev ]] || die "${RUNNER} says version '$(field version)', expected dev" +[[ -z "${COMMIT}" || "$(field commit)" == "${COMMIT}" ]] || + die "${RUNNER} says commit '$(field commit)', expected ${COMMIT}" +if [[ "$(field gl)" == 1 ]]; then + SDL_NOTE="SDL3 ${SDL_NOTE:-linked statically}; --gl available" +else + [[ -z "${SDL3_PREFIX}" ]] || die "--sdl3-prefix ${SDL3_PREFIX} was given, but SDL3 was not found there" + SDL_NOTE="no SDL3; --gl (lending a GL context) unavailable" +fi + +echo "build-local: ${PLATFORM} ${CONFIG} runner in ${OUT}" +echo "build-local: ${SDL_NOTE}" +echo "${report}" +echo "RETRO_CORE_RUNNER=${RUNNER}"