Skip to content

Commit dbdfd14

Browse files
committed
WIP Test,btools/: XXX
XXX Actually, the --prereq-only has to be split out to a separate "install-deps" file so we don't have to always re-install all deps in the image when we change btools/build. I guess that means extraction of common routines to btools/common.bash to be 'source'd?
1 parent 4507063 commit dbdfd14

3 files changed

Lines changed: 323 additions & 0 deletions

File tree

Test

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env bash
2+
set -Eeuo pipefail
3+
trap 'ec=$?; echo 1>&2 "INTERNAL ERROR: ec=$ec line=$LINENO cmd=$BASH_COMMAND";
4+
exit $ec;' ERR
5+
6+
####################################################################
7+
8+
error() { echo -e 1>&2 "● ERROR: $(basename "$0"):" "$@"; }
9+
die() { local ec=$1; shift; error "$@"; exit $ec; }
10+
11+
# Given $1 as a {distro}-{release}-{frontend} string,
12+
# set globals $distro $release $frontend to its components,
13+
# and $platform to colon-separated $distro:$release.
14+
# These are implicit parameters to many routines.
15+
parse_build_spec() {
16+
declare -g platform distro release frontend
17+
local spec="$1"; shift
18+
[[ $spec =~ ^([^-]+)-([^-]+)-([^-]+)$ ]] || die 2 "Bad build spec: $spec"
19+
distro="${BASH_REMATCH[1]}"
20+
release="${BASH_REMATCH[2]}"
21+
frontend="${BASH_REMATCH[3]}"
22+
platform="$distro:$release"
23+
}
24+
25+
docker_unavailable() {
26+
die 3 'Cannot run $docker due to setup_docker() failure (see above).'
27+
}
28+
29+
setup_docker() {
30+
declare -g docker=docker
31+
if ! $docker --version >/dev/null 2>&1; then
32+
echo 1>&2 "WARNING: Cannot run '$docker' command. Check path?"
33+
docker=docker_unavailable
34+
elif ! $docker info >/dev/null 2>&1; then
35+
docker='sudo docker'
36+
if ! sudo -v 2>/dev/null; then
37+
echo 1>&2 "WARNING: Cannot sudo to run '$docker'; start proxy?"
38+
docker=docker_unavailable
39+
fi
40+
fi
41+
}
42+
43+
####################################################################
44+
45+
build_image() {
46+
echo "───── Build image $platform"
47+
# XXX Actually, it would be nice to use a single image for all
48+
# frontends, but we'd have to figure out how we pre-install all
49+
# the frontend deps, rather than just one.
50+
declare -g image_name="linapple-build" \
51+
image_tag="$distro-$release-$frontend"
52+
$docker build \
53+
--tag "$image_name:$image_tag" \
54+
--build-arg PLATFORM="$platform" \
55+
--build-arg FRONTEND="$frontend" \
56+
"$PROJDIR/btools"
57+
}
58+
59+
run_container() {
60+
echo "───── Run container"
61+
# --chown-to leaves the bind-mounted build dir owned by the host user.
62+
$docker run --rm \
63+
--volume "$PROJDIR:$PROJDIR" \
64+
--workdir "$PROJDIR" \
65+
"$image_name:$image_tag" \
66+
btools/build --chown-to "$(id -u):$(id -g)" "$frontend"
67+
}
68+
69+
####################################################################
70+
# Usage and argparse.
71+
72+
# Default list if no distros given as $@.
73+
# XXX but how do we provide non-default SDLs?
74+
default_builds=(
75+
debian-13-sdl3 debian-12-sdl2 debian-12-sdl1.2
76+
#debian:12 debian:13 ubuntu:24.04 ubuntu:26.04
77+
#fedora:38 fedora::43
78+
#arch:latest
79+
)
80+
81+
usage() {
82+
local exitcode=$1; shift
83+
[[ -n ${1:-} ]] && error "$@"
84+
cat <<_____
85+
Usage: $(basename "$0") --current [BUILD-ARGS]
86+
$(basename "$0") [OPTS] [BUILD-SPEC...]
87+
88+
Calls btools/build for one or more Linux build specifications, which are in
89+
{distro}-{release}-{frontend} format, e.g. 'ubuntu-24.04-sdl2'. All
90+
builds (whether local or in containers) are placed under
91+
'build/{distro}-{release}-{frontend}/'.
92+
93+
With --current, does a build for the current host rather than using
94+
containers. BUILD-ARGS are passed to btools/build. (Run 'btools/build -h'
95+
for further information.)
96+
97+
Otherwise starts a container for each BUILD-SPEC and does the build and
98+
unit tests in that container, leaving the results under build/ on the host.
99+
100+
The default build specs are: ${default_builds[@]}.
101+
102+
_____
103+
exit $exitcode
104+
}
105+
106+
build_current=false
107+
while [[ $# -gt 0 ]]; do case "$1" in
108+
-h|--help) usage 0;;
109+
--current) shift; build_current=true;;
110+
-*) usage 2 "Unknown option: $1";;
111+
--) break;;
112+
*) break;;
113+
esac; done
114+
115+
####################################################################
116+
# Main
117+
118+
PROJDIR=$(command cd $(dirname "$0") && pwd -P)
119+
120+
$build_current && {
121+
"$PROJDIR/btools/build" "$@"
122+
exit 0
123+
}
124+
125+
setup_docker
126+
for build_spec in "${@:-${default_builds[@]}}"; do
127+
echo "━━━━━ $build_spec"
128+
parse_build_spec "$build_spec" # sets $distro $release $frontend $platform
129+
build_image
130+
run_container
131+
done
132+
133+
# XXX --current vs. --all ?
134+
# XXX -d DISTRO -f FRONTEND (either implies --current?)
135+
# no, maybe better limits --all? patterns? `-d deb` for deb*?

btools/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# check=skip=InvalidDefaultArgInFrom $PLATFORM unset produces good error
2+
ARG PLATFORM
3+
FROM ${PLATFORM}
4+
ARG FRONTEND PLATFORM
5+
# Cache the installed dependencies in the image so that, at least for
6+
# a while, the build based on this image will find all prerequisites
7+
# already installed, speeding the build.
8+
COPY build /tmp/
9+
RUN /tmp/build --prereq-only ${FRONTEND} && rm /tmp/build

btools/build

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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

Comments
 (0)