3 # setup-linux.sh -- install the rsync build toolchain and AGI.
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.
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
16 # ./setup-linux.sh --dry-run # show what would be installed
18 # AGI_VERSION=3.3.3 ./setup-linux.sh pins a release instead of asking GitHub
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.
33 --minimal) MINIMAL=1; WANT_AGI=0 ;;
34 --dry-run) DRY_RUN=1 ;;
35 --no-agi) WANT_AGI=0 ;;
36 --agi-only) WANT_RSYNC=0; WANT_AGI=1 ;;
38 sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'
41 echo "setup-linux.sh: unknown option '$arg' (try --help)" >&2
46 # ---------------------------------------------------------------- helpers
48 red() { printf '\033[31m%s\033[0m\n' "$*"; }
49 green() { printf '\033[32m%s\033[0m\n' "$*"; }
50 bold() { printf '\033[1m%s\033[0m\n' "$*"; }
52 die() { red "ERROR: $*"; exit 1; }
54 # ------------------------------------------------- package manager wrapper
56 # Two families are supported: apt (Debian/Ubuntu) and pacman (Arch/CachyOS).
58 if command -v apt-get >/dev/null 2>&1; then
60 elif command -v pacman >/dev/null 2>&1; then
63 die "found neither apt-get nor pacman -- unsupported distribution."
70 cand=$(apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/{print $2}')
71 [ -n "$cand" ] && [ "$cand" != "(none)" ] ;;
73 pacman -Si "$1" >/dev/null 2>&1 ;;
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.
83 if pkg_available "$p"; then
92 [ $# -eq 0 ] && return 0
94 apt) apt-get install -y "$@" ;;
95 pacman) pacman -S --needed --noconfirm "$@" ;;
99 # ------------------------------------------------------- sanity checks
103 if [ -r /etc/os-release ]; then
104 # shellcheck disable=SC1091 # runtime file, not available to the linter
106 DISTRO="${ID:-unknown}"
107 RELEASE="${VERSION_ID:-unknown}"
110 bold "rsync build dependencies and AGI"
111 echo " distro ......... ${PRETTY_NAME:-$DISTRO $RELEASE}"
112 echo " architecture ... $(uname -m)"
113 echo " packages ....... $PM"
116 ubuntu|debian|linuxmint|pop|arch|cachyos|endeavouros|manjaro) ;;
118 red "Warning: '$DISTRO' is untested; assuming it behaves like a $PM distro."
119 echo "Continuing anyway -- package names may differ."
123 # AGI ships x86-64 binaries only.
124 case "$(uname -m)" in
126 *) if [ "$WANT_AGI" -eq 1 ]; then
127 red "Warning: AGI publishes x86-64 Linux builds only; skipping it."
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.
135 if [ "$(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."
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.
142 exec sudo AGI_VERSION="${AGI_VERSION:-}" "$0" "$@"
145 export DEBIAN_FRONTEND=noninteractive
146 export NEEDRESTART_MODE=a # don't prompt about restarting services
148 # ------------------------------------------------------- rsync packages
153 if [ "$WANT_RSYNC" -eq 1 ]; then
156 # Needed to build rsync at all.
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
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)
169 # Optional: each one switches on an rsync feature. Missing ones only
170 # mean a smaller feature set, never a failed build.
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
183 # Arch splits nothing out into -dev packages: the headers ship with
184 # the library, and base-devel covers the compiler toolchain.
186 base-devel # gcc, make, and friends
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
209 # Development tooling: not needed to build rsync, but useful when working
210 # on its shell scripts -- including this one, which shellcheck keeps honest.
212 # ffmpeg and adb are here for the same reason: they are common across the
213 # projects on these machines rather than specific to any one of them, so
214 # installing them once here means a per-project setup script only has to
215 # CHECK for them instead of carrying its own package-manager logic. What
216 # stays out of this script on purpose is anything project-shaped -- no
217 # virtualenvs, no per-repo Python packages, no test tooling. Those belong
218 # next to the repo that needs them (e.g. RAWcorder's setup-linux-tests.sh).
220 # ffmpeg ffmpeg + ffprobe, for building and probing test media
221 # adb reaches Android devices. AGI installs it too, but only
222 # on an --agi run, and it is worth having either way.
223 # python3-venv Debian and Ubuntu split venv and ensurepip out of python3,
224 # so `python3 -m venv` fails on a stock Ubuntu without it.
225 # Arch bundles both into `python`, so there is nothing to add.
226 OPTIONAL+=(shellcheck ffmpeg)
229 COMMON_PKG=$(pick_pkg python3-venv) && OPTIONAL+=("$COMMON_PKG")
230 COMMON_PKG=$(pick_pkg adb android-sdk-platform-tools) && OPTIONAL+=("$COMMON_PKG")
233 COMMON_PKG=$(pick_pkg android-tools) && OPTIONAL+=("$COMMON_PKG")
237 # The manpages need one of two python3 markdown libraries; upstream prefers
238 # cmarkgfm. Pick whichever this release actually offers.
240 apt) MARKDOWN_PKG=$(pick_pkg python3-cmarkgfm python3-commonmark) ;;
241 pacman) MARKDOWN_PKG=$(pick_pkg python-cmarkgfm python-commonmark) ;;
243 if [ -n "$MARKDOWN_PKG" ]; then
244 OPTIONAL+=("$MARKDOWN_PKG")
247 if [ "$MINIMAL" -eq 1 ]; then
252 # ------------------------------------------------------- AGI packages
254 # AGI is a Java/SWT desktop app plus a set of Go binaries. The .deb declares
255 # "Depends: openjdk-22-jre, libgtk-3-0, libwebkit2gtk-4.1-0", but Ubuntu has
256 # never packaged openjdk-22 -- so pick a JRE that exists instead. The GUI's
257 # class files are Java 19 bytecode, so anything from 21 up runs it; the launcher
258 # itself only insists on 11. adb is not a hard dependency, but without it AGI
259 # cannot see Android devices ("adb could not be found from ANDROID_HOME or PATH").
266 if [ "$WANT_AGI" -eq 1 ]; then
269 AGI_JRE=$(pick_pkg openjdk-21-jre openjdk-25-jre openjdk-24-jre \
270 openjdk-23-jre openjdk-22-jre openjdk-26-jre)
271 AGI_GTK=$(pick_pkg libgtk-3-0t64 libgtk-3-0)
272 AGI_WEBKIT=$(pick_pkg libwebkit2gtk-4.1-0)
273 AGI_ADB=$(pick_pkg adb android-sdk-platform-tools)
274 # curl fetches the release, binutils' ar rewrites the .deb's control
275 # member, ca-certificates lets curl trust github.com.
276 AGI_DEPS=(curl ca-certificates binutils xz-utils unzip)
279 AGI_JRE=$(pick_pkg jre-openjdk jre21-openjdk)
280 AGI_GTK=$(pick_pkg gtk3)
281 AGI_WEBKIT=$(pick_pkg webkit2gtk-4.1)
282 AGI_ADB=$(pick_pkg android-tools)
283 AGI_DEPS=(curl unzip)
287 for p in "$AGI_GTK" "$AGI_WEBKIT" "$AGI_ADB"; do
288 [ -n "$p" ] && AGI_DEPS+=("$p")
291 # A JRE already on the box is good enough if it is new enough to load the
292 # GUI's Java 19 class files. On apt we install one anyway: the .deb's
293 # rewritten Depends line has to name a package dpkg knows about.
295 if command -v java >/dev/null 2>&1; then
296 JAVA_MAJOR=$(java -version 2>&1 \
297 | sed -n '1s/.*version "\([0-9]\{1,\}\).*/\1/p')
298 JAVA_MAJOR=${JAVA_MAJOR:-0}
300 if [ -n "$AGI_JRE" ] && { [ "$PM" = apt ] || [ "$JAVA_MAJOR" -lt 19 ]; }; then
301 AGI_DEPS+=("$AGI_JRE")
305 # ------------------------------------------------------- AGI release info
307 # Used only when GitHub cannot be reached; the release that this script was
308 # last checked against.
309 AGI_FALLBACK_VERSION=3.3.3
313 agi_installed_version() {
314 [ -r /opt/agi/build.properties ] || return 1
316 /^Version\.Major/ { maj = $2 }
317 /^Version\.Minor/ { min = $2 }
318 /^Version\.Micro/ { mic = $2 }
319 END { if (maj == "") exit 1; print maj "." min "." mic }
320 ' /opt/agi/build.properties | tr -d ' \r'
323 agi_latest_version() {
324 command -v curl >/dev/null 2>&1 || return 1
325 curl -fsSL --retry 3 --max-time 20 \
326 https://api.github.com/repos/google/agi/releases/latest 2>/dev/null \
327 | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\{0,1\}\([^"]*\)".*/\1/p' \
331 if [ "$WANT_AGI" -eq 1 ]; then
332 AGI_WANTED="${AGI_VERSION:-}"
333 if [ -z "$AGI_WANTED" ]; then
334 AGI_WANTED=$(agi_latest_version)
336 if [ -z "$AGI_WANTED" ]; then
337 red "Could not reach the GitHub releases API; falling back to AGI $AGI_FALLBACK_VERSION."
338 AGI_WANTED="$AGI_FALLBACK_VERSION"
340 AGI_INSTALLED=$(agi_installed_version) || AGI_INSTALLED=""
343 AGI_BASE_URL="https://github.com/google/agi/releases/download"
345 # ------------------------------------------------------------- dry run
347 if [ "$DRY_RUN" -eq 1 ]; then
348 if [ "$WANT_RSYNC" -eq 1 ]; then
350 bold "Would install (rsync, required):"
351 printf ' %s\n' "${REQUIRED[@]}"
352 if [ ${#OPTIONAL[@]} -gt 0 ]; then
353 bold "Would install (rsync, optional):"
354 printf ' %s\n' "${OPTIONAL[@]}"
357 if [ "$WANT_AGI" -eq 1 ]; then
359 bold "Would install (AGI dependencies):"
360 printf ' %s\n' "${AGI_DEPS[@]}"
361 bold "Would install AGI $AGI_WANTED:"
362 if [ "$PM" = apt ]; then
363 echo " $AGI_BASE_URL/v$AGI_WANTED/agi-$AGI_WANTED-linux.deb"
364 echo " via dpkg, with Depends retargeted at ${AGI_JRE:-the installed JRE}"
366 echo " $AGI_BASE_URL/v$AGI_WANTED/agi-$AGI_WANTED-linux.zip"
367 echo " unpacked into /opt/agi, symlinked as /usr/local/bin/agi"
369 [ -n "$AGI_INSTALLED" ] && echo " (currently installed: $AGI_INSTALLED)"
374 # ------------------------------------------------------------- install
379 bold "Updating package lists..."
380 if ! apt-get update -qq; then
381 red "apt-get update failed -- continuing with the cached lists."
385 # No "pacman -Sy" here on purpose: refreshing the database without also
386 # upgrading is how Arch systems end up half-broken. If a package fetch
387 # 404s below, the sync database is stale -- run "pacman -Syu" first.
388 bold "Using the existing pacman database (run 'pacman -Syu' if it is stale)."
394 if [ "$WANT_RSYNC" -eq 1 ]; then
396 bold "Installing required packages..."
397 if ! pkg_install "${REQUIRED[@]}"; then
398 # Fall back to one-at-a-time so the failing package is obvious.
399 red "Bulk install failed; retrying individually to find the culprit..."
401 for p in "${REQUIRED[@]}"; do
402 pkg_install "$p" || FAILED+=("$p")
404 [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}"
407 if [ ${#OPTIONAL[@]} -gt 0 ]; then
409 bold "Installing optional packages..."
410 if ! pkg_install "${OPTIONAL[@]}"; then
411 red "Bulk install failed; retrying individually..."
412 for p in "${OPTIONAL[@]}"; do
413 pkg_install "$p" || OPT_FAILED+=("$p")
419 # ----------------------------------------------------------------- AGI
421 # The .desktop and MIME files the .deb ships, replayed for the .zip install so
422 # both routes leave the same system behind.
423 agi_desktop_files() {
424 cat >/usr/share/applications/google-agi.desktop <<'DESKTOP'
428 Name=Android GPU Inspector
429 Comment=Android GPU Inspector
430 Categories=Development;Debugger;Graphics;3DGraphics
431 Icon=/opt/agi/icon.png
434 MimeType=application/x-gfxtrace;application/x-perfetto
437 mkdir -p /usr/share/mime/packages
438 cat >/usr/share/mime/packages/agi.xml <<'MIME'
439 <?xml version="1.0" encoding="UTF-8"?>
440 <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
441 <mime-type type="application/x-gfxtrace">
442 <comment>Graphics API Trace</comment>
443 <glob pattern="*.gfxtrace"/>
445 <mime-type type="application/x-perfetto">
446 <comment>System Profile Trace</comment>
447 <glob pattern="*.perfetto"/>
452 command -v update-mime-database >/dev/null 2>&1 \
453 && update-mime-database /usr/share/mime >/dev/null 2>&1
454 command -v update-desktop-database >/dev/null 2>&1 \
455 && update-desktop-database /usr/share/applications >/dev/null 2>&1
460 # $1 = url, $2 = destination file
461 echo " fetching $(basename "$1")..."
462 curl -fL --retry 3 --progress-bar -o "$2" "$1"
465 # Every dependency the .deb names has to exist in the archive, or dpkg will
466 # refuse the package. openjdk-22-jre does not exist in any Ubuntu release,
467 # so rewrite that line to the JRE we just installed. Only the control member
468 # is rebuilt -- unpacking and recompressing the 230MB payload with dpkg-deb
469 # would take minutes and change nothing.
470 deb_depends_satisfiable() {
472 while IFS= read -r dep; do
473 dep=$(echo "$dep" | sed 's/(.*)//; s/^[[:space:]]*//; s/[[:space:]]*$//')
474 [ -z "$dep" ] && continue
475 pkg_available "$dep" || return 1
476 done < <(dpkg-deb -f "$1" Depends 2>/dev/null | tr ',' '\n')
480 rewrite_deb_depends() {
481 # $1 = .deb, $2 = new Depends line, $3 = scratch directory
482 local deb="$1" depends="$2" work="$3/control"
483 command -v ar >/dev/null 2>&1 || return 1
485 mkdir -p "$work/c" || return 1
486 ( cd "$work" && ar x "$deb" control.tar.xz ) || return 1
487 tar xf "$work/control.tar.xz" -C "$work/c" || return 1
488 sed -i "s|^Depends:.*|Depends: $depends|" "$work/c/control" || return 1
489 rm -f "$work/control.tar.xz"
490 ( cd "$work/c" && tar cJf ../control.tar.xz . ) || return 1
491 ( cd "$work" && ar r "$deb" control.tar.xz ) || return 1
492 # Make sure dpkg still understands the archive we just edited.
493 dpkg-deb -f "$deb" Depends >/dev/null 2>&1 || return 1
497 # Split across two 'local's: a value assigned in the same 'local' as the
498 # variable that uses it has not taken effect yet (SC2318).
499 local ver="$1" tmp="$2"
500 local deb="$tmp/agi.deb" depends
502 agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.deb" "$deb" || return 1
504 if ! deb_depends_satisfiable "$deb"; then
505 depends="${AGI_JRE:-default-jre}"
506 [ -n "$AGI_GTK" ] && depends="$depends, $AGI_GTK"
507 [ -n "$AGI_WEBKIT" ] && depends="$depends, $AGI_WEBKIT"
508 echo " retargeting the package's Depends at: $depends"
509 rewrite_deb_depends "$deb" "$depends" "$tmp" || {
510 red " could not rewrite the .deb's dependencies."
515 if ! dpkg -i "$deb"; then
516 red " dpkg reported a problem; asking apt to resolve it..."
517 apt-get -f install -y || return 1
518 dpkg -i "$deb" || return 1
523 local ver="$1" tmp="$2"
525 agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.zip" "$tmp/agi.zip" || return 1
526 unzip -q "$tmp/agi.zip" -d "$tmp/unpacked" || return 1
527 [ -x "$tmp/unpacked/agi/agi" ] || {
528 red " the zip did not contain agi/agi -- leaving /opt/agi alone."
532 # Only ever replace a directory that looks like a previous AGI install.
533 if [ -e /opt/agi ] && [ ! -e /opt/agi/build.properties ]; then
534 red " /opt/agi exists but is not an AGI install; refusing to replace it."
539 mv "$tmp/unpacked/agi" /opt/agi || return 1
540 chown -R root:root /opt/agi
541 ln -sf /opt/agi/agi /usr/local/bin/agi
546 if [ "$WANT_AGI" -eq 1 ]; then
548 bold "Installing AGI dependencies..."
549 if ! pkg_install "${AGI_DEPS[@]}"; then
550 red "Bulk install failed; retrying individually..."
551 for p in "${AGI_DEPS[@]}"; do
552 pkg_install "$p" || OPT_FAILED+=("$p")
557 if [ -n "$AGI_INSTALLED" ] && [ "$AGI_INSTALLED" = "$AGI_WANTED" ]; then
558 bold "AGI $AGI_INSTALLED is already installed in /opt/agi."
561 bold "Installing AGI $AGI_WANTED..."
562 [ -n "$AGI_INSTALLED" ] && echo " replacing the installed $AGI_INSTALLED"
563 AGI_TMP=$(mktemp -d "${TMPDIR:-/tmp}/agi-install.XXXXXX") \
564 || die "could not create a temporary directory."
565 # 85MB of .deb or .zip; do not leave it behind on any exit path.
566 trap 'rm -rf "$AGI_TMP"' EXIT
568 if [ "$PM" = apt ]; then
569 if install_agi_deb "$AGI_WANTED" "$AGI_TMP"; then
572 red " .deb install failed; falling back to the zip release."
573 install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1
576 install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1
581 [ "$AGI_OK" -eq 1 ] || red "AGI installation failed."
585 # --------------------------------------------------------------- verify
589 if command -v "$1" >/dev/null 2>&1; then
590 printf ' %-16s %s\n' "$1" "$(command -v "$1")"
592 printf ' %-16s %s\n' "$1" "MISSING"
597 if [ "$WANT_RSYNC" -eq 1 ]; then
599 bold "Verifying the toolchain..."
601 for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do
605 # None of these is required to build rsync, so report them without failing
606 # the run. ffprobe has no package of its own -- it ships inside ffmpeg --
607 # so it is checked rather than installed, which also catches a stripped-down
608 # distro build that leaves it out.
609 for c in shellcheck ffmpeg ffprobe adb; do
610 if command -v "$c" >/dev/null 2>&1; then
611 printf ' %-16s %s\n' "$c" "$(command -v "$c")"
613 printf ' %-16s %s\n' "$c" "not installed (optional)"
618 bold "Verifying optional libraries..."
620 # $1 = human name, $2 = header path
621 if [ -e "/usr/include/$2" ] || \
622 find /usr/include -maxdepth 3 -name "$(basename "$2")" -print -quit \
623 2>/dev/null | grep -q .; then
624 printf ' %-16s yes\n' "$1"
626 printf ' %-16s no\n' "$1"
629 check_header acl sys/acl.h
630 check_header xattr sys/xattr.h
631 check_header xxhash xxhash.h
632 check_header zstd zstd.h
633 check_header lz4 lz4.h
634 check_header openssl openssl/md5.h
635 check_header zlib zlib.h
638 bold "Verifying the manpage generator..."
640 for m in cmarkgfm commonmark; do
641 if python3 -c "import $m" >/dev/null 2>&1; then
642 echo " python3 module '$m' importable"
647 if [ "$MD_OK" -eq 0 ]; then
648 if [ "$MINIMAL" -eq 1 ]; then
649 echo " skipped (--minimal); build with ./configure --disable-md2man"
651 red " neither cmarkgfm nor commonmark is importable."
652 echo " Install one with: python3 -mpip install --user commonmark"
653 echo " ...or build with: ./configure --disable-md2man"
658 if [ "$WANT_AGI" -eq 1 ]; then
660 bold "Verifying AGI..."
661 if [ -x /opt/agi/agi ]; then
662 printf ' %-16s %s\n' "agi" "/opt/agi/agi ($(agi_installed_version))"
664 printf ' %-16s %s\n' "agi" "MISSING"
667 # The launcher settles for a JRE >= 11, but the GUI's jar is Java 19
668 # bytecode -- an older JVM starts and then dies on UnsupportedClassVersion.
669 if command -v java >/dev/null 2>&1; then
670 JAVA_MAJOR=$(java -version 2>&1 \
671 | sed -n '1s/.*version "\([0-9]\{1,\}\).*/\1/p')
672 JAVA_MAJOR=${JAVA_MAJOR:-0}
673 if [ "$JAVA_MAJOR" -ge 19 ]; then
674 printf ' %-16s java %s\n' "jre" "$JAVA_MAJOR"
676 printf ' %-16s java %s (too old, AGI needs 19+)\n' "jre" "$JAVA_MAJOR"
679 printf ' %-16s %s\n' "jre" "MISSING"
682 for lib in libgtk-3.so.0 libwebkit2gtk-4.1.so.0; do
683 if ldconfig -p 2>/dev/null | grep -q "$lib"; then
684 printf ' %-16s yes\n' "${lib%%.so*}"
686 printf ' %-16s no\n' "${lib%%.so*}"
690 # Not fatal: AGI runs without adb, it just cannot see Android devices.
691 if command -v adb >/dev/null 2>&1; then
692 printf ' %-16s %s\n' "adb" "$(command -v adb)"
694 printf ' %-16s %s\n' "adb" "not installed (no Android devices)"
698 # ---------------------------------------------------------------- done
701 if [ ${#MISSING[@]} -ne 0 ]; then
702 red "Missing required tools: ${MISSING[*]}"
706 if [ ${#OPT_FAILED[@]} -ne 0 ]; then
707 red "Optional packages that failed to install: ${OPT_FAILED[*]}"
708 echo "The build will still work, with those features disabled."
711 if [ "$WANT_RSYNC" -eq 1 ]; then
712 green "All required build dependencies are installed."
715 Build rsync with either build system:
717 autoconf: ./configure && make
718 CMake: cmake -B build -G Ninja && cmake --build build
720 Run the test suite (needs the autoconf build's helper programs):
726 if [ "$WANT_AGI" -eq 1 ] && [ "$AGI_OK" -eq 1 ]; then
727 green "AGI $AGI_WANTED is installed."
728 if [ "$PM" = apt ]; then
731 Run it from the desktop menu ("Android GPU Inspector") or:
733 /opt/agi/agi # GUI (--console to keep it in the terminal)
736 Remove it with: sudo apt-get remove agi
741 Run it from the desktop menu ("Android GPU Inspector") or:
743 agi # GUI, symlinked into /usr/local/bin
746 Remove it with: sudo rm -rf /opt/agi /usr/local/bin/agi \
747 /usr/share/applications/google-agi.desktop \
748 /usr/share/mime/packages/agi.xml
751 echo "Tracing an Android device needs adb and USB debugging enabled on it."