|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# btools/build - Build linapple for a distro/frontend |
| 4 | +# |
| 5 | +# See usage() for details. |
| 6 | +# |
| 7 | +set -Eeuo pipefail |
| 8 | +trap 'ec=$?; echo 1>&2 "INTERNAL ERROR: ec=$ec line=$LINENO cmd=$BASH_COMMAND"; |
| 9 | + exit $ec;' ERR |
| 10 | + |
| 11 | +error() { echo -e 1>&2 "● ERROR: $(basename "$0"):" "$@"; } |
| 12 | +die() { local ec=$1; shift; error "$@"; exit $ec; } |
| 13 | + |
| 14 | +# $1 is a timestamp dir/file. If modified within the last hour, |
| 15 | +# return 1. Otherwise touch it and return 0. |
| 16 | +needs_update() { |
| 17 | + local timestamp="$1"; shift |
| 18 | + [[ -e $timestamp ]] || { touch "$timestamp"; return 0; } |
| 19 | + local age=$(( $(date +%s) - $(stat -c %Y "$timestamp") )) |
| 20 | + (( age <= ${1:-3600} )) || { touch "$timestamp"; return 0; } |
| 21 | + return 1 |
| 22 | +} |
| 23 | + |
| 24 | +sudo= |
| 25 | +set_sudo() { |
| 26 | + declare -g sudo |
| 27 | + [[ $(id -u) -eq 0 ]] || sudo=sudo |
| 28 | +} |
| 29 | + |
| 30 | +chown_builddir() { # Used by --chown-to. |
| 31 | + local to="$1"; shift |
| 32 | + chown -R "$to" "$PROJDIR/$BUILDDIR_REL" \ |
| 33 | + || echo 1>&2 "WARNING: chown -R '$to' '$BUILDDIR_REL' failed" |
| 34 | +} |
| 35 | + |
| 36 | +#################################################################### |
| 37 | + |
| 38 | +# Set variables defined in /etc/os-release, in particular $ID and |
| 39 | +# $VERSION_ID. If /etc/os-release doesn't exist these may be synthesised |
| 40 | +# through other means, otherwise this exits with an error. |
| 41 | +os_release() { |
| 42 | + [[ -r /etc/os-release ]] \ |
| 43 | + || die 1 "/etc/os-release not found; cannot autodetect distro" |
| 44 | + source /etc/os-release |
| 45 | + [[ -n ${ID:-} && -n ${VERSION_ID:-} ]] \ |
| 46 | + || die 5 '$ID and/or $VERSION_ID not set in /etc/os-release' |
| 47 | +} |
| 48 | + |
| 49 | +# We know os_release but no FRONTEND specified: choose a default or |
| 50 | +# exit with an error indicating we have no default. |
| 51 | +default_frontend() { |
| 52 | + local rel=$VERSION_ID |
| 53 | + case $ID in |
| 54 | + debian) [[ $rel -ge 13 ]] && echo sdl3 || echo sdl2;; |
| 55 | + ubuntu) [[ $rel > 24.04 ]] && echo sdl3 || echo sdl2;; |
| 56 | + *) die 4 "No default frontends known for distro $ID";; |
| 57 | + esac |
| 58 | +} |
| 59 | + |
| 60 | +install_prerequisites() { |
| 61 | + echo '━━━━━ Install prerequisites' |
| 62 | + |
| 63 | + # This needs to be on a filesystem local to the container, if we're using |
| 64 | + # one, so that we don't honour a stale timestamp in a new container. |
| 65 | + local tsfile="/tmp/linappleii-linapple/prereq-$ID-$VERSION_ID-$FRONTEND" |
| 66 | + mkdir -p "$(dirname "$tsfile")" |
| 67 | + # If we've updated and re-installed in the last hour, just skip. |
| 68 | + needs_update "$tsfile" \ |
| 69 | + || { echo 'updated in last hour; skipping'; return 0; } |
| 70 | + |
| 71 | + local known_ids=( # all these must be in the `case` below. |
| 72 | + debian ubuntu |
| 73 | + ) |
| 74 | + local id k |
| 75 | + for distro in "$ID" ${ID_LIKE:-}; do # split $ID_LIKE |
| 76 | + for k in "${known_ids[@]}"; do |
| 77 | + # Stop both loops at first one we know. |
| 78 | + [[ $distro == "$k" ]] && break 2 |
| 79 | + done |
| 80 | + die 4 "This system's IDs match no known distros" \ |
| 81 | + "\n• Known:" "${known_ids[@]}" \ |
| 82 | + "\n• ID=$ID ID_LIKE=${ID_LIKE:-}" |
| 83 | + done |
| 84 | + |
| 85 | + case "$distro" in |
| 86 | + debian|ubuntu) install_deps_debian;; |
| 87 | + *) die 10 "Internal error: set unknown distro '$distro'";; |
| 88 | + esac |
| 89 | +} |
| 90 | + |
| 91 | +install_deps_debian() { |
| 92 | + echo '───── apt update' |
| 93 | + $sudo apt update && $sudo touch /var/lib/apt/lists |
| 94 | + echo '───── apt install' |
| 95 | + $sudo apt install -y -q \ |
| 96 | + git g++ cmake \ |
| 97 | + libzip-dev libcurl4-openssl-dev zlib1g-dev imagemagick |
| 98 | + case "$FRONTEND" in |
| 99 | + sdl1.2) $sudo apt install -y -q libsdl1.2-dev;; |
| 100 | + sdl2) $sudo apt install -y -q libsdl2-dev libsdl2-image-dev;; |
| 101 | + sdl3) $sudo apt install -y -q libsdl3-dev libsdl3-image-dev;; |
| 102 | + *) die 4 "Unknown frontend: $FRONTEND";; |
| 103 | + esac |
| 104 | +} |
| 105 | + |
| 106 | +#################################################################### |
| 107 | +# Usage and argument parsing. |
| 108 | + |
| 109 | +usage() { |
| 110 | + local exitcode=$1; shift |
| 111 | + [[ -n ${1:-} ]] && error "$@" |
| 112 | + cat <<_____ |
| 113 | +Usage: $(basename "$0") [OPTS] [-D DISTRO] [FRONTEND] |
| 114 | +For the current distro and given (or default) frontend, installs |
| 115 | +prerequisites, builds linapple and runs tests. |
| 116 | +
|
| 117 | +• The distro is auto-detected from /etc/os-release, but can be overridden with |
| 118 | + e.g. '-D ubuntu:24.04' (but you should never need this). |
| 119 | +Options: |
| 120 | + -h,--help Print this message. |
| 121 | + -D ID:VERSION_ID Use given distro information instead of autodetecting |
| 122 | + from /etc/os-release. E.g. 'ubuntu:24.04'. |
| 123 | + --prereq-only Only install build prerequisites; do not build. |
| 124 | + --chown OWNER On exit, chown -R the build dir to OWNER (UID:GID or |
| 125 | + user:group). For container builds writing to a bind- |
| 126 | + mounted host dir; failures warn but do not abort. |
| 127 | +_____ |
| 128 | + exit $exitcode |
| 129 | +} |
| 130 | + |
| 131 | +chown_to= |
| 132 | +force_distro= |
| 133 | +prereq_only=false |
| 134 | +FRONTEND= |
| 135 | +while [[ $# -gt 0 ]]; do case "$1" in |
| 136 | + -h|--help) usage 0;; |
| 137 | + -D) shift; force_distro="$1"; shift;; |
| 138 | + --prereq-only) shift; prereq_only=true;; |
| 139 | + --chown-to) shift; chown_to="$1"; shift;; |
| 140 | + -*) usage 2 "Unknown option: $1";; |
| 141 | + --) break;; |
| 142 | + *) break;; |
| 143 | +esac; done |
| 144 | +[[ $# -gt 0 ]] && { FRONTEND="$1"; shift; } |
| 145 | +[[ $# -eq 0 ]] || usage 2 "Too many arguments." |
| 146 | + |
| 147 | +if [[ -z $force_distro ]]; then |
| 148 | + os_release |
| 149 | +else |
| 150 | + if [[ $force_distro =~ ^([^:]+):([^:]+)$ ]]; then |
| 151 | + ID=${BASH_REMATCH[1]} |
| 152 | + VERSION_ID=${BASH_REMATCH[2]} |
| 153 | + else |
| 154 | + die 2 "DISTRO must be 'name:ver'" |
| 155 | + fi |
| 156 | +fi |
| 157 | + |
| 158 | +[[ -n $FRONTEND ]] || FRONTEND=$(default_frontend) |
| 159 | + |
| 160 | +#################################################################### |
| 161 | + |
| 162 | +PROJDIR=$(command cd $(dirname "$0")/.. && pwd -P) |
| 163 | +# No colon in builddir path because even on Linux it makes Cmake upset. |
| 164 | +# This is relative because we need to `cd` to `cmake` anyway, so why |
| 165 | +# not keep it short for messages etc. |
| 166 | +echo "▶ DISTRO=$ID:$VERSION_ID FRONTEND=$FRONTEND" |
| 167 | + |
| 168 | +install_prerequisites |
| 169 | +$prereq_only && exit 0 |
| 170 | + |
| 171 | +command cd "$PROJDIR" |
| 172 | +echo '━━━━━ Cmake setup' |
| 173 | +BUILDDIR_REL="build/$ID$VERSION_ID-$FRONTEND" |
| 174 | +echo "▶ BUILDDIR=$BUILDDIR_REL" |
| 175 | +[[ -n $chown_to ]] && trap "chown_builddir $chown_to" EXIT |
| 176 | +cmake -B "$BUILDDIR_REL" -DFRONTEND=$FRONTEND |
| 177 | +echo '━━━━━ Cmake build' |
| 178 | +echo ● cmake --quiet --build "$BUILDDIR_REL" -j$(nproc) |
| 179 | +cmake --build "$BUILDDIR_REL" -j$(nproc) |
0 commit comments