#!/usr/bin/env bash # Run an executable module by its CMake target name (as registered in # that module's own CMakeLists.txt), resolved via the manifest that # _write_module_manifest() generates at configure time -- so this script # never needs updating when modules are added, removed, or renamed. # # Usage (from the project root): # breeze/scripts/run.sh [--preset ] [-- program-args...] # # --preset Run the build made with that preset (out/build/). # Without it: the build breeze/scripts/build.sh made last, or # the only one that exists. Must come first if given at # all; --preset= also works. # # Examples: # breeze/scripts/run.sh hello_exe # breeze/scripts/run.sh --preset linux-x64-clang hello_exe # breeze/scripts/run.sh farewell_exe -- Ale # # Set BUILD_DIR to run out of an exact build directory instead. # # An executable built for another architecture or OS -- linux-arm64-* on an # x64 machine, windows-*-mingw* from Linux -- is run under the emulator its # toolchain registered (qemu-user, wine) if one was installed when the build # was configured; otherwise it can only run on the target itself. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR/../.." # _pmn_read_config, _pmn_resolve_build_dir, _pmn_record_build_dir. source "$SCRIPT_DIR/_common.sh" # --preset must come first if given at all, same as breeze/scripts/build.sh. PRESET="" case "${1:-}" in --preset) if [ "$#" -lt 2 ] || [[ "$2" == -* ]]; then echo "error: --preset requires a value, e.g. --preset linux-x64-gcc" >&2 exit 1 fi PRESET="$2" shift 2 ;; --preset=*) PRESET="${1#--preset=}" shift if [ -z "$PRESET" ]; then echo "error: --preset= requires a value, e.g. --preset=linux-x64-gcc" >&2 exit 1 fi ;; esac # BUILD_DIR from the environment beats everything; otherwise --preset, else # whichever build was made last (see breeze/cmake/BuildDir.cmake). Help and the # usage message look it up best-effort -- they should still print on a # fresh checkout with nothing built yet -- while actually running # something needs a real answer, and stops with the reason if there isn't # one. _pmn_try_build_dir() { if [ -z "${BUILD_DIR:-}" ]; then BUILD_DIR="$(_pmn_resolve_build_dir "$PRESET" 2>/dev/null || true)" fi } list_targets() { local manifest [ -n "${BUILD_DIR:-}" ] || return 0 for manifest in "${BUILD_DIR}"/module_manifest-*.txt; do [ -e "$manifest" ] || continue cut -d= -f1 "$manifest" done | sort -u } usage() { _pmn_try_build_dir echo "Usage: $0 [--preset ] [-- program-args...]" >&2 echo >&2 echo "Available executable targets:" >&2 local targets targets="$(list_targets)" if [ -n "$targets" ]; then echo "$targets" | sed 's/^/ /' >&2 else echo " (none found -- did you run breeze/scripts/build.sh?)" >&2 fi exit 1 } show_help() { _pmn_try_build_dir echo "Usage: $0 [--preset ] [-- program-args...]" echo echo "Runs an executable module by its CMake target name, resolved via the" echo "manifest breeze/scripts/build.sh generates at configure time -- see the" echo "comment at the top of this script." echo echo " --preset Run the build made with that preset. Default: the" echo " build breeze/scripts/build.sh made last." echo echo "Available executable targets:" local targets targets="$(list_targets)" if [ -n "$targets" ]; then echo "$targets" | sed 's/^/ /' else echo " (none found -- did you run breeze/scripts/build.sh?)" fi echo echo "Examples:" echo " breeze/scripts/run.sh hello_exe" echo " breeze/scripts/run.sh --preset linux-x64-clang hello_exe" echo " breeze/scripts/run.sh farewell_exe -- Ale" exit 0 } case "${1:-}" in -h|--help) show_help ;; esac [ $# -ge 1 ] || usage TARGET_NAME="$1" shift if [ "${1:-}" = "--" ]; then shift fi if [ -z "${BUILD_DIR:-}" ]; then # Explains itself and stops if there's nothing to pick. BUILD_DIR="$(_pmn_resolve_build_dir "$PRESET")" fi MANIFEST="" for m in "${BUILD_DIR}"/module_manifest-*.txt; do [ -e "$m" ] || continue MANIFEST="$m" break done if [ -z "$MANIFEST" ]; then echo "error: no build manifest found in '${BUILD_DIR}'. Run breeze/scripts/build.sh${PRESET:+ --preset $PRESET} first." >&2 exit 1 fi BIN_PATH="$(grep "^${TARGET_NAME}=" "$MANIFEST" | head -n1 | cut -d= -f2- || true)" if [ -z "$BIN_PATH" ]; then echo "error: no executable target named '${TARGET_NAME}' in '${BUILD_DIR}'." >&2 echo "Available targets:" >&2 list_targets | sed 's/^/ /' >&2 exit 1 fi if [ ! -x "$BIN_PATH" ]; then echo "error: '${BIN_PATH}' was not found or is not executable. Rebuild with breeze/scripts/build.sh${PRESET:+ --preset $PRESET}." >&2 exit 1 fi # _pmn_pe_kind : what kind of Windows program this is, read from the # Machine field of its PE header -- the two bytes after the "PE\0\0" # signature, which e_lfanew (the 4 bytes at offset 0x3C) points at. Prints # x86 (32-bit), x64 (64-bit x86), or other (ARM64, a file that isn't a PE at # all, one that can't be read). `od` rather than `file`, so nothing has to be # installed, and byte by byte so the host's endianness doesn't matter. _pmn_pe_kind() { local o0 o1 o2 o3 s0 s1 s2 s3 m0 m1 off read -r o0 o1 o2 o3 <<< "$(od -An -tu1 -j60 -N4 "$1" 2>/dev/null || true)" if [ -z "${o3:-}" ]; then echo other return 0 fi off=$(( o0 + o1 * 256 + o2 * 65536 + o3 * 16777216 )) read -r s0 s1 s2 s3 m0 m1 <<< "$(od -An -tu1 -j"$off" -N6 "$1" 2>/dev/null || true)" # 80 69 0 0 is "PE\0\0". if [ "${s0:-}" = 80 ] && [ "${s1:-}" = 69 ] && [ "${s2:-}" = 0 ] && [ "${s3:-}" = 0 ] && [ -n "${m1:-}" ]; then case $(( m0 + m1 * 256 )) in 332) echo x86; return 0 ;; # 0x014c IMAGE_FILE_MACHINE_I386 34404) echo x64; return 0 ;; # 0x8664 IMAGE_FILE_MACHINE_AMD64 esac fi echo other } # _pmn_wine_lacks_wine64 : 0 if is Debian/Ubuntu's helper script # and this machine has that packaging's 32-bit loader but no 64-bit one -- the # state described below -- so that a 64-bit program cannot run here whatever # else is tried. Recognised by layout rather than by which version of wine it # is: a script under /usr/bin (alternatives -> wine-stable or # wine-development) beside a loader directory that holds wine but no wine64. _pmn_wine_lacks_wine64() { local emu real dir emu="$(command -v "$1" 2>/dev/null)" || return 1 real="$(readlink -f "$emu" 2>/dev/null)" || real="$emu" case "$real" in /usr/bin/wine-development) dir=/usr/lib/wine-development ;; /usr/bin/*) dir=/usr/lib/wine ;; *) return 1 ;; esac [ "$(head -c2 "$real" 2>/dev/null)" = '#!' ] && [ -x "$dir/wine" ] && [ ! -x "$dir/wine64" ] } # _pmn_wine64_loader : prints a loader that can run 64-bit Windows # programs and create a 64-bit prefix for them, given the plain `wine` the # toolchain registered -- or fails if it knows of nothing better than . # # Plain `wine` isn't always that. On Debian and Ubuntu it is a helper script # (alternatives -> wine-stable / wine-development), not a loader, and the one # in wine 9.0's packaging runs the 32-bit loader whenever wine32 is installed # -- which breeze/scripts/setup.sh makes sure of, for the 32-bit preset. A 32-bit # loader with no wine64 to hand over to creates a 32-bit prefix even under # WINEARCH=win64 and then refuses it: "WINEARCH set to win64 but '' # is a 32-bit installation". That is what a machine looks like after # `apt-get install wine wine32:i386` (wine only depends on "wine64 | wine32", # so wine64 gets left out): reproduced, with that exact output, from that # command. The 64-bit loader is a separate file there, /usr/lib/wine/wine64, # and nothing puts it on PATH. _pmn_wine64_loader() { local emu real w64 emu="$(command -v "$1" 2>/dev/null)" || return 1 real="$(readlink -f "$emu" 2>/dev/null)" || real="$emu" # A wine64 right next to it: Arch, Fedora, WineHQ's /opt/wine-*/bin, ... for w64 in "${real%/*}/wine64" "${emu%/*}/wine64"; do if [ -x "$w64" ]; then printf '%s\n' "$w64" return 0 fi done case "${real##*/}" in wine-stable) w64=/usr/lib/wine/wine64 ;; wine-development) w64=/usr/lib/wine-development/wine64 ;; *) return 1 ;; esac if [ -x "$w64" ]; then printf '%s\n' "$w64" return 0 fi return 1 } # A cross build's toolchain file (breeze/cmake/toolchains/) registers an emulator -- # qemu-user for another CPU, wine for Windows -- as # CMAKE_CROSSCOMPILING_EMULATOR when one is installed, which is also what # lets ctest run the same binaries. Use it the same way. It's a # ";"-separated list (the emulator, then its arguments). RUN_PREFIX=() if [ -f "${BUILD_DIR}/CMakeCache.txt" ]; then _pmn_emulator="$(sed -n 's/^CMAKE_CROSSCOMPILING_EMULATOR:[A-Z]*=//p' "${BUILD_DIR}/CMakeCache.txt")" if [ -n "$_pmn_emulator" ]; then IFS=';' read -r -a RUN_PREFIX <<< "$_pmn_emulator" fi fi # Wine refuses to start at all without a valid XDG_RUNTIME_DIR -- "error: # XDG_RUNTIME_DIR is invalid or not set in the environment" -- even for a # console-only .exe that needs no desktop integration whatsoever; a # session manager normally sets this, which containers (confirmed on a # distrobox one) typically have no reason to run. Wine only checks that # the directory exists with the right ownership/permissions, not that it # came from a real session, so a private one under TMPDIR is enough to # get past this. Only for wine, and only if nothing is already set -- a # real desktop session already has a real one, left alone. case "${RUN_PREFIX[0]:-}" in */wine|wine|*/wine64|wine64) if [ -z "${XDG_RUNTIME_DIR:-}" ]; then export XDG_RUNTIME_DIR="${TMPDIR:-/tmp}/xdg-runtime-$(id -u)" mkdir -p "$XDG_RUNTIME_DIR" 2>/dev/null chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null fi # 32-bit or 64-bit, read from the program itself. Everything below # keeps the two apart, because a wine prefix is one or the other from # the moment it is created and is never shared safely between them. _pmn_kind="$(_pmn_pe_kind "$BIN_PATH")" # A 64-bit program needs a loader that can build and run a 64-bit # prefix, which plain `wine` isn't everywhere (see # _pmn_wine64_loader). if [ "$_pmn_kind" = x64 ] && [ "${RUN_PREFIX[0]##*/}" = wine ]; then if _pmn_w64="$(_pmn_wine64_loader "${RUN_PREFIX[0]}")"; then RUN_PREFIX[0]="$_pmn_w64" elif _pmn_wine_lacks_wine64 "${RUN_PREFIX[0]}"; then # Nothing below can make this work, and trying only ends in # wine's own "WINEARCH set to win64 but ... is a 32-bit # installation" -- which says nothing about the cause. echo "error: ${TARGET_NAME} is a 64-bit Windows program, but this machine has only the" >&2 echo " 32-bit wine, which can neither run it nor create the 64-bit prefix it" >&2 echo " needs. Debian and Ubuntu package the 64-bit part separately:" >&2 echo " sudo apt install wine64 (breeze/scripts/setup.sh installs it too)" >&2 exit 1 fi fi # Running Debian's own loader directly -- chosen just above, or # already what the build registered -- means going around the helper # script that normally runs it, so do the two things that script does # for wine64: point WINELOADER at it, and quiet wine's "fixme:" # chatter with WINEDEBUG=fixme-all. Each only if nothing set it. case "${RUN_PREFIX[0]}" in /usr/lib/wine/wine64|/usr/lib/wine-development/wine64) if [ -z "${WINELOADER:-}" ]; then export WINELOADER="${RUN_PREFIX[0]}"; fi if [ -z "${WINEDEBUG:-}" ]; then export WINEDEBUG=fixme-all; fi ;; esac # Default wine prefix is ~/.wine, shared with every other use of # wine on the machine -- scoping it under .cache/ instead makes a # fresh `rm -rf .cache/wine` the fix for anything that looks like a # wine problem rather than a build one, and keeps this project's # own wine state from mixing with anything else's. One prefix per # kind of program (.cache/wine/x86, .cache/wine/x64): a prefix's # architecture is fixed by whichever program first creates it, so # sharing one made the outcome depend on which preset happened to # run first, and on a wine whose 32-bit loader can only make 32-bit # prefixes, no amount of recreating it helped. Only if WINEPREFIX # isn't already set -- someone who's deliberately pointed it # elsewhere gets left alone, and that includes never deleting it. if [ -z "${WINEPREFIX:-}" ]; then # Older versions used .cache/wine itself as the one prefix. A # prefix has a system.reg at its top, so that is easy to tell # apart from this layout; its content is only a disposable cache. if [ -f "$PWD/.cache/wine/system.reg" ]; then rm -rf "$PWD/.cache/wine" fi export WINEPREFIX="$PWD/.cache/wine/$_pmn_kind" # A 64-bit prefix recorded as 32-bit (a prefix's own system.reg # says which, in a line reading exactly "#arch=win32" or # "#arch=win64") can't be what asked for 64-bit, and wine refuses # to run anything against it once WINEARCH=win64 disagrees -- # so it's rebuilt, since it's this project's own cache. if [ "$_pmn_kind" = x64 ] && [ -f "$WINEPREFIX/system.reg" ] \ && grep -q '^#arch=win32' "$WINEPREFIX/system.reg" 2>/dev/null; then rm -rf "$WINEPREFIX" fi fi # Wine creates the last component of the prefix path and no more, so # with no .cache/ yet (a fresh checkout) it fails: "wine: chdir to # : No such file or directory". mkdir -p "$WINEPREFIX" # A 64-bit prefix is asked for outright, so that one that can't be # made fails at once and by name rather than as a 32-bit prefix that # then can't run the program. Left alone for 32-bit programs: any # prefix runs those, and forcing win32 isn't possible on every wine. if [ "$_pmn_kind" = x64 ] && [ -z "${WINEARCH:-}" ]; then export WINEARCH=win64 fi ;; esac # Which build this came from -- to stderr, so a program's own stdout stays # clean to pipe. echo "==> Running ${TARGET_NAME} (${BUILD_DIR})${RUN_PREFIX[0]:+ under ${RUN_PREFIX[0]##*/}}" >&2 # Not `exec`, so a failure to start at all can be explained below. _pmn_status=0 ${RUN_PREFIX[@]+"${RUN_PREFIX[@]}"} "$BIN_PATH" "$@" || _pmn_status=$? # 126 is the shell's "found it, couldn't execute it" -- which is what running # a binary built for another architecture or OS looks like. if [ "$_pmn_status" -eq 126 ]; then echo "error: '${BIN_PATH}' couldn't be started on this machine. If it is a cross build" >&2 echo " (built for another CPU or OS), run it on the target, or install an" >&2 echo " emulator -- qemu-user for another CPU, wine for Windows -- and reconfigure" >&2 echo " (breeze/scripts/build.sh --preset ) so the toolchain picks it up." >&2 fi exit "$_pmn_status"