#!/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): # scripts/run.sh [--preset ] [-- program-args...] # # --preset Run the build made with that preset (out/build/). # Without it: the build scripts/build.sh made last, or # the only one that exists. Must come first if given at # all; --preset= also works. # # Examples: # scripts/run.sh hello_exe # scripts/run.sh --preset linux-x64-clang hello_exe # 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 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 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 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 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 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 scripts/build.sh?)" fi echo echo "Examples:" echo " scripts/run.sh hello_exe" echo " scripts/run.sh --preset linux-x64-clang hello_exe" echo " 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 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 scripts/build.sh${PRESET:+ --preset $PRESET}." >&2 exit 1 fi # A cross build's toolchain file (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 # 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 " (scripts/build.sh --preset ) so the toolchain picks it up." >&2 fi exit "$_pmn_status"