]>
| Commit | Line | Data |
|---|---|---|
| 1 | #!/bin/bash | |
| 2 | # | |
| 3 | # setup-linux.sh -- install the rsync build toolchain and AGI. | |
| 4 | # | |
| 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. | |
| 10 | # | |
| 11 | # Usage: | |
| 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 | |
| 17 | # | |
| 18 | # AGI_VERSION=3.3.3 ./setup-linux.sh pins a release instead of asking GitHub | |
| 19 | # for the latest one. | |
| 20 | # | |
| 21 | # Safe to re-run: the package managers skip anything already present, and AGI | |
| 22 | # is re-downloaded only when the installed version is not the wanted one. | |
| 23 | ||
| 24 | set -uo pipefail | |
| 25 | ||
| 26 | MINIMAL=0 | |
| 27 | DRY_RUN=0 | |
| 28 | WANT_AGI=1 | |
| 29 | WANT_RSYNC=1 | |
| 30 | ||
| 31 | for arg in "$@"; do | |
| 32 | case "$arg" in | |
| 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 ;; | |
| 37 | -h|--help) | |
| 38 | sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//' | |
| 39 | exit 0 ;; | |
| 40 | *) | |
| 41 | echo "setup-linux.sh: unknown option '$arg' (try --help)" >&2 | |
| 42 | exit 2 ;; | |
| 43 | esac | |
| 44 | done | |
| 45 | ||
| 46 | # ---------------------------------------------------------------- helpers | |
| 47 | ||
| 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' "$*"; } | |
| 51 | ||
| 52 | die() { red "ERROR: $*"; exit 1; } | |
| 53 | ||
| 54 | # ------------------------------------------------- package manager wrapper | |
| 55 | ||
| 56 | # Two families are supported: apt (Debian/Ubuntu) and pacman (Arch/CachyOS). | |
| 57 | PM="" | |
| 58 | if command -v apt-get >/dev/null 2>&1; then | |
| 59 | PM=apt | |
| 60 | elif command -v pacman >/dev/null 2>&1; then | |
| 61 | PM=pacman | |
| 62 | else | |
| 63 | die "found neither apt-get nor pacman -- unsupported distribution." | |
| 64 | fi | |
| 65 | ||
| 66 | pkg_available() { | |
| 67 | case "$PM" in | |
| 68 | apt) | |
| 69 | local cand | |
| 70 | cand=$(apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/{print $2}') | |
| 71 | [ -n "$cand" ] && [ "$cand" != "(none)" ] ;; | |
| 72 | pacman) | |
| 73 | pacman -Si "$1" >/dev/null 2>&1 ;; | |
| 74 | esac | |
| 75 | } | |
| 76 | ||
| 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. | |
| 80 | pick_pkg() { | |
| 81 | local p | |
| 82 | for p in "$@"; do | |
| 83 | if pkg_available "$p"; then | |
| 84 | echo "$p" | |
| 85 | return 0 | |
| 86 | fi | |
| 87 | done | |
| 88 | return 1 | |
| 89 | } | |
| 90 | ||
| 91 | pkg_install() { | |
| 92 | [ $# -eq 0 ] && return 0 | |
| 93 | case "$PM" in | |
| 94 | apt) apt-get install -y "$@" ;; | |
| 95 | pacman) pacman -S --needed --noconfirm "$@" ;; | |
| 96 | esac | |
| 97 | } | |
| 98 | ||
| 99 | # ------------------------------------------------------- sanity checks | |
| 100 | ||
| 101 | DISTRO="unknown" | |
| 102 | RELEASE="unknown" | |
| 103 | if [ -r /etc/os-release ]; then | |
| 104 | # shellcheck disable=SC1091 # runtime file, not available to the linter | |
| 105 | . /etc/os-release | |
| 106 | DISTRO="${ID:-unknown}" | |
| 107 | RELEASE="${VERSION_ID:-unknown}" | |
| 108 | fi | |
| 109 | ||
| 110 | bold "rsync build dependencies and AGI" | |
| 111 | echo " distro ......... ${PRETTY_NAME:-$DISTRO $RELEASE}" | |
| 112 | echo " architecture ... $(uname -m)" | |
| 113 | echo " packages ....... $PM" | |
| 114 | ||
| 115 | case "$DISTRO" in | |
| 116 | ubuntu|debian|linuxmint|pop|arch|cachyos|endeavouros|manjaro) ;; | |
| 117 | *) echo | |
| 118 | red "Warning: '$DISTRO' is untested; assuming it behaves like a $PM distro." | |
| 119 | echo "Continuing anyway -- package names may differ." | |
| 120 | ;; | |
| 121 | esac | |
| 122 | ||
| 123 | # AGI ships x86-64 binaries only. | |
| 124 | case "$(uname -m)" in | |
| 125 | x86_64|amd64) ;; | |
| 126 | *) if [ "$WANT_AGI" -eq 1 ]; then | |
| 127 | red "Warning: AGI publishes x86-64 Linux builds only; skipping it." | |
| 128 | WANT_AGI=0 | |
| 129 | fi | |
| 130 | ;; | |
| 131 | esac | |
| 132 | ||
| 133 | # The package managers need root. Re-exec under sudo rather than sprinkling | |
| 134 | # it around, so that a single password prompt covers the whole run. | |
| 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." | |
| 138 | echo | |
| 139 | echo "Re-running under sudo..." | |
| 140 | # Plain sudo, not "sudo -E": some sudoers configs refuse to preserve the | |
| 141 | # environment, and the exports below happen in the root instance anyway. | |
| 142 | exec sudo AGI_VERSION="${AGI_VERSION:-}" "$0" "$@" | |
| 143 | fi | |
| 144 | ||
| 145 | export DEBIAN_FRONTEND=noninteractive | |
| 146 | export NEEDRESTART_MODE=a # don't prompt about restarting services | |
| 147 | ||
| 148 | # ------------------------------------------------------- rsync packages | |
| 149 | ||
| 150 | REQUIRED=() | |
| 151 | OPTIONAL=() | |
| 152 | ||
| 153 | if [ "$WANT_RSYNC" -eq 1 ]; then | |
| 154 | case "$PM" in | |
| 155 | apt) | |
| 156 | # Needed to build rsync at all. | |
| 157 | REQUIRED=( | |
| 158 | build-essential # gcc, g++, make, libc headers | |
| 159 | gawk # rsync needs a modern awk for its code generators | |
| 160 | autoconf # ./configure is generated from configure.ac | |
| 161 | automake | |
| 162 | python3 # manpage + header generators, and the test suite | |
| 163 | cmake # the CMake build | |
| 164 | ninja-build # ...and its default generator | |
| 165 | git # version stamping (git-version.h) | |
| 166 | pkg-config | |
| 167 | ) | |
| 168 | ||
| 169 | # Optional: each one switches on an rsync feature. Missing ones only | |
| 170 | # mean a smaller feature set, never a failed build. | |
| 171 | OPTIONAL=( | |
| 172 | acl libacl1-dev # --acls (helper tools also used by the tests) | |
| 173 | attr libattr1-dev # --xattrs (likewise) | |
| 174 | libxxhash-dev # xxhash checksums (becomes the default) | |
| 175 | libzstd-dev # zstd compression (becomes the default) | |
| 176 | liblz4-dev # lz4 compression | |
| 177 | libssl-dev # OpenSSL MD4/MD5 | |
| 178 | zlib1g-dev # for cmake -DRSYNC_EXTERNAL_ZLIB=ON | |
| 179 | libpopt-dev # for ./configure --with-included-popt=no | |
| 180 | ) | |
| 181 | ;; | |
| 182 | pacman) | |
| 183 | # Arch splits nothing out into -dev packages: the headers ship with | |
| 184 | # the library, and base-devel covers the compiler toolchain. | |
| 185 | REQUIRED=( | |
| 186 | base-devel # gcc, make, and friends | |
| 187 | gawk | |
| 188 | autoconf | |
| 189 | automake | |
| 190 | python | |
| 191 | cmake | |
| 192 | ninja | |
| 193 | git | |
| 194 | pkgconf | |
| 195 | ) | |
| 196 | OPTIONAL=( | |
| 197 | acl # --acls | |
| 198 | attr # --xattrs | |
| 199 | xxhash # xxhash checksums (becomes the default) | |
| 200 | zstd # zstd compression (becomes the default) | |
| 201 | lz4 # lz4 compression | |
| 202 | openssl # OpenSSL MD4/MD5 | |
| 203 | zlib # for cmake -DRSYNC_EXTERNAL_ZLIB=ON | |
| 204 | popt # for ./configure --with-included-popt=no | |
| 205 | ) | |
| 206 | ;; | |
| 207 | esac | |
| 208 | ||
| 209 | # Development tooling: not needed to build rsync, but useful when working | |
| 210 | # on its shell scripts -- including this one, which shellcheck keeps honest. | |
| 211 | # | |
| 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). | |
| 219 | # | |
| 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) | |
| 227 | case "$PM" in | |
| 228 | apt) | |
| 229 | COMMON_PKG=$(pick_pkg python3-venv) && OPTIONAL+=("$COMMON_PKG") | |
| 230 | COMMON_PKG=$(pick_pkg adb android-sdk-platform-tools) && OPTIONAL+=("$COMMON_PKG") | |
| 231 | ;; | |
| 232 | pacman) | |
| 233 | COMMON_PKG=$(pick_pkg android-tools) && OPTIONAL+=("$COMMON_PKG") | |
| 234 | ;; | |
| 235 | esac | |
| 236 | ||
| 237 | # The manpages need one of two python3 markdown libraries; upstream prefers | |
| 238 | # cmarkgfm. Pick whichever this release actually offers. | |
| 239 | case "$PM" in | |
| 240 | apt) MARKDOWN_PKG=$(pick_pkg python3-cmarkgfm python3-commonmark) ;; | |
| 241 | pacman) MARKDOWN_PKG=$(pick_pkg python-cmarkgfm python-commonmark) ;; | |
| 242 | esac | |
| 243 | if [ -n "$MARKDOWN_PKG" ]; then | |
| 244 | OPTIONAL+=("$MARKDOWN_PKG") | |
| 245 | fi | |
| 246 | ||
| 247 | if [ "$MINIMAL" -eq 1 ]; then | |
| 248 | OPTIONAL=() | |
| 249 | fi | |
| 250 | fi | |
| 251 | ||
| 252 | # ------------------------------------------------------- AGI packages | |
| 253 | ||
| 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"). | |
| 260 | AGI_DEPS=() | |
| 261 | AGI_JRE="" | |
| 262 | AGI_GTK="" | |
| 263 | AGI_WEBKIT="" | |
| 264 | AGI_ADB="" | |
| 265 | ||
| 266 | if [ "$WANT_AGI" -eq 1 ]; then | |
| 267 | case "$PM" in | |
| 268 | apt) | |
| 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) | |
| 277 | ;; | |
| 278 | pacman) | |
| 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) | |
| 284 | ;; | |
| 285 | esac | |
| 286 | ||
| 287 | for p in "$AGI_GTK" "$AGI_WEBKIT" "$AGI_ADB"; do | |
| 288 | [ -n "$p" ] && AGI_DEPS+=("$p") | |
| 289 | done | |
| 290 | ||
| 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. | |
| 294 | JAVA_MAJOR=0 | |
| 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} | |
| 299 | fi | |
| 300 | if [ -n "$AGI_JRE" ] && { [ "$PM" = apt ] || [ "$JAVA_MAJOR" -lt 19 ]; }; then | |
| 301 | AGI_DEPS+=("$AGI_JRE") | |
| 302 | fi | |
| 303 | fi | |
| 304 | ||
| 305 | # ------------------------------------------------------- AGI release info | |
| 306 | ||
| 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 | |
| 310 | AGI_WANTED="" | |
| 311 | AGI_INSTALLED="" | |
| 312 | ||
| 313 | agi_installed_version() { | |
| 314 | [ -r /opt/agi/build.properties ] || return 1 | |
| 315 | awk -F= ' | |
| 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' | |
| 321 | } | |
| 322 | ||
| 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' \ | |
| 328 | | head -1 | |
| 329 | } | |
| 330 | ||
| 331 | if [ "$WANT_AGI" -eq 1 ]; then | |
| 332 | AGI_WANTED="${AGI_VERSION:-}" | |
| 333 | if [ -z "$AGI_WANTED" ]; then | |
| 334 | AGI_WANTED=$(agi_latest_version) | |
| 335 | fi | |
| 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" | |
| 339 | fi | |
| 340 | AGI_INSTALLED=$(agi_installed_version) || AGI_INSTALLED="" | |
| 341 | fi | |
| 342 | ||
| 343 | AGI_BASE_URL="https://github.com/google/agi/releases/download" | |
| 344 | ||
| 345 | # ------------------------------------------------------------- dry run | |
| 346 | ||
| 347 | if [ "$DRY_RUN" -eq 1 ]; then | |
| 348 | if [ "$WANT_RSYNC" -eq 1 ]; then | |
| 349 | echo | |
| 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[@]}" | |
| 355 | fi | |
| 356 | fi | |
| 357 | if [ "$WANT_AGI" -eq 1 ]; then | |
| 358 | echo | |
| 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}" | |
| 365 | else | |
| 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" | |
| 368 | fi | |
| 369 | [ -n "$AGI_INSTALLED" ] && echo " (currently installed: $AGI_INSTALLED)" | |
| 370 | fi | |
| 371 | exit 0 | |
| 372 | fi | |
| 373 | ||
| 374 | # ------------------------------------------------------------- install | |
| 375 | ||
| 376 | echo | |
| 377 | case "$PM" in | |
| 378 | apt) | |
| 379 | bold "Updating package lists..." | |
| 380 | if ! apt-get update -qq; then | |
| 381 | red "apt-get update failed -- continuing with the cached lists." | |
| 382 | fi | |
| 383 | ;; | |
| 384 | pacman) | |
| 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)." | |
| 389 | ;; | |
| 390 | esac | |
| 391 | ||
| 392 | OPT_FAILED=() | |
| 393 | ||
| 394 | if [ "$WANT_RSYNC" -eq 1 ]; then | |
| 395 | echo | |
| 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..." | |
| 400 | FAILED=() | |
| 401 | for p in "${REQUIRED[@]}"; do | |
| 402 | pkg_install "$p" || FAILED+=("$p") | |
| 403 | done | |
| 404 | [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}" | |
| 405 | fi | |
| 406 | ||
| 407 | if [ ${#OPTIONAL[@]} -gt 0 ]; then | |
| 408 | echo | |
| 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") | |
| 414 | done | |
| 415 | fi | |
| 416 | fi | |
| 417 | fi | |
| 418 | ||
| 419 | # ----------------------------------------------------------------- AGI | |
| 420 | ||
| 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' | |
| 425 | [Desktop Entry] | |
| 426 | Encoding=UTF-8 | |
| 427 | Type=Application | |
| 428 | Name=Android GPU Inspector | |
| 429 | Comment=Android GPU Inspector | |
| 430 | Categories=Development;Debugger;Graphics;3DGraphics | |
| 431 | Icon=/opt/agi/icon.png | |
| 432 | Exec=/opt/agi/agi %f | |
| 433 | Terminal=false | |
| 434 | MimeType=application/x-gfxtrace;application/x-perfetto | |
| 435 | DESKTOP | |
| 436 | ||
| 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"/> | |
| 444 | </mime-type> | |
| 445 | <mime-type type="application/x-perfetto"> | |
| 446 | <comment>System Profile Trace</comment> | |
| 447 | <glob pattern="*.perfetto"/> | |
| 448 | </mime-type> | |
| 449 | </mime-info> | |
| 450 | MIME | |
| 451 | ||
| 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 | |
| 456 | return 0 | |
| 457 | } | |
| 458 | ||
| 459 | agi_download() { | |
| 460 | # $1 = url, $2 = destination file | |
| 461 | echo " fetching $(basename "$1")..." | |
| 462 | curl -fL --retry 3 --progress-bar -o "$2" "$1" | |
| 463 | } | |
| 464 | ||
| 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() { | |
| 471 | local dep | |
| 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') | |
| 477 | return 0 | |
| 478 | } | |
| 479 | ||
| 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 | |
| 484 | rm -rf "$work" | |
| 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 | |
| 494 | } | |
| 495 | ||
| 496 | install_agi_deb() { | |
| 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 | |
| 501 | ||
| 502 | agi_download "$AGI_BASE_URL/v$ver/agi-$ver-linux.deb" "$deb" || return 1 | |
| 503 | ||
| 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." | |
| 511 | return 1 | |
| 512 | } | |
| 513 | fi | |
| 514 | ||
| 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 | |
| 519 | fi | |
| 520 | } | |
| 521 | ||
| 522 | install_agi_zip() { | |
| 523 | local ver="$1" tmp="$2" | |
| 524 | ||
| 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." | |
| 529 | return 1 | |
| 530 | } | |
| 531 | ||
| 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." | |
| 535 | return 1 | |
| 536 | fi | |
| 537 | ||
| 538 | rm -rf /opt/agi | |
| 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 | |
| 542 | agi_desktop_files | |
| 543 | } | |
| 544 | ||
| 545 | AGI_OK=0 | |
| 546 | if [ "$WANT_AGI" -eq 1 ]; then | |
| 547 | echo | |
| 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") | |
| 553 | done | |
| 554 | fi | |
| 555 | ||
| 556 | echo | |
| 557 | if [ -n "$AGI_INSTALLED" ] && [ "$AGI_INSTALLED" = "$AGI_WANTED" ]; then | |
| 558 | bold "AGI $AGI_INSTALLED is already installed in /opt/agi." | |
| 559 | AGI_OK=1 | |
| 560 | else | |
| 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 | |
| 567 | ||
| 568 | if [ "$PM" = apt ]; then | |
| 569 | if install_agi_deb "$AGI_WANTED" "$AGI_TMP"; then | |
| 570 | AGI_OK=1 | |
| 571 | else | |
| 572 | red " .deb install failed; falling back to the zip release." | |
| 573 | install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1 | |
| 574 | fi | |
| 575 | else | |
| 576 | install_agi_zip "$AGI_WANTED" "$AGI_TMP" && AGI_OK=1 | |
| 577 | fi | |
| 578 | ||
| 579 | rm -rf "$AGI_TMP" | |
| 580 | trap - EXIT | |
| 581 | [ "$AGI_OK" -eq 1 ] || red "AGI installation failed." | |
| 582 | fi | |
| 583 | fi | |
| 584 | ||
| 585 | # --------------------------------------------------------------- verify | |
| 586 | ||
| 587 | MISSING=() | |
| 588 | check_cmd() { | |
| 589 | if command -v "$1" >/dev/null 2>&1; then | |
| 590 | printf ' %-16s %s\n' "$1" "$(command -v "$1")" | |
| 591 | else | |
| 592 | printf ' %-16s %s\n' "$1" "MISSING" | |
| 593 | MISSING+=("$1") | |
| 594 | fi | |
| 595 | } | |
| 596 | ||
| 597 | if [ "$WANT_RSYNC" -eq 1 ]; then | |
| 598 | echo | |
| 599 | bold "Verifying the toolchain..." | |
| 600 | ||
| 601 | for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do | |
| 602 | check_cmd "$c" | |
| 603 | done | |
| 604 | ||
| 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")" | |
| 612 | else | |
| 613 | printf ' %-16s %s\n' "$c" "not installed (optional)" | |
| 614 | fi | |
| 615 | done | |
| 616 | ||
| 617 | echo | |
| 618 | bold "Verifying optional libraries..." | |
| 619 | check_header() { | |
| 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" | |
| 625 | else | |
| 626 | printf ' %-16s no\n' "$1" | |
| 627 | fi | |
| 628 | } | |
| 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 | |
| 636 | ||
| 637 | echo | |
| 638 | bold "Verifying the manpage generator..." | |
| 639 | MD_OK=0 | |
| 640 | for m in cmarkgfm commonmark; do | |
| 641 | if python3 -c "import $m" >/dev/null 2>&1; then | |
| 642 | echo " python3 module '$m' importable" | |
| 643 | MD_OK=1 | |
| 644 | break | |
| 645 | fi | |
| 646 | done | |
| 647 | if [ "$MD_OK" -eq 0 ]; then | |
| 648 | if [ "$MINIMAL" -eq 1 ]; then | |
| 649 | echo " skipped (--minimal); build with ./configure --disable-md2man" | |
| 650 | else | |
| 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" | |
| 654 | fi | |
| 655 | fi | |
| 656 | fi | |
| 657 | ||
| 658 | if [ "$WANT_AGI" -eq 1 ]; then | |
| 659 | echo | |
| 660 | bold "Verifying AGI..." | |
| 661 | if [ -x /opt/agi/agi ]; then | |
| 662 | printf ' %-16s %s\n' "agi" "/opt/agi/agi ($(agi_installed_version))" | |
| 663 | else | |
| 664 | printf ' %-16s %s\n' "agi" "MISSING" | |
| 665 | fi | |
| 666 | ||
| 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" | |
| 675 | else | |
| 676 | printf ' %-16s java %s (too old, AGI needs 19+)\n' "jre" "$JAVA_MAJOR" | |
| 677 | fi | |
| 678 | else | |
| 679 | printf ' %-16s %s\n' "jre" "MISSING" | |
| 680 | fi | |
| 681 | ||
| 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*}" | |
| 685 | else | |
| 686 | printf ' %-16s no\n' "${lib%%.so*}" | |
| 687 | fi | |
| 688 | done | |
| 689 | ||
| 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)" | |
| 693 | else | |
| 694 | printf ' %-16s %s\n' "adb" "not installed (no Android devices)" | |
| 695 | fi | |
| 696 | fi | |
| 697 | ||
| 698 | # ---------------------------------------------------------------- done | |
| 699 | ||
| 700 | echo | |
| 701 | if [ ${#MISSING[@]} -ne 0 ]; then | |
| 702 | red "Missing required tools: ${MISSING[*]}" | |
| 703 | exit 1 | |
| 704 | fi | |
| 705 | ||
| 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." | |
| 709 | fi | |
| 710 | ||
| 711 | if [ "$WANT_RSYNC" -eq 1 ]; then | |
| 712 | green "All required build dependencies are installed." | |
| 713 | cat <<'EOF' | |
| 714 | ||
| 715 | Build rsync with either build system: | |
| 716 | ||
| 717 | autoconf: ./configure && make | |
| 718 | CMake: cmake -B build -G Ninja && cmake --build build | |
| 719 | ||
| 720 | Run the test suite (needs the autoconf build's helper programs): | |
| 721 | ||
| 722 | make check | |
| 723 | EOF | |
| 724 | fi | |
| 725 | ||
| 726 | if [ "$WANT_AGI" -eq 1 ] && [ "$AGI_OK" -eq 1 ]; then | |
| 727 | green "AGI $AGI_WANTED is installed." | |
| 728 | if [ "$PM" = apt ]; then | |
| 729 | cat <<'EOF' | |
| 730 | ||
| 731 | Run it from the desktop menu ("Android GPU Inspector") or: | |
| 732 | ||
| 733 | /opt/agi/agi # GUI (--console to keep it in the terminal) | |
| 734 | /opt/agi/gapit # CLI | |
| 735 | ||
| 736 | Remove it with: sudo apt-get remove agi | |
| 737 | EOF | |
| 738 | else | |
| 739 | cat <<'EOF' | |
| 740 | ||
| 741 | Run it from the desktop menu ("Android GPU Inspector") or: | |
| 742 | ||
| 743 | agi # GUI, symlinked into /usr/local/bin | |
| 744 | /opt/agi/gapit # CLI | |
| 745 | ||
| 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 | |
| 749 | EOF | |
| 750 | fi | |
| 751 | echo "Tracing an Android device needs adb and USB debugging enabled on it." | |
| 752 | fi |