#!/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
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"
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."
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
# ------------------------------------------------------------- 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'
+<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+ <mime-type type="application/x-gfxtrace">
+ <comment>Graphics API Trace</comment>
+ <glob pattern="*.gfxtrace"/>
+ </mime-type>
+ <mime-type type="application/x-perfetto">
+ <comment>System Profile Trace</comment>
+ <glob pattern="*.perfetto"/>
+ </mime-type>
+</mime-info>
+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
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
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:
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