From: Max Vilimpoc Date: Mon, 31 Aug 2026 09:20:55 +0000 (+0200) Subject: dotfiles: install AGI from setup-linux.sh, on Ubuntu and on CachyOS X-Git-Url: https://vilimpoc.org/repos/dotfiles/commitdiff_plain/03b846cd7abe33973416d13f981462969bce4507?ds=sidebyside dotfiles: install AGI from setup-linux.sh, on Ubuntu and on CachyOS The script died on anything without apt-get, which is most of the machines it now has to run on, so the package layer is a two-line wrapper over apt-get and pacman and the rsync lists gained their Arch spellings -- base-devel, pkgconf, and the libraries without the -dev half Arch does not split out. AGI ships a .deb and a .zip per release; Debian and Ubuntu get the .deb, Arch and CachyOS the .zip unpacked to /opt/agi, since there is no AUR package for it (the AUR RPC knows no agi, agi-bin or android-graphics-inspector). The .zip route replays the .desktop and MIME files the .deb installs, so both routes leave the same system behind, and only ever replaces a /opt/agi that carries a build.properties. The .deb cannot be installed as it stands. It declares "Depends: openjdk-22-jre, libgtk-3-0, libwebkit2gtk-4.1-0" and Ubuntu has never packaged openjdk-22 -- jammy through resolute ship 11, 17, 21 and 25 -- so apt has nothing to resolve that against. Nothing in the package needs 22 either: it bundles no JRE, the Go launcher settles for >= 11, and the highest class file version in gapic.jar is 63, i.e. Java 19. openjdk-21-jre satisfies both and exists on every release from 22.04 on, so the install picks a JRE that is actually in the archive and rewrites that one control field to name it. Only the control member is rebuilt, with ar; dpkg-deb -R/-b would unpack and recompress 230MB of payload for a one-line edit. Verified against the real 84MB package: member order preserved, Depends retargeted, and dpkg-deb still parses the result. Should any of that fail, the zip installer is the fallback, so the .deb path can only cost time. libgtk-3-0 became libgtk-3-0t64 in 24.04 and SWT wants webkit 4.1 rather than the 4.0 only jammy has, so both are chosen by asking the archive rather than by name. adb is installed too: it is not a hard dependency, but gapis carries the string "adb could not be found from ANDROID_HOME or PATH", which is all AGI can do without one. --agi-only and --no-agi split the two halves; --minimal implies --no-agi. AGI_VERSION pins a release, otherwise the version comes from the releases API with the last-checked 3.3.3 as the offline fallback, and a re-run whose /opt/agi/build.properties already matches skips the 85MB download. No pacman -Sy anywhere -- refreshing the database without upgrading is how an Arch box ends up half-broken -- so a stale database is reported rather than worked around. Exercised on CachyOS: every --dry-run path, the version lookup, and AGI 3.3.3 itself starting against the installed JRE with gtk3 and webkit2gtk-4.1. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PqwcPKuLX7xML2DZqHnNJY --- diff --git a/setup-linux.sh b/setup-linux.sh old mode 100644 new mode 100755 index afb36a7..823dfa0 --- a/setup-linux.sh +++ b/setup-linux.sh @@ -1,29 +1,41 @@ #!/bin/bash # -# setup-linux.sh -- install everything needed to build rsync on Ubuntu. +# setup-linux.sh -- install the rsync build toolchain and AGI. # -# Tested on Ubuntu 22.04.5 LTS and 26.04 LTS. Covers both build systems: -# the autoconf one (./configure && make) and the CMake one (CMakeLists.txt), -# plus the optional feature libraries and the manpage generator. +# Tested on Ubuntu 22.04.5 LTS and 26.04 LTS, and on CachyOS. Covers both +# rsync build systems: the autoconf one (./configure && make) and the CMake +# one (CMakeLists.txt), plus the optional feature libraries and the manpage +# generator. Also installs AGI (Android GPU Inspector) from the latest +# google/agi release: the .deb on Debian/Ubuntu, the .zip on Arch/CachyOS. # # Usage: -# ./setup-linux.sh # install required + optional -# ./setup-linux.sh --minimal # required only (no ACLs/xattrs/zstd/...) +# ./setup-linux.sh # rsync required + optional, then AGI +# ./setup-linux.sh --minimal # rsync required only, no AGI +# ./setup-linux.sh --no-agi # rsync deps only +# ./setup-linux.sh --agi-only # AGI and its dependencies only # ./setup-linux.sh --dry-run # show what would be installed # -# Safe to re-run: apt skips anything already present. +# AGI_VERSION=3.3.3 ./setup-linux.sh pins a release instead of asking GitHub +# for the latest one. +# +# Safe to re-run: the package managers skip anything already present, and AGI +# is re-downloaded only when the installed version is not the wanted one. set -uo pipefail MINIMAL=0 DRY_RUN=0 +WANT_AGI=1 +WANT_RSYNC=1 for arg in "$@"; do case "$arg" in - --minimal) MINIMAL=1 ;; + --minimal) MINIMAL=1; WANT_AGI=0 ;; --dry-run) DRY_RUN=1 ;; + --no-agi) WANT_AGI=0 ;; + --agi-only) WANT_RSYNC=0; WANT_AGI=1 ;; -h|--help) - sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "setup-linux.sh: unknown option '$arg' (try --help)" >&2 @@ -39,10 +51,52 @@ bold() { printf '\033[1m%s\033[0m\n' "$*"; } die() { red "ERROR: $*"; exit 1; } -# ------------------------------------------------------- sanity checks +# ------------------------------------------------- package manager wrapper + +# Two families are supported: apt (Debian/Ubuntu) and pacman (Arch/CachyOS). +PM="" +if command -v apt-get >/dev/null 2>&1; then + PM=apt +elif command -v pacman >/dev/null 2>&1; then + PM=pacman +else + die "found neither apt-get nor pacman -- unsupported distribution." +fi + +pkg_available() { + case "$PM" in + apt) + local cand + cand=$(apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/{print $2}') + [ -n "$cand" ] && [ "$cand" != "(none)" ] ;; + pacman) + pacman -Si "$1" >/dev/null 2>&1 ;; + esac +} -command -v apt-get >/dev/null 2>&1 \ - || die "no apt-get found -- this script is for Debian/Ubuntu systems." +# Echo the first name the distro actually ships. Package names drift between +# releases (libgtk-3-0 became libgtk-3-0t64 in Ubuntu 24.04, and the JDK +# version in the archive moves every six months), so nothing is hard-coded. +pick_pkg() { + local p + for p in "$@"; do + if pkg_available "$p"; then + echo "$p" + return 0 + fi + done + return 1 +} + +pkg_install() { + [ $# -eq 0 ] && return 0 + case "$PM" in + apt) apt-get install -y "$@" ;; + pacman) pacman -S --needed --noconfirm "$@" ;; + esac +} + +# ------------------------------------------------------- sanity checks DISTRO="unknown" RELEASE="unknown" @@ -53,20 +107,31 @@ if [ -r /etc/os-release ]; then RELEASE="${VERSION_ID:-unknown}" fi -bold "rsync build dependencies" +bold "rsync build dependencies and AGI" echo " distro ......... ${PRETTY_NAME:-$DISTRO $RELEASE}" echo " architecture ... $(uname -m)" +echo " packages ....... $PM" case "$DISTRO" in -ubuntu|debian|linuxmint|pop) ;; +ubuntu|debian|linuxmint|pop|arch|cachyos|endeavouros|manjaro) ;; *) echo - red "Warning: this script targets Ubuntu/Debian; '$DISTRO' is untested." + red "Warning: '$DISTRO' is untested; assuming it behaves like a $PM distro." echo "Continuing anyway -- package names may differ." ;; esac -# apt needs root. Re-exec under sudo rather than sprinkling it around, so -# that a single password prompt covers the whole run. +# AGI ships x86-64 binaries only. +case "$(uname -m)" in +x86_64|amd64) ;; +*) if [ "$WANT_AGI" -eq 1 ]; then + red "Warning: AGI publishes x86-64 Linux builds only; skipping it." + WANT_AGI=0 + fi + ;; +esac + +# The package managers need root. Re-exec under sudo rather than sprinkling +# it around, so that a single password prompt covers the whole run. if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" -eq 0 ]; then command -v sudo >/dev/null 2>&1 \ || die "not running as root and sudo is not installed." @@ -74,72 +139,210 @@ if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" -eq 0 ]; then echo "Re-running under sudo..." # Plain sudo, not "sudo -E": some sudoers configs refuse to preserve the # environment, and the exports below happen in the root instance anyway. - exec sudo "$0" "$@" + exec sudo AGI_VERSION="${AGI_VERSION:-}" "$0" "$@" fi export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a # don't prompt about restarting services -# ------------------------------------------------------------- packages - -# Needed to build rsync at all. -REQUIRED=( - build-essential # gcc, g++, make, libc headers - gawk # rsync needs a modern awk for its code generators - autoconf # ./configure is generated from configure.ac - automake - python3 # manpage + header generators, and the test suite - cmake # the CMake build - ninja-build # ...and its default generator - git # version stamping (git-version.h) - pkg-config -) - -# Optional: each one switches on an rsync feature. Missing ones only mean -# a smaller feature set, never a failed build. -OPTIONAL=( - acl libacl1-dev # --acls (helper tools also used by the tests) - attr libattr1-dev # --xattrs (likewise) - libxxhash-dev # xxhash checksums (becomes the default) - libzstd-dev # zstd compression (becomes the default) - liblz4-dev # lz4 compression - libssl-dev # OpenSSL MD4/MD5 - zlib1g-dev # for cmake -DRSYNC_EXTERNAL_ZLIB=ON - libpopt-dev # for ./configure --with-included-popt=no -) - -# Development tooling: not needed to build rsync, but useful when working on -# its shell scripts -- including this one, which shellcheck keeps honest. -DEVTOOLS=( - shellcheck -) -OPTIONAL+=("${DEVTOOLS[@]}") - -# The manpages need one of two python3 markdown libraries; upstream prefers -# cmarkgfm. Pick whichever this release actually offers. -MARKDOWN_PKG="" -for p in python3-cmarkgfm python3-commonmark; do - cand=$(apt-cache policy "$p" 2>/dev/null | awk '/Candidate:/{print $2}') - if [ -n "$cand" ] && [ "$cand" != "(none)" ]; then - MARKDOWN_PKG="$p" - break +# ------------------------------------------------------- rsync packages + +REQUIRED=() +OPTIONAL=() + +if [ "$WANT_RSYNC" -eq 1 ]; then + case "$PM" in + apt) + # Needed to build rsync at all. + REQUIRED=( + build-essential # gcc, g++, make, libc headers + gawk # rsync needs a modern awk for its code generators + autoconf # ./configure is generated from configure.ac + automake + python3 # manpage + header generators, and the test suite + cmake # the CMake build + ninja-build # ...and its default generator + git # version stamping (git-version.h) + pkg-config + ) + + # Optional: each one switches on an rsync feature. Missing ones only + # mean a smaller feature set, never a failed build. + OPTIONAL=( + acl libacl1-dev # --acls (helper tools also used by the tests) + attr libattr1-dev # --xattrs (likewise) + libxxhash-dev # xxhash checksums (becomes the default) + libzstd-dev # zstd compression (becomes the default) + liblz4-dev # lz4 compression + libssl-dev # OpenSSL MD4/MD5 + zlib1g-dev # for cmake -DRSYNC_EXTERNAL_ZLIB=ON + libpopt-dev # for ./configure --with-included-popt=no + ) + ;; + pacman) + # Arch splits nothing out into -dev packages: the headers ship with + # the library, and base-devel covers the compiler toolchain. + REQUIRED=( + base-devel # gcc, make, and friends + gawk + autoconf + automake + python + cmake + ninja + git + pkgconf + ) + OPTIONAL=( + acl # --acls + attr # --xattrs + xxhash # xxhash checksums (becomes the default) + zstd # zstd compression (becomes the default) + lz4 # lz4 compression + openssl # OpenSSL MD4/MD5 + zlib # for cmake -DRSYNC_EXTERNAL_ZLIB=ON + popt # for ./configure --with-included-popt=no + ) + ;; + esac + + # Development tooling: not needed to build rsync, but useful when working + # on its shell scripts -- including this one, which shellcheck keeps honest. + OPTIONAL+=(shellcheck) + + # The manpages need one of two python3 markdown libraries; upstream prefers + # cmarkgfm. Pick whichever this release actually offers. + case "$PM" in + apt) MARKDOWN_PKG=$(pick_pkg python3-cmarkgfm python3-commonmark) ;; + pacman) MARKDOWN_PKG=$(pick_pkg python-cmarkgfm python-commonmark) ;; + esac + if [ -n "$MARKDOWN_PKG" ]; then + OPTIONAL+=("$MARKDOWN_PKG") + fi + + if [ "$MINIMAL" -eq 1 ]; then + OPTIONAL=() fi -done -if [ -n "$MARKDOWN_PKG" ]; then - OPTIONAL+=("$MARKDOWN_PKG") fi -if [ "$MINIMAL" -eq 1 ]; then - OPTIONAL=() +# ------------------------------------------------------- AGI packages + +# AGI is a Java/SWT desktop app plus a set of Go binaries. The .deb declares +# "Depends: openjdk-22-jre, libgtk-3-0, libwebkit2gtk-4.1-0", but Ubuntu has +# never packaged openjdk-22 -- so pick a JRE that exists instead. The GUI's +# class files are Java 19 bytecode, so anything from 21 up runs it; the launcher +# itself only insists on 11. adb is not a hard dependency, but without it AGI +# cannot see Android devices ("adb could not be found from ANDROID_HOME or PATH"). +AGI_DEPS=() +AGI_JRE="" +AGI_GTK="" +AGI_WEBKIT="" +AGI_ADB="" + +if [ "$WANT_AGI" -eq 1 ]; then + case "$PM" in + apt) + AGI_JRE=$(pick_pkg openjdk-21-jre openjdk-25-jre openjdk-24-jre \ + openjdk-23-jre openjdk-22-jre openjdk-26-jre) + AGI_GTK=$(pick_pkg libgtk-3-0t64 libgtk-3-0) + AGI_WEBKIT=$(pick_pkg libwebkit2gtk-4.1-0) + AGI_ADB=$(pick_pkg adb android-sdk-platform-tools) + # curl fetches the release, binutils' ar rewrites the .deb's control + # member, ca-certificates lets curl trust github.com. + AGI_DEPS=(curl ca-certificates binutils xz-utils unzip) + ;; + pacman) + AGI_JRE=$(pick_pkg jre-openjdk jre21-openjdk) + AGI_GTK=$(pick_pkg gtk3) + AGI_WEBKIT=$(pick_pkg webkit2gtk-4.1) + AGI_ADB=$(pick_pkg android-tools) + AGI_DEPS=(curl unzip) + ;; + esac + + for p in "$AGI_GTK" "$AGI_WEBKIT" "$AGI_ADB"; do + [ -n "$p" ] && AGI_DEPS+=("$p") + done + + # A JRE already on the box is good enough if it is new enough to load the + # GUI's Java 19 class files. On apt we install one anyway: the .deb's + # rewritten Depends line has to name a package dpkg knows about. + JAVA_MAJOR=0 + if command -v java >/dev/null 2>&1; then + JAVA_MAJOR=$(java -version 2>&1 \ + | sed -n '1s/.*version "\([0-9]\{1,\}\).*/\1/p') + JAVA_MAJOR=${JAVA_MAJOR:-0} + fi + if [ -n "$AGI_JRE" ] && { [ "$PM" = apt ] || [ "$JAVA_MAJOR" -lt 19 ]; }; then + AGI_DEPS+=("$AGI_JRE") + fi +fi + +# ------------------------------------------------------- AGI release info + +# Used only when GitHub cannot be reached; the release that this script was +# last checked against. +AGI_FALLBACK_VERSION=3.3.3 +AGI_WANTED="" +AGI_INSTALLED="" + +agi_installed_version() { + [ -r /opt/agi/build.properties ] || return 1 + awk -F= ' + /^Version\.Major/ { maj = $2 } + /^Version\.Minor/ { min = $2 } + /^Version\.Micro/ { mic = $2 } + END { if (maj == "") exit 1; print maj "." min "." mic } + ' /opt/agi/build.properties | tr -d ' \r' +} + +agi_latest_version() { + command -v curl >/dev/null 2>&1 || return 1 + curl -fsSL --retry 3 --max-time 20 \ + https://api.github.com/repos/google/agi/releases/latest 2>/dev/null \ + | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\{0,1\}\([^"]*\)".*/\1/p' \ + | head -1 +} + +if [ "$WANT_AGI" -eq 1 ]; then + AGI_WANTED="${AGI_VERSION:-}" + if [ -z "$AGI_WANTED" ]; then + AGI_WANTED=$(agi_latest_version) + fi + if [ -z "$AGI_WANTED" ]; then + red "Could not reach the GitHub releases API; falling back to AGI $AGI_FALLBACK_VERSION." + AGI_WANTED="$AGI_FALLBACK_VERSION" + fi + AGI_INSTALLED=$(agi_installed_version) || AGI_INSTALLED="" fi +AGI_BASE_URL="https://github.com/google/agi/releases/download" + +# ------------------------------------------------------------- dry run + if [ "$DRY_RUN" -eq 1 ]; then - echo - bold "Would install (required):" - printf ' %s\n' "${REQUIRED[@]}" - if [ ${#OPTIONAL[@]} -gt 0 ]; then - bold "Would install (optional):" - printf ' %s\n' "${OPTIONAL[@]}" + if [ "$WANT_RSYNC" -eq 1 ]; then + echo + bold "Would install (rsync, required):" + printf ' %s\n' "${REQUIRED[@]}" + if [ ${#OPTIONAL[@]} -gt 0 ]; then + bold "Would install (rsync, optional):" + printf ' %s\n' "${OPTIONAL[@]}" + fi + fi + if [ "$WANT_AGI" -eq 1 ]; then + echo + bold "Would install (AGI dependencies):" + printf ' %s\n' "${AGI_DEPS[@]}" + bold "Would install AGI $AGI_WANTED:" + if [ "$PM" = apt ]; then + echo " $AGI_BASE_URL/v$AGI_WANTED/agi-$AGI_WANTED-linux.deb" + echo " via dpkg, with Depends retargeted at ${AGI_JRE:-the installed JRE}" + else + echo " $AGI_BASE_URL/v$AGI_WANTED/agi-$AGI_WANTED-linux.zip" + echo " unpacked into /opt/agi, symlinked as /usr/local/bin/agi" + fi + [ -n "$AGI_INSTALLED" ] && echo " (currently installed: $AGI_INSTALLED)" fi exit 0 fi @@ -147,40 +350,213 @@ fi # ------------------------------------------------------------- install echo -bold "Updating package lists..." -if ! apt-get update -qq; then - red "apt-get update failed -- continuing with the cached lists." -fi +case "$PM" in +apt) + bold "Updating package lists..." + if ! apt-get update -qq; then + red "apt-get update failed -- continuing with the cached lists." + fi + ;; +pacman) + # No "pacman -Sy" here on purpose: refreshing the database without also + # upgrading is how Arch systems end up half-broken. If a package fetch + # 404s below, the sync database is stale -- run "pacman -Syu" first. + bold "Using the existing pacman database (run 'pacman -Syu' if it is stale)." + ;; +esac -echo -bold "Installing required packages..." -if ! apt-get install -y "${REQUIRED[@]}"; then - # Fall back to one-at-a-time so the failing package is obvious. - red "Bulk install failed; retrying individually to find the culprit..." - FAILED=() - for p in "${REQUIRED[@]}"; do - apt-get install -y "$p" || FAILED+=("$p") - done - [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}" +OPT_FAILED=() + +if [ "$WANT_RSYNC" -eq 1 ]; then + echo + bold "Installing required packages..." + if ! pkg_install "${REQUIRED[@]}"; then + # Fall back to one-at-a-time so the failing package is obvious. + red "Bulk install failed; retrying individually to find the culprit..." + FAILED=() + for p in "${REQUIRED[@]}"; do + pkg_install "$p" || FAILED+=("$p") + done + [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}" + fi + + if [ ${#OPTIONAL[@]} -gt 0 ]; then + echo + bold "Installing optional packages..." + if ! pkg_install "${OPTIONAL[@]}"; then + red "Bulk install failed; retrying individually..." + for p in "${OPTIONAL[@]}"; do + pkg_install "$p" || OPT_FAILED+=("$p") + done + fi + fi fi -OPT_FAILED=() -if [ ${#OPTIONAL[@]} -gt 0 ]; then +# ----------------------------------------------------------------- AGI + +# The .desktop and MIME files the .deb ships, replayed for the .zip install so +# both routes leave the same system behind. +agi_desktop_files() { + cat >/usr/share/applications/google-agi.desktop <<'DESKTOP' +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Android GPU Inspector +Comment=Android GPU Inspector +Categories=Development;Debugger;Graphics;3DGraphics +Icon=/opt/agi/icon.png +Exec=/opt/agi/agi %f +Terminal=false +MimeType=application/x-gfxtrace;application/x-perfetto +DESKTOP + + mkdir -p /usr/share/mime/packages + cat >/usr/share/mime/packages/agi.xml <<'MIME' + + + + Graphics API Trace + + + + System Profile Trace + + + +MIME + + command -v update-mime-database >/dev/null 2>&1 \ + && update-mime-database /usr/share/mime >/dev/null 2>&1 + command -v update-desktop-database >/dev/null 2>&1 \ + && update-desktop-database /usr/share/applications >/dev/null 2>&1 + return 0 +} + +agi_download() { + # $1 = url, $2 = destination file + echo " fetching $(basename "$1")..." + curl -fL --retry 3 --progress-bar -o "$2" "$1" +} + +# Every dependency the .deb names has to exist in the archive, or dpkg will +# refuse the package. openjdk-22-jre does not exist in any Ubuntu release, +# so rewrite that line to the JRE we just installed. Only the control member +# is rebuilt -- unpacking and recompressing the 230MB payload with dpkg-deb +# would take minutes and change nothing. +deb_depends_satisfiable() { + local dep + while IFS= read -r dep; do + dep=$(echo "$dep" | sed 's/(.*)//; s/^[[:space:]]*//; s/[[:space:]]*$//') + [ -z "$dep" ] && continue + pkg_available "$dep" || return 1 + done < <(dpkg-deb -f "$1" Depends 2>/dev/null | tr ',' '\n') + return 0 +} + +rewrite_deb_depends() { + # $1 = .deb, $2 = new Depends line, $3 = scratch directory + local deb="$1" depends="$2" work="$3/control" + command -v ar >/dev/null 2>&1 || return 1 + rm -rf "$work" + mkdir -p "$work/c" || return 1 + ( cd "$work" && ar x "$deb" control.tar.xz ) || return 1 + tar xf "$work/control.tar.xz" -C "$work/c" || return 1 + sed -i "s|^Depends:.*|Depends: $depends|" "$work/c/control" || return 1 + rm -f "$work/control.tar.xz" + ( cd "$work/c" && tar cJf ../control.tar.xz . ) || return 1 + ( cd "$work" && ar r "$deb" control.tar.xz ) || return 1 + # Make sure dpkg still understands the archive we just edited. + dpkg-deb -f "$deb" Depends >/dev/null 2>&1 || return 1 +} + +install_agi_deb() { + local ver="$1" tmp="$2" deb="$tmp/agi.deb" depends + + agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.deb" "$deb" || return 1 + + if ! deb_depends_satisfiable "$deb"; then + depends="${AGI_JRE:-default-jre}" + [ -n "$AGI_GTK" ] && depends="$depends, $AGI_GTK" + [ -n "$AGI_WEBKIT" ] && depends="$depends, $AGI_WEBKIT" + echo " retargeting the package's Depends at: $depends" + rewrite_deb_depends "$deb" "$depends" "$tmp" || { + red " could not rewrite the .deb's dependencies." + return 1 + } + fi + + if ! dpkg -i "$deb"; then + red " dpkg reported a problem; asking apt to resolve it..." + apt-get -f install -y || return 1 + dpkg -i "$deb" || return 1 + fi +} + +install_agi_zip() { + local ver="$1" tmp="$2" + + agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.zip" "$tmp/agi.zip" || return 1 + unzip -q "$tmp/agi.zip" -d "$tmp/unpacked" || return 1 + [ -x "$tmp/unpacked/agi/agi" ] || { + red " the zip did not contain agi/agi -- leaving /opt/agi alone." + return 1 + } + + # Only ever replace a directory that looks like a previous AGI install. + if [ -e /opt/agi ] && [ ! -e /opt/agi/build.properties ]; then + red " /opt/agi exists but is not an AGI install; refusing to replace it." + return 1 + fi + + rm -rf /opt/agi + mv "$tmp/unpacked/agi" /opt/agi || return 1 + chown -R root:root /opt/agi + ln -sf /opt/agi/agi /usr/local/bin/agi + agi_desktop_files +} + +AGI_OK=0 +if [ "$WANT_AGI" -eq 1 ]; then echo - bold "Installing optional packages..." - if ! apt-get install -y "${OPTIONAL[@]}"; then + bold "Installing AGI dependencies..." + if ! pkg_install "${AGI_DEPS[@]}"; then red "Bulk install failed; retrying individually..." - for p in "${OPTIONAL[@]}"; do - apt-get install -y "$p" || OPT_FAILED+=("$p") + for p in "${AGI_DEPS[@]}"; do + pkg_install "$p" || OPT_FAILED+=("$p") done fi + + echo + if [ -n "$AGI_INSTALLED" ] && [ "$AGI_INSTALLED" = "$AGI_WANTED" ]; then + bold "AGI $AGI_INSTALLED is already installed in /opt/agi." + AGI_OK=1 + else + bold "Installing AGI $AGI_WANTED..." + [ -n "$AGI_INSTALLED" ] && echo " replacing the installed $AGI_INSTALLED" + AGI_TMP=$(mktemp -d "${TMPDIR:-/tmp}/agi-install.XXXXXX") \ + || die "could not create a temporary directory." + # 85MB of .deb or .zip; do not leave it behind on any exit path. + trap 'rm -rf "$AGI_TMP"' EXIT + + if [ "$PM" = apt ]; then + if install_agi_deb "$AGI_WANTED" "$AGI_TMP"; then + AGI_OK=1 + else + red " .deb install failed; falling back to the zip release." + install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1 + fi + else + install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1 + fi + + rm -rf "$AGI_TMP" + trap - EXIT + [ "$AGI_OK" -eq 1 ] || red "AGI installation failed." + fi fi # --------------------------------------------------------------- verify -echo -bold "Verifying the toolchain..." - MISSING=() check_cmd() { if command -v "$1" >/dev/null 2>&1; then @@ -191,54 +567,99 @@ check_cmd() { fi } -for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do - check_cmd "$c" -done +if [ "$WANT_RSYNC" -eq 1 ]; then + echo + bold "Verifying the toolchain..." -# Not required to build, so report it without failing the run. -if command -v shellcheck >/dev/null 2>&1; then - printf ' %-16s %s\n' "shellcheck" "$(command -v shellcheck)" -else - printf ' %-16s %s\n' "shellcheck" "not installed (optional)" + for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do + check_cmd "$c" + done + + # Not required to build, so report it without failing the run. + if command -v shellcheck >/dev/null 2>&1; then + printf ' %-16s %s\n' "shellcheck" "$(command -v shellcheck)" + else + printf ' %-16s %s\n' "shellcheck" "not installed (optional)" + fi + + echo + bold "Verifying optional libraries..." + check_header() { + # $1 = human name, $2 = header path + if [ -e "/usr/include/$2" ] || \ + find /usr/include -maxdepth 3 -name "$(basename "$2")" -print -quit \ + 2>/dev/null | grep -q .; then + printf ' %-16s yes\n' "$1" + else + printf ' %-16s no\n' "$1" + fi + } + check_header acl sys/acl.h + check_header xattr sys/xattr.h + check_header xxhash xxhash.h + check_header zstd zstd.h + check_header lz4 lz4.h + check_header openssl openssl/md5.h + check_header zlib zlib.h + + echo + bold "Verifying the manpage generator..." + MD_OK=0 + for m in cmarkgfm commonmark; do + if python3 -c "import $m" >/dev/null 2>&1; then + echo " python3 module '$m' importable" + MD_OK=1 + break + fi + done + if [ "$MD_OK" -eq 0 ]; then + if [ "$MINIMAL" -eq 1 ]; then + echo " skipped (--minimal); build with ./configure --disable-md2man" + else + red " neither cmarkgfm nor commonmark is importable." + echo " Install one with: python3 -mpip install --user commonmark" + echo " ...or build with: ./configure --disable-md2man" + fi + fi fi -echo -bold "Verifying optional libraries..." -check_header() { - # $1 = human name, $2 = header path - if [ -e "/usr/include/$2" ] || \ - find /usr/include -maxdepth 3 -name "$(basename "$2")" -print -quit \ - 2>/dev/null | grep -q .; then - printf ' %-16s yes\n' "$1" +if [ "$WANT_AGI" -eq 1 ]; then + echo + bold "Verifying AGI..." + if [ -x /opt/agi/agi ]; then + printf ' %-16s %s\n' "agi" "/opt/agi/agi ($(agi_installed_version))" else - printf ' %-16s no\n' "$1" + printf ' %-16s %s\n' "agi" "MISSING" fi -} -check_header acl sys/acl.h -check_header xattr sys/xattr.h -check_header xxhash xxhash.h -check_header zstd zstd.h -check_header lz4 lz4.h -check_header openssl openssl/md5.h -check_header zlib zlib.h -echo -bold "Verifying the manpage generator..." -MD_OK=0 -for m in cmarkgfm commonmark; do - if python3 -c "import $m" >/dev/null 2>&1; then - echo " python3 module '$m' importable" - MD_OK=1 - break + # The launcher settles for a JRE >= 11, but the GUI's jar is Java 19 + # bytecode -- an older JVM starts and then dies on UnsupportedClassVersion. + if command -v java >/dev/null 2>&1; then + JAVA_MAJOR=$(java -version 2>&1 \ + | sed -n '1s/.*version "\([0-9]\{1,\}\).*/\1/p') + JAVA_MAJOR=${JAVA_MAJOR:-0} + if [ "$JAVA_MAJOR" -ge 19 ]; then + printf ' %-16s java %s\n' "jre" "$JAVA_MAJOR" + else + printf ' %-16s java %s (too old, AGI needs 19+)\n' "jre" "$JAVA_MAJOR" + fi + else + printf ' %-16s %s\n' "jre" "MISSING" fi -done -if [ "$MD_OK" -eq 0 ]; then - if [ "$MINIMAL" -eq 1 ]; then - echo " skipped (--minimal); build with ./configure --disable-md2man" + + for lib in libgtk-3.so.0 libwebkit2gtk-4.1.so.0; do + if ldconfig -p 2>/dev/null | grep -q "$lib"; then + printf ' %-16s yes\n' "${lib%%.so*}" + else + printf ' %-16s no\n' "${lib%%.so*}" + fi + done + + # Not fatal: AGI runs without adb, it just cannot see Android devices. + if command -v adb >/dev/null 2>&1; then + printf ' %-16s %s\n' "adb" "$(command -v adb)" else - red " neither cmarkgfm nor commonmark is importable." - echo " Install one with: python3 -mpip install --user commonmark" - echo " ...or build with: ./configure --disable-md2man" + printf ' %-16s %s\n' "adb" "not installed (no Android devices)" fi fi @@ -255,8 +676,9 @@ if [ ${#OPT_FAILED[@]} -ne 0 ]; then echo "The build will still work, with those features disabled." fi -green "All required build dependencies are installed." -cat <<'EOF' +if [ "$WANT_RSYNC" -eq 1 ]; then + green "All required build dependencies are installed." + cat <<'EOF' Build rsync with either build system: @@ -267,3 +689,32 @@ Run the test suite (needs the autoconf build's helper programs): make check EOF +fi + +if [ "$WANT_AGI" -eq 1 ] && [ "$AGI_OK" -eq 1 ]; then + green "AGI $AGI_WANTED is installed." + if [ "$PM" = apt ]; then + cat <<'EOF' + +Run it from the desktop menu ("Android GPU Inspector") or: + + /opt/agi/agi # GUI (--console to keep it in the terminal) + /opt/agi/gapit # CLI + +Remove it with: sudo apt-get remove agi +EOF + else + cat <<'EOF' + +Run it from the desktop menu ("Android GPU Inspector") or: + + agi # GUI, symlinked into /usr/local/bin + /opt/agi/gapit # CLI + +Remove it with: sudo rm -rf /opt/agi /usr/local/bin/agi \ + /usr/share/applications/google-agi.desktop \ + /usr/share/mime/packages/agi.xml +EOF + fi + echo "Tracing an Android device needs adb and USB debugging enabled on it." +fi