X-Git-Url: https://vilimpoc.org/repos/dotfiles/blobdiff_plain/6f6caf69d4488bf9a1eb21e777cf7173c8af8e9a..5c01c42c07a9003596daec75d22f779e743f8e55:/setup-linux.sh?ds=sidebyside diff --git a/setup-linux.sh b/setup-linux.sh old mode 100644 new mode 100755 index afb36a7..43426a2 --- 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,234 @@ 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. + # + # ffmpeg and adb are here for the same reason: they are common across the + # projects on these machines rather than specific to any one of them, so + # installing them once here means a per-project setup script only has to + # CHECK for them instead of carrying its own package-manager logic. What + # stays out of this script on purpose is anything project-shaped -- no + # virtualenvs, no per-repo Python packages, no test tooling. Those belong + # next to the repo that needs them (e.g. RAWcorder's setup-linux-tests.sh). + # + # ffmpeg ffmpeg + ffprobe, for building and probing test media + # adb reaches Android devices. AGI installs it too, but only + # on an --agi run, and it is worth having either way. + # python3-venv Debian and Ubuntu split venv and ensurepip out of python3, + # so `python3 -m venv` fails on a stock Ubuntu without it. + # Arch bundles both into `python`, so there is nothing to add. + OPTIONAL+=(shellcheck ffmpeg) + case "$PM" in + apt) + COMMON_PKG=$(pick_pkg python3-venv) && OPTIONAL+=("$COMMON_PKG") + COMMON_PKG=$(pick_pkg adb android-sdk-platform-tools) && OPTIONAL+=("$COMMON_PKG") + ;; + pacman) + COMMON_PKG=$(pick_pkg android-tools) && OPTIONAL+=("$COMMON_PKG") + ;; + esac + + # 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 +fi + +# ------------------------------------------------------- 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 -done -if [ -n "$MARKDOWN_PKG" ]; then - OPTIONAL+=("$MARKDOWN_PKG") fi -if [ "$MINIMAL" -eq 1 ]; then - OPTIONAL=() +# ------------------------------------------------------- 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 +374,216 @@ 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() { + # Split across two 'local's: a value assigned in the same 'local' as the + # variable that uses it has not taken effect yet (SC2318). + local ver="$1" tmp="$2" + local 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 +594,104 @@ 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 + + # None of these is required to build rsync, so report them without failing + # the run. ffprobe has no package of its own -- it ships inside ffmpeg -- + # so it is checked rather than installed, which also catches a stripped-down + # distro build that leaves it out. + for c in shellcheck ffmpeg ffprobe adb; do + if command -v "$c" >/dev/null 2>&1; then + printf ' %-16s %s\n' "$c" "$(command -v "$c")" + else + printf ' %-16s %s\n' "$c" "not installed (optional)" + fi + done + + 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 +708,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 +721,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