]> vilimpoc.org git repositories - dotfiles/blame - setup-linux.sh
dotfiles: install AGI from setup-linux.sh, on Ubuntu and on CachyOS
[dotfiles] / setup-linux.sh
CommitLineData
6f6caf69
MV
1#!/bin/bash
2#
03b846cd 3# setup-linux.sh -- install the rsync build toolchain and AGI.
6f6caf69 4#
03b846cd
MV
5# Tested on Ubuntu 22.04.5 LTS and 26.04 LTS, and on CachyOS. Covers both
6# rsync build systems: the autoconf one (./configure && make) and the CMake
7# one (CMakeLists.txt), plus the optional feature libraries and the manpage
8# generator. Also installs AGI (Android GPU Inspector) from the latest
9# google/agi release: the .deb on Debian/Ubuntu, the .zip on Arch/CachyOS.
6f6caf69
MV
10#
11# Usage:
03b846cd
MV
12# ./setup-linux.sh # rsync required + optional, then AGI
13# ./setup-linux.sh --minimal # rsync required only, no AGI
14# ./setup-linux.sh --no-agi # rsync deps only
15# ./setup-linux.sh --agi-only # AGI and its dependencies only
6f6caf69
MV
16# ./setup-linux.sh --dry-run # show what would be installed
17#
03b846cd
MV
18# AGI_VERSION=3.3.3 ./setup-linux.sh pins a release instead of asking GitHub
19# for the latest one.
20#
21# Safe to re-run: the package managers skip anything already present, and AGI
22# is re-downloaded only when the installed version is not the wanted one.
6f6caf69
MV
23
24set -uo pipefail
25
26MINIMAL=0
27DRY_RUN=0
03b846cd
MV
28WANT_AGI=1
29WANT_RSYNC=1
6f6caf69
MV
30
31for arg in "$@"; do
32 case "$arg" in
03b846cd 33 --minimal) MINIMAL=1; WANT_AGI=0 ;;
6f6caf69 34 --dry-run) DRY_RUN=1 ;;
03b846cd
MV
35 --no-agi) WANT_AGI=0 ;;
36 --agi-only) WANT_RSYNC=0; WANT_AGI=1 ;;
6f6caf69 37 -h|--help)
03b846cd 38 sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'
6f6caf69
MV
39 exit 0 ;;
40 *)
41 echo "setup-linux.sh: unknown option '$arg' (try --help)" >&2
42 exit 2 ;;
43 esac
44done
45
46# ---------------------------------------------------------------- helpers
47
48red() { printf '\033[31m%s\033[0m\n' "$*"; }
49green() { printf '\033[32m%s\033[0m\n' "$*"; }
50bold() { printf '\033[1m%s\033[0m\n' "$*"; }
51
52die() { red "ERROR: $*"; exit 1; }
53
03b846cd
MV
54# ------------------------------------------------- package manager wrapper
55
56# Two families are supported: apt (Debian/Ubuntu) and pacman (Arch/CachyOS).
57PM=""
58if command -v apt-get >/dev/null 2>&1; then
59 PM=apt
60elif command -v pacman >/dev/null 2>&1; then
61 PM=pacman
62else
63 die "found neither apt-get nor pacman -- unsupported distribution."
64fi
65
66pkg_available() {
67 case "$PM" in
68 apt)
69 local cand
70 cand=$(apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/{print $2}')
71 [ -n "$cand" ] && [ "$cand" != "(none)" ] ;;
72 pacman)
73 pacman -Si "$1" >/dev/null 2>&1 ;;
74 esac
75}
6f6caf69 76
03b846cd
MV
77# Echo the first name the distro actually ships. Package names drift between
78# releases (libgtk-3-0 became libgtk-3-0t64 in Ubuntu 24.04, and the JDK
79# version in the archive moves every six months), so nothing is hard-coded.
80pick_pkg() {
81 local p
82 for p in "$@"; do
83 if pkg_available "$p"; then
84 echo "$p"
85 return 0
86 fi
87 done
88 return 1
89}
90
91pkg_install() {
92 [ $# -eq 0 ] && return 0
93 case "$PM" in
94 apt) apt-get install -y "$@" ;;
95 pacman) pacman -S --needed --noconfirm "$@" ;;
96 esac
97}
98
99# ------------------------------------------------------- sanity checks
6f6caf69
MV
100
101DISTRO="unknown"
102RELEASE="unknown"
103if [ -r /etc/os-release ]; then
104 # shellcheck disable=SC1091 # runtime file, not available to the linter
105 . /etc/os-release
106 DISTRO="${ID:-unknown}"
107 RELEASE="${VERSION_ID:-unknown}"
108fi
109
03b846cd 110bold "rsync build dependencies and AGI"
6f6caf69
MV
111echo " distro ......... ${PRETTY_NAME:-$DISTRO $RELEASE}"
112echo " architecture ... $(uname -m)"
03b846cd 113echo " packages ....... $PM"
6f6caf69
MV
114
115case "$DISTRO" in
03b846cd 116ubuntu|debian|linuxmint|pop|arch|cachyos|endeavouros|manjaro) ;;
6f6caf69 117*) echo
03b846cd 118 red "Warning: '$DISTRO' is untested; assuming it behaves like a $PM distro."
6f6caf69
MV
119 echo "Continuing anyway -- package names may differ."
120 ;;
121esac
122
03b846cd
MV
123# AGI ships x86-64 binaries only.
124case "$(uname -m)" in
125x86_64|amd64) ;;
126*) if [ "$WANT_AGI" -eq 1 ]; then
127 red "Warning: AGI publishes x86-64 Linux builds only; skipping it."
128 WANT_AGI=0
129 fi
130 ;;
131esac
132
133# The package managers need root. Re-exec under sudo rather than sprinkling
134# it around, so that a single password prompt covers the whole run.
6f6caf69
MV
135if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" -eq 0 ]; then
136 command -v sudo >/dev/null 2>&1 \
137 || die "not running as root and sudo is not installed."
138 echo
139 echo "Re-running under sudo..."
140 # Plain sudo, not "sudo -E": some sudoers configs refuse to preserve the
141 # environment, and the exports below happen in the root instance anyway.
03b846cd 142 exec sudo AGI_VERSION="${AGI_VERSION:-}" "$0" "$@"
6f6caf69
MV
143fi
144
145export DEBIAN_FRONTEND=noninteractive
146export NEEDRESTART_MODE=a # don't prompt about restarting services
147
03b846cd
MV
148# ------------------------------------------------------- rsync packages
149
150REQUIRED=()
151OPTIONAL=()
152
153if [ "$WANT_RSYNC" -eq 1 ]; then
154 case "$PM" in
155 apt)
156 # Needed to build rsync at all.
157 REQUIRED=(
158 build-essential # gcc, g++, make, libc headers
159 gawk # rsync needs a modern awk for its code generators
160 autoconf # ./configure is generated from configure.ac
161 automake
162 python3 # manpage + header generators, and the test suite
163 cmake # the CMake build
164 ninja-build # ...and its default generator
165 git # version stamping (git-version.h)
166 pkg-config
167 )
168
169 # Optional: each one switches on an rsync feature. Missing ones only
170 # mean a smaller feature set, never a failed build.
171 OPTIONAL=(
172 acl libacl1-dev # --acls (helper tools also used by the tests)
173 attr libattr1-dev # --xattrs (likewise)
174 libxxhash-dev # xxhash checksums (becomes the default)
175 libzstd-dev # zstd compression (becomes the default)
176 liblz4-dev # lz4 compression
177 libssl-dev # OpenSSL MD4/MD5
178 zlib1g-dev # for cmake -DRSYNC_EXTERNAL_ZLIB=ON
179 libpopt-dev # for ./configure --with-included-popt=no
180 )
181 ;;
182 pacman)
183 # Arch splits nothing out into -dev packages: the headers ship with
184 # the library, and base-devel covers the compiler toolchain.
185 REQUIRED=(
186 base-devel # gcc, make, and friends
187 gawk
188 autoconf
189 automake
190 python
191 cmake
192 ninja
193 git
194 pkgconf
195 )
196 OPTIONAL=(
197 acl # --acls
198 attr # --xattrs
199 xxhash # xxhash checksums (becomes the default)
200 zstd # zstd compression (becomes the default)
201 lz4 # lz4 compression
202 openssl # OpenSSL MD4/MD5
203 zlib # for cmake -DRSYNC_EXTERNAL_ZLIB=ON
204 popt # for ./configure --with-included-popt=no
205 )
206 ;;
207 esac
208
209 # Development tooling: not needed to build rsync, but useful when working
210 # on its shell scripts -- including this one, which shellcheck keeps honest.
211 OPTIONAL+=(shellcheck)
212
213 # The manpages need one of two python3 markdown libraries; upstream prefers
214 # cmarkgfm. Pick whichever this release actually offers.
215 case "$PM" in
216 apt) MARKDOWN_PKG=$(pick_pkg python3-cmarkgfm python3-commonmark) ;;
217 pacman) MARKDOWN_PKG=$(pick_pkg python-cmarkgfm python-commonmark) ;;
218 esac
219 if [ -n "$MARKDOWN_PKG" ]; then
220 OPTIONAL+=("$MARKDOWN_PKG")
221 fi
222
223 if [ "$MINIMAL" -eq 1 ]; then
224 OPTIONAL=()
6f6caf69 225 fi
6f6caf69
MV
226fi
227
03b846cd
MV
228# ------------------------------------------------------- AGI packages
229
230# AGI is a Java/SWT desktop app plus a set of Go binaries. The .deb declares
231# "Depends: openjdk-22-jre, libgtk-3-0, libwebkit2gtk-4.1-0", but Ubuntu has
232# never packaged openjdk-22 -- so pick a JRE that exists instead. The GUI's
233# class files are Java 19 bytecode, so anything from 21 up runs it; the launcher
234# itself only insists on 11. adb is not a hard dependency, but without it AGI
235# cannot see Android devices ("adb could not be found from ANDROID_HOME or PATH").
236AGI_DEPS=()
237AGI_JRE=""
238AGI_GTK=""
239AGI_WEBKIT=""
240AGI_ADB=""
241
242if [ "$WANT_AGI" -eq 1 ]; then
243 case "$PM" in
244 apt)
245 AGI_JRE=$(pick_pkg openjdk-21-jre openjdk-25-jre openjdk-24-jre \
246 openjdk-23-jre openjdk-22-jre openjdk-26-jre)
247 AGI_GTK=$(pick_pkg libgtk-3-0t64 libgtk-3-0)
248 AGI_WEBKIT=$(pick_pkg libwebkit2gtk-4.1-0)
249 AGI_ADB=$(pick_pkg adb android-sdk-platform-tools)
250 # curl fetches the release, binutils' ar rewrites the .deb's control
251 # member, ca-certificates lets curl trust github.com.
252 AGI_DEPS=(curl ca-certificates binutils xz-utils unzip)
253 ;;
254 pacman)
255 AGI_JRE=$(pick_pkg jre-openjdk jre21-openjdk)
256 AGI_GTK=$(pick_pkg gtk3)
257 AGI_WEBKIT=$(pick_pkg webkit2gtk-4.1)
258 AGI_ADB=$(pick_pkg android-tools)
259 AGI_DEPS=(curl unzip)
260 ;;
261 esac
262
263 for p in "$AGI_GTK" "$AGI_WEBKIT" "$AGI_ADB"; do
264 [ -n "$p" ] && AGI_DEPS+=("$p")
265 done
266
267 # A JRE already on the box is good enough if it is new enough to load the
268 # GUI's Java 19 class files. On apt we install one anyway: the .deb's
269 # rewritten Depends line has to name a package dpkg knows about.
270 JAVA_MAJOR=0
271 if command -v java >/dev/null 2>&1; then
272 JAVA_MAJOR=$(java -version 2>&1 \
273 | sed -n '1s/.*version "\([0-9]\{1,\}\).*/\1/p')
274 JAVA_MAJOR=${JAVA_MAJOR:-0}
275 fi
276 if [ -n "$AGI_JRE" ] && { [ "$PM" = apt ] || [ "$JAVA_MAJOR" -lt 19 ]; }; then
277 AGI_DEPS+=("$AGI_JRE")
278 fi
279fi
280
281# ------------------------------------------------------- AGI release info
282
283# Used only when GitHub cannot be reached; the release that this script was
284# last checked against.
285AGI_FALLBACK_VERSION=3.3.3
286AGI_WANTED=""
287AGI_INSTALLED=""
288
289agi_installed_version() {
290 [ -r /opt/agi/build.properties ] || return 1
291 awk -F= '
292 /^Version\.Major/ { maj = $2 }
293 /^Version\.Minor/ { min = $2 }
294 /^Version\.Micro/ { mic = $2 }
295 END { if (maj == "") exit 1; print maj "." min "." mic }
296 ' /opt/agi/build.properties | tr -d ' \r'
297}
298
299agi_latest_version() {
300 command -v curl >/dev/null 2>&1 || return 1
301 curl -fsSL --retry 3 --max-time 20 \
302 https://api.github.com/repos/google/agi/releases/latest 2>/dev/null \
303 | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\{0,1\}\([^"]*\)".*/\1/p' \
304 | head -1
305}
306
307if [ "$WANT_AGI" -eq 1 ]; then
308 AGI_WANTED="${AGI_VERSION:-}"
309 if [ -z "$AGI_WANTED" ]; then
310 AGI_WANTED=$(agi_latest_version)
311 fi
312 if [ -z "$AGI_WANTED" ]; then
313 red "Could not reach the GitHub releases API; falling back to AGI $AGI_FALLBACK_VERSION."
314 AGI_WANTED="$AGI_FALLBACK_VERSION"
315 fi
316 AGI_INSTALLED=$(agi_installed_version) || AGI_INSTALLED=""
6f6caf69
MV
317fi
318
03b846cd
MV
319AGI_BASE_URL="https://github.com/google/agi/releases/download"
320
321# ------------------------------------------------------------- dry run
322
6f6caf69 323if [ "$DRY_RUN" -eq 1 ]; then
03b846cd
MV
324 if [ "$WANT_RSYNC" -eq 1 ]; then
325 echo
326 bold "Would install (rsync, required):"
327 printf ' %s\n' "${REQUIRED[@]}"
328 if [ ${#OPTIONAL[@]} -gt 0 ]; then
329 bold "Would install (rsync, optional):"
330 printf ' %s\n' "${OPTIONAL[@]}"
331 fi
332 fi
333 if [ "$WANT_AGI" -eq 1 ]; then
334 echo
335 bold "Would install (AGI dependencies):"
336 printf ' %s\n' "${AGI_DEPS[@]}"
337 bold "Would install AGI $AGI_WANTED:"
338 if [ "$PM" = apt ]; then
339 echo " $AGI_BASE_URL/v$AGI_WANTED/agi-$AGI_WANTED-linux.deb"
340 echo " via dpkg, with Depends retargeted at ${AGI_JRE:-the installed JRE}"
341 else
342 echo " $AGI_BASE_URL/v$AGI_WANTED/agi-$AGI_WANTED-linux.zip"
343 echo " unpacked into /opt/agi, symlinked as /usr/local/bin/agi"
344 fi
345 [ -n "$AGI_INSTALLED" ] && echo " (currently installed: $AGI_INSTALLED)"
6f6caf69
MV
346 fi
347 exit 0
348fi
349
350# ------------------------------------------------------------- install
351
352echo
03b846cd
MV
353case "$PM" in
354apt)
355 bold "Updating package lists..."
356 if ! apt-get update -qq; then
357 red "apt-get update failed -- continuing with the cached lists."
358 fi
359 ;;
360pacman)
361 # No "pacman -Sy" here on purpose: refreshing the database without also
362 # upgrading is how Arch systems end up half-broken. If a package fetch
363 # 404s below, the sync database is stale -- run "pacman -Syu" first.
364 bold "Using the existing pacman database (run 'pacman -Syu' if it is stale)."
365 ;;
366esac
6f6caf69 367
03b846cd
MV
368OPT_FAILED=()
369
370if [ "$WANT_RSYNC" -eq 1 ]; then
371 echo
372 bold "Installing required packages..."
373 if ! pkg_install "${REQUIRED[@]}"; then
374 # Fall back to one-at-a-time so the failing package is obvious.
375 red "Bulk install failed; retrying individually to find the culprit..."
376 FAILED=()
377 for p in "${REQUIRED[@]}"; do
378 pkg_install "$p" || FAILED+=("$p")
379 done
380 [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}"
381 fi
382
383 if [ ${#OPTIONAL[@]} -gt 0 ]; then
384 echo
385 bold "Installing optional packages..."
386 if ! pkg_install "${OPTIONAL[@]}"; then
387 red "Bulk install failed; retrying individually..."
388 for p in "${OPTIONAL[@]}"; do
389 pkg_install "$p" || OPT_FAILED+=("$p")
390 done
391 fi
392 fi
6f6caf69
MV
393fi
394
03b846cd
MV
395# ----------------------------------------------------------------- AGI
396
397# The .desktop and MIME files the .deb ships, replayed for the .zip install so
398# both routes leave the same system behind.
399agi_desktop_files() {
400 cat >/usr/share/applications/google-agi.desktop <<'DESKTOP'
401[Desktop Entry]
402Encoding=UTF-8
403Type=Application
404Name=Android GPU Inspector
405Comment=Android GPU Inspector
406Categories=Development;Debugger;Graphics;3DGraphics
407Icon=/opt/agi/icon.png
408Exec=/opt/agi/agi %f
409Terminal=false
410MimeType=application/x-gfxtrace;application/x-perfetto
411DESKTOP
412
413 mkdir -p /usr/share/mime/packages
414 cat >/usr/share/mime/packages/agi.xml <<'MIME'
415<?xml version="1.0" encoding="UTF-8"?>
416<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
417 <mime-type type="application/x-gfxtrace">
418 <comment>Graphics API Trace</comment>
419 <glob pattern="*.gfxtrace"/>
420 </mime-type>
421 <mime-type type="application/x-perfetto">
422 <comment>System Profile Trace</comment>
423 <glob pattern="*.perfetto"/>
424 </mime-type>
425</mime-info>
426MIME
427
428 command -v update-mime-database >/dev/null 2>&1 \
429 && update-mime-database /usr/share/mime >/dev/null 2>&1
430 command -v update-desktop-database >/dev/null 2>&1 \
431 && update-desktop-database /usr/share/applications >/dev/null 2>&1
432 return 0
433}
434
435agi_download() {
436 # $1 = url, $2 = destination file
437 echo " fetching $(basename "$1")..."
438 curl -fL --retry 3 --progress-bar -o "$2" "$1"
439}
440
441# Every dependency the .deb names has to exist in the archive, or dpkg will
442# refuse the package. openjdk-22-jre does not exist in any Ubuntu release,
443# so rewrite that line to the JRE we just installed. Only the control member
444# is rebuilt -- unpacking and recompressing the 230MB payload with dpkg-deb
445# would take minutes and change nothing.
446deb_depends_satisfiable() {
447 local dep
448 while IFS= read -r dep; do
449 dep=$(echo "$dep" | sed 's/(.*)//; s/^[[:space:]]*//; s/[[:space:]]*$//')
450 [ -z "$dep" ] && continue
451 pkg_available "$dep" || return 1
452 done < <(dpkg-deb -f "$1" Depends 2>/dev/null | tr ',' '\n')
453 return 0
454}
455
456rewrite_deb_depends() {
457 # $1 = .deb, $2 = new Depends line, $3 = scratch directory
458 local deb="$1" depends="$2" work="$3/control"
459 command -v ar >/dev/null 2>&1 || return 1
460 rm -rf "$work"
461 mkdir -p "$work/c" || return 1
462 ( cd "$work" && ar x "$deb" control.tar.xz ) || return 1
463 tar xf "$work/control.tar.xz" -C "$work/c" || return 1
464 sed -i "s|^Depends:.*|Depends: $depends|" "$work/c/control" || return 1
465 rm -f "$work/control.tar.xz"
466 ( cd "$work/c" && tar cJf ../control.tar.xz . ) || return 1
467 ( cd "$work" && ar r "$deb" control.tar.xz ) || return 1
468 # Make sure dpkg still understands the archive we just edited.
469 dpkg-deb -f "$deb" Depends >/dev/null 2>&1 || return 1
470}
471
472install_agi_deb() {
473 local ver="$1" tmp="$2" deb="$tmp/agi.deb" depends
474
475 agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.deb" "$deb" || return 1
476
477 if ! deb_depends_satisfiable "$deb"; then
478 depends="${AGI_JRE:-default-jre}"
479 [ -n "$AGI_GTK" ] && depends="$depends, $AGI_GTK"
480 [ -n "$AGI_WEBKIT" ] && depends="$depends, $AGI_WEBKIT"
481 echo " retargeting the package's Depends at: $depends"
482 rewrite_deb_depends "$deb" "$depends" "$tmp" || {
483 red " could not rewrite the .deb's dependencies."
484 return 1
485 }
486 fi
487
488 if ! dpkg -i "$deb"; then
489 red " dpkg reported a problem; asking apt to resolve it..."
490 apt-get -f install -y || return 1
491 dpkg -i "$deb" || return 1
492 fi
493}
494
495install_agi_zip() {
496 local ver="$1" tmp="$2"
497
498 agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.zip" "$tmp/agi.zip" || return 1
499 unzip -q "$tmp/agi.zip" -d "$tmp/unpacked" || return 1
500 [ -x "$tmp/unpacked/agi/agi" ] || {
501 red " the zip did not contain agi/agi -- leaving /opt/agi alone."
502 return 1
503 }
504
505 # Only ever replace a directory that looks like a previous AGI install.
506 if [ -e /opt/agi ] && [ ! -e /opt/agi/build.properties ]; then
507 red " /opt/agi exists but is not an AGI install; refusing to replace it."
508 return 1
509 fi
510
511 rm -rf /opt/agi
512 mv "$tmp/unpacked/agi" /opt/agi || return 1
513 chown -R root:root /opt/agi
514 ln -sf /opt/agi/agi /usr/local/bin/agi
515 agi_desktop_files
516}
517
518AGI_OK=0
519if [ "$WANT_AGI" -eq 1 ]; then
6f6caf69 520 echo
03b846cd
MV
521 bold "Installing AGI dependencies..."
522 if ! pkg_install "${AGI_DEPS[@]}"; then
6f6caf69 523 red "Bulk install failed; retrying individually..."
03b846cd
MV
524 for p in "${AGI_DEPS[@]}"; do
525 pkg_install "$p" || OPT_FAILED+=("$p")
6f6caf69
MV
526 done
527 fi
03b846cd
MV
528
529 echo
530 if [ -n "$AGI_INSTALLED" ] && [ "$AGI_INSTALLED" = "$AGI_WANTED" ]; then
531 bold "AGI $AGI_INSTALLED is already installed in /opt/agi."
532 AGI_OK=1
533 else
534 bold "Installing AGI $AGI_WANTED..."
535 [ -n "$AGI_INSTALLED" ] && echo " replacing the installed $AGI_INSTALLED"
536 AGI_TMP=$(mktemp -d "${TMPDIR:-/tmp}/agi-install.XXXXXX") \
537 || die "could not create a temporary directory."
538 # 85MB of .deb or .zip; do not leave it behind on any exit path.
539 trap 'rm -rf "$AGI_TMP"' EXIT
540
541 if [ "$PM" = apt ]; then
542 if install_agi_deb "$AGI_WANTED" "$AGI_TMP"; then
543 AGI_OK=1
544 else
545 red " .deb install failed; falling back to the zip release."
546 install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1
547 fi
548 else
549 install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1
550 fi
551
552 rm -rf "$AGI_TMP"
553 trap - EXIT
554 [ "$AGI_OK" -eq 1 ] || red "AGI installation failed."
555 fi
6f6caf69
MV
556fi
557
558# --------------------------------------------------------------- verify
559
6f6caf69
MV
560MISSING=()
561check_cmd() {
562 if command -v "$1" >/dev/null 2>&1; then
563 printf ' %-16s %s\n' "$1" "$(command -v "$1")"
564 else
565 printf ' %-16s %s\n' "$1" "MISSING"
566 MISSING+=("$1")
567 fi
568}
569
03b846cd
MV
570if [ "$WANT_RSYNC" -eq 1 ]; then
571 echo
572 bold "Verifying the toolchain..."
6f6caf69 573
03b846cd
MV
574 for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do
575 check_cmd "$c"
576 done
577
578 # Not required to build, so report it without failing the run.
579 if command -v shellcheck >/dev/null 2>&1; then
580 printf ' %-16s %s\n' "shellcheck" "$(command -v shellcheck)"
581 else
582 printf ' %-16s %s\n' "shellcheck" "not installed (optional)"
583 fi
584
585 echo
586 bold "Verifying optional libraries..."
587 check_header() {
588 # $1 = human name, $2 = header path
589 if [ -e "/usr/include/$2" ] || \
590 find /usr/include -maxdepth 3 -name "$(basename "$2")" -print -quit \
591 2>/dev/null | grep -q .; then
592 printf ' %-16s yes\n' "$1"
593 else
594 printf ' %-16s no\n' "$1"
595 fi
596 }
597 check_header acl sys/acl.h
598 check_header xattr sys/xattr.h
599 check_header xxhash xxhash.h
600 check_header zstd zstd.h
601 check_header lz4 lz4.h
602 check_header openssl openssl/md5.h
603 check_header zlib zlib.h
604
605 echo
606 bold "Verifying the manpage generator..."
607 MD_OK=0
608 for m in cmarkgfm commonmark; do
609 if python3 -c "import $m" >/dev/null 2>&1; then
610 echo " python3 module '$m' importable"
611 MD_OK=1
612 break
613 fi
614 done
615 if [ "$MD_OK" -eq 0 ]; then
616 if [ "$MINIMAL" -eq 1 ]; then
617 echo " skipped (--minimal); build with ./configure --disable-md2man"
618 else
619 red " neither cmarkgfm nor commonmark is importable."
620 echo " Install one with: python3 -mpip install --user commonmark"
621 echo " ...or build with: ./configure --disable-md2man"
622 fi
623 fi
6f6caf69
MV
624fi
625
03b846cd
MV
626if [ "$WANT_AGI" -eq 1 ]; then
627 echo
628 bold "Verifying AGI..."
629 if [ -x /opt/agi/agi ]; then
630 printf ' %-16s %s\n' "agi" "/opt/agi/agi ($(agi_installed_version))"
6f6caf69 631 else
03b846cd 632 printf ' %-16s %s\n' "agi" "MISSING"
6f6caf69 633 fi
6f6caf69 634
03b846cd
MV
635 # The launcher settles for a JRE >= 11, but the GUI's jar is Java 19
636 # bytecode -- an older JVM starts and then dies on UnsupportedClassVersion.
637 if command -v java >/dev/null 2>&1; then
638 JAVA_MAJOR=$(java -version 2>&1 \
639 | sed -n '1s/.*version "\([0-9]\{1,\}\).*/\1/p')
640 JAVA_MAJOR=${JAVA_MAJOR:-0}
641 if [ "$JAVA_MAJOR" -ge 19 ]; then
642 printf ' %-16s java %s\n' "jre" "$JAVA_MAJOR"
643 else
644 printf ' %-16s java %s (too old, AGI needs 19+)\n' "jre" "$JAVA_MAJOR"
645 fi
646 else
647 printf ' %-16s %s\n' "jre" "MISSING"
6f6caf69 648 fi
03b846cd
MV
649
650 for lib in libgtk-3.so.0 libwebkit2gtk-4.1.so.0; do
651 if ldconfig -p 2>/dev/null | grep -q "$lib"; then
652 printf ' %-16s yes\n' "${lib%%.so*}"
653 else
654 printf ' %-16s no\n' "${lib%%.so*}"
655 fi
656 done
657
658 # Not fatal: AGI runs without adb, it just cannot see Android devices.
659 if command -v adb >/dev/null 2>&1; then
660 printf ' %-16s %s\n' "adb" "$(command -v adb)"
6f6caf69 661 else
03b846cd 662 printf ' %-16s %s\n' "adb" "not installed (no Android devices)"
6f6caf69
MV
663 fi
664fi
665
666# ---------------------------------------------------------------- done
667
668echo
669if [ ${#MISSING[@]} -ne 0 ]; then
670 red "Missing required tools: ${MISSING[*]}"
671 exit 1
672fi
673
674if [ ${#OPT_FAILED[@]} -ne 0 ]; then
675 red "Optional packages that failed to install: ${OPT_FAILED[*]}"
676 echo "The build will still work, with those features disabled."
677fi
678
03b846cd
MV
679if [ "$WANT_RSYNC" -eq 1 ]; then
680 green "All required build dependencies are installed."
681 cat <<'EOF'
6f6caf69
MV
682
683Build rsync with either build system:
684
685 autoconf: ./configure && make
686 CMake: cmake -B build -G Ninja && cmake --build build
687
688Run the test suite (needs the autoconf build's helper programs):
689
690 make check
691EOF
03b846cd
MV
692fi
693
694if [ "$WANT_AGI" -eq 1 ] && [ "$AGI_OK" -eq 1 ]; then
695 green "AGI $AGI_WANTED is installed."
696 if [ "$PM" = apt ]; then
697 cat <<'EOF'
698
699Run it from the desktop menu ("Android GPU Inspector") or:
700
701 /opt/agi/agi # GUI (--console to keep it in the terminal)
702 /opt/agi/gapit # CLI
703
704Remove it with: sudo apt-get remove agi
705EOF
706 else
707 cat <<'EOF'
708
709Run it from the desktop menu ("Android GPU Inspector") or:
710
711 agi # GUI, symlinked into /usr/local/bin
712 /opt/agi/gapit # CLI
713
714Remove it with: sudo rm -rf /opt/agi /usr/local/bin/agi \
715 /usr/share/applications/google-agi.desktop \
716 /usr/share/mime/packages/agi.xml
717EOF
718 fi
719 echo "Tracing an Android device needs adb and USB debugging enabled on it."
720fi