1#!/usr/bin/env python3 2 3# Copyright 2023 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7# Script to install everything needed to build chromium 8# including items requiring sudo privileges. 9# See https://chromium.googlesource.com/chromium/src/+/main/docs/linux/build_instructions.md 10 11import argparse 12import functools 13import os 14import re 15import shutil 16import subprocess 17import sys 18 19 20@functools.lru_cache(maxsize=1) 21def build_apt_package_list(): 22 print("Building apt package list.", file=sys.stderr) 23 output = subprocess.check_output(["apt-cache", "dumpavail"]).decode() 24 arch_map = {"i386": ":i386"} 25 package_regex = re.compile(r"^Package: (.+?)$.+?^Architecture: (.+?)$", 26 re.M | re.S) 27 return set(package + arch_map.get(arch, "") 28 for package, arch in re.findall(package_regex, output)) 29 30 31def package_exists(package_name: str) -> bool: 32 return package_name in build_apt_package_list() 33 34 35def parse_args(argv): 36 parser = argparse.ArgumentParser( 37 description="Install Chromium build dependencies.") 38 parser.add_argument("--syms", 39 action="store_true", 40 help="Enable installation of debugging symbols") 41 parser.add_argument( 42 "--no-syms", 43 action="store_false", 44 dest="syms", 45 help="Disable installation of debugging symbols", 46 ) 47 parser.add_argument( 48 "--lib32", 49 action="store_true", 50 help="Enable installation of 32-bit libraries, e.g. for V8 snapshot", 51 ) 52 parser.add_argument( 53 "--android", 54 action="store_true", 55 # Deprecated flag retained as functional for backward compatibility: 56 # Enable installation of android dependencies 57 help=argparse.SUPPRESS) 58 parser.add_argument( 59 "--no-android", 60 action="store_false", 61 dest="android", 62 # Deprecated flag retained as functional for backward compatibility: 63 # Enable installation of android dependencies 64 help=argparse.SUPPRESS) 65 parser.add_argument("--arm", 66 action="store_true", 67 help="Enable installation of arm cross toolchain") 68 parser.add_argument( 69 "--no-arm", 70 action="store_false", 71 dest="arm", 72 help="Disable installation of arm cross toolchain", 73 ) 74 parser.add_argument( 75 "--chromeos-fonts", 76 action="store_true", 77 help="Enable installation of Chrome OS fonts", 78 ) 79 parser.add_argument( 80 "--no-chromeos-fonts", 81 action="store_false", 82 dest="chromeos_fonts", 83 help="Disable installation of Chrome OS fonts", 84 ) 85 parser.add_argument( 86 "--nacl", 87 action="store_true", 88 help="Enable installation of prerequisites for building NaCl", 89 ) 90 parser.add_argument( 91 "--no-nacl", 92 action="store_false", 93 dest="nacl", 94 help="Disable installation of prerequisites for building NaCl", 95 ) 96 parser.add_argument( 97 "--backwards-compatible", 98 action="store_true", 99 help= 100 "Enable installation of packages that are no longer currently needed and" 101 + "have been removed from this script. Useful for bisection.", 102 ) 103 parser.add_argument( 104 "--no-backwards-compatible", 105 action="store_false", 106 dest="backwards_compatible", 107 help= 108 "Disable installation of packages that are no longer currently needed and" 109 + "have been removed from this script.", 110 ) 111 parser.add_argument("--no-prompt", 112 action="store_true", 113 help="Automatic yes to prompts") 114 parser.add_argument( 115 "--quick-check", 116 action="store_true", 117 help="Quickly try to determine if dependencies are installed", 118 ) 119 parser.add_argument( 120 "--unsupported", 121 action="store_true", 122 help="Attempt installation even on unsupported systems", 123 ) 124 125 options = parser.parse_args(argv) 126 127 if options.arm or options.android: 128 options.lib32 = True 129 130 return options 131 132 133def check_lsb_release(): 134 if not shutil.which("lsb_release"): 135 print("ERROR: lsb_release not found in $PATH", file=sys.stderr) 136 print("try: sudo apt-get install lsb-release", file=sys.stderr) 137 sys.exit(1) 138 139 140@functools.lru_cache(maxsize=1) 141def distro_codename(): 142 return subprocess.check_output(["lsb_release", "--codename", 143 "--short"]).decode().strip() 144 145 146def check_distro(options): 147 if options.unsupported or options.quick_check: 148 return 149 150 distro_id = subprocess.check_output(["lsb_release", "--id", 151 "--short"]).decode().strip() 152 153 supported_codenames = ["focal", "jammy", "noble"] 154 supported_ids = ["Debian"] 155 156 if (distro_codename() not in supported_codenames 157 and distro_id not in supported_ids): 158 print( 159 "WARNING: The following distributions are supported,", 160 "but distributions not in the list below can also try to install", 161 "dependencies by passing the `--unsupported` parameter.", 162 "EoS refers to end of standard support and does not include", 163 "extended security support.", 164 "\tUbuntu 20.04 LTS (focal with EoS April 2025)", 165 "\tUbuntu 22.04 LTS (jammy with EoS June 2027)", 166 "\tUbuntu 24.04 LTS (noble with EoS June 2029)", 167 "\tDebian 11 (bullseye) or later", 168 sep="\n", 169 file=sys.stderr, 170 ) 171 sys.exit(1) 172 173 174def check_architecture(): 175 architecture = subprocess.check_output(["uname", "-m"]).decode().strip() 176 if architecture not in ["i686", "x86_64", 'aarch64']: 177 print("Only x86 and ARM64 architectures are currently supported", 178 file=sys.stderr) 179 sys.exit(1) 180 181 182def check_root(): 183 if os.geteuid() != 0: 184 print("Running as non-root user.", file=sys.stderr) 185 print("You might have to enter your password one or more times for 'sudo'.", 186 file=sys.stderr) 187 print(file=sys.stderr) 188 189 190def apt_update(options): 191 if options.lib32 or options.nacl: 192 subprocess.check_call(["sudo", "dpkg", "--add-architecture", "i386"]) 193 subprocess.check_call(["sudo", "apt-get", "update"]) 194 195 196# Packages needed for development 197def dev_list(): 198 packages = [ 199 "binutils", 200 "bison", 201 "bzip2", 202 "cdbs", 203 "curl", 204 "dbus-x11", 205 "devscripts", 206 "dpkg-dev", 207 "elfutils", 208 "fakeroot", 209 "flex", 210 "git-core", 211 "gperf", 212 "libasound2-dev", 213 "libatspi2.0-dev", 214 "libbrlapi-dev", 215 "libbz2-dev", 216 "libc6-dev", 217 "libcairo2-dev", 218 "libcap-dev", 219 "libcups2-dev", 220 "libcurl4-gnutls-dev", 221 "libdrm-dev", 222 "libelf-dev", 223 "libevdev-dev", 224 "libffi-dev", 225 "libfuse2", 226 "libgbm-dev", 227 "libglib2.0-dev", 228 "libglu1-mesa-dev", 229 "libgtk-3-dev", 230 "libkrb5-dev", 231 "libnspr4-dev", 232 "libnss3-dev", 233 "libpam0g-dev", 234 "libpci-dev", 235 "libpulse-dev", 236 "libsctp-dev", 237 "libspeechd-dev", 238 "libsqlite3-dev", 239 "libssl-dev", 240 "libsystemd-dev", 241 "libudev-dev", 242 "libudev1", 243 "libva-dev", 244 "libwww-perl", 245 "libxshmfence-dev", 246 "libxslt1-dev", 247 "libxss-dev", 248 "libxt-dev", 249 "libxtst-dev", 250 "lighttpd", 251 "locales", 252 "openbox", 253 "p7zip", 254 "patch", 255 "perl", 256 "pkgconf", 257 "rpm", 258 "ruby", 259 "uuid-dev", 260 "wdiff", 261 "x11-utils", 262 "xcompmgr", 263 "xz-utils", 264 "zip", 265 ] 266 267 # Packages needed for chromeos only 268 packages += [ 269 "libbluetooth-dev", 270 "libxkbcommon-dev", 271 "mesa-common-dev", 272 "zstd", 273 ] 274 275 if package_exists("realpath"): 276 packages.append("realpath") 277 278 if package_exists("libjpeg-dev"): 279 packages.append("libjpeg-dev") 280 else: 281 packages.append("libjpeg62-dev") 282 283 if package_exists("libbrlapi0.8"): 284 packages.append("libbrlapi0.8") 285 elif package_exists("libbrlapi0.7"): 286 packages.append("libbrlapi0.7") 287 elif package_exists("libbrlapi0.6"): 288 packages.append("libbrlapi0.6") 289 else: 290 packages.append("libbrlapi0.5") 291 292 if package_exists("libav-tools"): 293 packages.append("libav-tools") 294 295 if package_exists("libvulkan-dev"): 296 packages.append("libvulkan-dev") 297 298 if package_exists("libinput-dev"): 299 packages.append("libinput-dev") 300 301 # So accessibility APIs work, needed for AX fuzzer 302 if package_exists("at-spi2-core"): 303 packages.append("at-spi2-core") 304 305 # Cross-toolchain strip is needed for building the sysroots. 306 if package_exists("binutils-arm-linux-gnueabihf"): 307 packages.append("binutils-arm-linux-gnueabihf") 308 if package_exists("binutils-aarch64-linux-gnu"): 309 packages.append("binutils-aarch64-linux-gnu") 310 if package_exists("binutils-mipsel-linux-gnu"): 311 packages.append("binutils-mipsel-linux-gnu") 312 if package_exists("binutils-mips64el-linux-gnuabi64"): 313 packages.append("binutils-mips64el-linux-gnuabi64") 314 315 # 64-bit systems need a minimum set of 32-bit compat packages for the 316 # pre-built NaCl binaries. 317 if "ELF 64-bit" in subprocess.check_output(["file", "-L", 318 "/sbin/init"]).decode(): 319 # ARM64 may not support these. 320 if package_exists("libc6-i386"): 321 packages.append("libc6-i386") 322 if package_exists("lib32stdc++6"): 323 packages.append("lib32stdc++6") 324 325 # lib32gcc-s1 used to be called lib32gcc1 in older distros. 326 if package_exists("lib32gcc-s1"): 327 packages.append("lib32gcc-s1") 328 elif package_exists("lib32gcc1"): 329 packages.append("lib32gcc1") 330 331 return packages 332 333 334# List of required run-time libraries 335def lib_list(): 336 packages = [ 337 "libatk1.0-0", 338 "libatspi2.0-0", 339 "libc6", 340 "libcairo2", 341 "libcap2", 342 "libcgi-session-perl", 343 "libcups2", 344 "libdrm2", 345 "libegl1", 346 "libevdev2", 347 "libexpat1", 348 "libfontconfig1", 349 "libfreetype6", 350 "libgbm1", 351 "libglib2.0-0", 352 "libgl1", 353 "libgtk-3-0", 354 "libpam0g", 355 "libpango-1.0-0", 356 "libpangocairo-1.0-0", 357 "libpci3", 358 "libpcre3", 359 "libpixman-1-0", 360 "libspeechd2", 361 "libstdc++6", 362 "libsqlite3-0", 363 "libuuid1", 364 "libwayland-egl1", 365 "libwayland-egl1-mesa", 366 "libx11-6", 367 "libx11-xcb1", 368 "libxau6", 369 "libxcb1", 370 "libxcomposite1", 371 "libxcursor1", 372 "libxdamage1", 373 "libxdmcp6", 374 "libxext6", 375 "libxfixes3", 376 "libxi6", 377 "libxinerama1", 378 "libxrandr2", 379 "libxrender1", 380 "libxtst6", 381 "x11-utils", 382 "x11-xserver-utils", 383 "xserver-xorg-core", 384 "xserver-xorg-video-dummy", 385 "xvfb", 386 "zlib1g", 387 ] 388 389 # Run-time libraries required by chromeos only 390 packages += [ 391 "libpulse0", 392 "libbz2-1.0", 393 ] 394 395 # May not exist (e.g. ARM64) 396 if package_exists("lib32z1"): 397 packages.append("lib32z1") 398 399 if package_exists("libffi8"): 400 packages.append("libffi8") 401 elif package_exists("libffi7"): 402 packages.append("libffi7") 403 elif package_exists("libffi6"): 404 packages.append("libffi6") 405 406 if package_exists("libpng16-16t64"): 407 packages.append("libpng16-16t64") 408 elif package_exists("libpng16-16"): 409 packages.append("libpng16-16") 410 else: 411 packages.append("libpng12-0") 412 413 if package_exists("libnspr4"): 414 packages.extend(["libnspr4", "libnss3"]) 415 else: 416 packages.extend(["libnspr4-0d", "libnss3-1d"]) 417 418 if package_exists("appmenu-gtk"): 419 packages.append("appmenu-gtk") 420 if package_exists("libgnome-keyring0"): 421 packages.append("libgnome-keyring0") 422 if package_exists("libgnome-keyring-dev"): 423 packages.append("libgnome-keyring-dev") 424 if package_exists("libvulkan1"): 425 packages.append("libvulkan1") 426 if package_exists("libinput10"): 427 packages.append("libinput10") 428 429 if package_exists("libncurses6"): 430 packages.append("libncurses6") 431 else: 432 packages.append("libncurses5") 433 434 if package_exists("libasound2t64"): 435 packages.append("libasound2t64") 436 else: 437 packages.append("libasound2") 438 439 return packages 440 441 442def lib32_list(options): 443 if not options.lib32: 444 print("Skipping 32-bit libraries.", file=sys.stderr) 445 return [] 446 print("Including 32-bit libraries.", file=sys.stderr) 447 448 packages = [ 449 # 32-bit libraries needed for a 32-bit build 450 # includes some 32-bit libraries required by the Android SDK 451 # See https://developer.android.com/sdk/installing/index.html?pkg=tools 452 "libasound2:i386", 453 "libatk-bridge2.0-0:i386", 454 "libatk1.0-0:i386", 455 "libatspi2.0-0:i386", 456 "libdbus-1-3:i386", 457 "libegl1:i386", 458 "libgl1:i386", 459 "libglib2.0-0:i386", 460 "libnss3:i386", 461 "libpango-1.0-0:i386", 462 "libpangocairo-1.0-0:i386", 463 "libstdc++6:i386", 464 "libwayland-egl1:i386", 465 "libx11-xcb1:i386", 466 "libxcomposite1:i386", 467 "libxdamage1:i386", 468 "libxkbcommon0:i386", 469 "libxrandr2:i386", 470 "libxtst6:i386", 471 "zlib1g:i386", 472 # 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf 473 "linux-libc-dev:i386", 474 "libexpat1:i386", 475 "libpci3:i386", 476 ] 477 478 # When cross building for arm/Android on 64-bit systems the host binaries 479 # that are part of v8 need to be compiled with -m32 which means 480 # that basic multilib support is needed. 481 if "ELF 64-bit" in subprocess.check_output(["file", "-L", 482 "/sbin/init"]).decode(): 483 # gcc-multilib conflicts with the arm cross compiler but 484 # g++-X.Y-multilib gives us the 32-bit support that we need. Find out the 485 # appropriate value of X and Y by seeing what version the current 486 # distribution's g++-multilib package depends on. 487 lines = subprocess.check_output( 488 ["apt-cache", "depends", "g++-multilib", "--important"]).decode() 489 pattern = re.compile(r"g\+\+-[0-9.]+-multilib") 490 packages += re.findall(pattern, lines) 491 492 if package_exists("libncurses6:i386"): 493 packages.append("libncurses6:i386") 494 else: 495 packages.append("libncurses5:i386") 496 497 return packages 498 499 500# Packages that have been removed from this script. Regardless of configuration 501# or options passed to this script, whenever a package is removed, it should be 502# added here. 503def backwards_compatible_list(options): 504 if not options.backwards_compatible: 505 print("Skipping backwards compatible packages.", file=sys.stderr) 506 return [] 507 print("Including backwards compatible packages.", file=sys.stderr) 508 509 packages = [ 510 "7za", 511 "fonts-indic", 512 "fonts-ipafont", 513 "fonts-stix", 514 "fonts-thai-tlwg", 515 "fonts-tlwg-garuda", 516 "g++", 517 "g++-4.8-multilib-arm-linux-gnueabihf", 518 "gcc-4.8-multilib-arm-linux-gnueabihf", 519 "g++-9-multilib-arm-linux-gnueabihf", 520 "gcc-9-multilib-arm-linux-gnueabihf", 521 "gcc-arm-linux-gnueabihf", 522 "g++-10-multilib-arm-linux-gnueabihf", 523 "gcc-10-multilib-arm-linux-gnueabihf", 524 "g++-10-arm-linux-gnueabihf", 525 "gcc-10-arm-linux-gnueabihf", 526 "git-svn", 527 "language-pack-da", 528 "language-pack-fr", 529 "language-pack-he", 530 "language-pack-zh-hant", 531 "libappindicator-dev", 532 "libappindicator1", 533 "libappindicator3-1", 534 "libappindicator3-dev", 535 "libdconf-dev", 536 "libdconf1", 537 "libdconf1:i386", 538 "libexif-dev", 539 "libexif12", 540 "libexif12:i386", 541 "libgbm-dev", 542 "libgbm-dev-lts-trusty", 543 "libgbm-dev-lts-xenial", 544 "libgconf-2-4:i386", 545 "libgconf2-dev", 546 "libgl1-mesa-dev", 547 "libgl1-mesa-dev-lts-trusty", 548 "libgl1-mesa-dev-lts-xenial", 549 "libgl1-mesa-glx:i386", 550 "libgl1-mesa-glx-lts-trusty:i386", 551 "libgl1-mesa-glx-lts-xenial:i386", 552 "libgles2-mesa-dev", 553 "libgles2-mesa-dev-lts-trusty", 554 "libgles2-mesa-dev-lts-xenial", 555 "libgtk-3-0:i386", 556 "libgtk2.0-0", 557 "libgtk2.0-0:i386", 558 "libgtk2.0-dev", 559 "mesa-common-dev", 560 "mesa-common-dev-lts-trusty", 561 "mesa-common-dev-lts-xenial", 562 "msttcorefonts", 563 "python-dev", 564 "python-setuptools", 565 "snapcraft", 566 "ttf-dejavu-core", 567 "ttf-indic-fonts", 568 "ttf-kochi-gothic", 569 "ttf-kochi-mincho", 570 "ttf-mscorefonts-installer", 571 "xfonts-mathml", 572 ] 573 574 if package_exists("python-is-python2"): 575 packages.extend(["python-is-python2", "python2-dev"]) 576 else: 577 packages.append("python") 578 579 if package_exists("python-crypto"): 580 packages.append("python-crypto") 581 582 if package_exists("python-numpy"): 583 packages.append("python-numpy") 584 585 if package_exists("python-openssl"): 586 packages.append("python-openssl") 587 588 if package_exists("python-psutil"): 589 packages.append("python-psutil") 590 591 if package_exists("python-yaml"): 592 packages.append("python-yaml") 593 594 if package_exists("apache2.2-bin"): 595 packages.append("apache2.2-bin") 596 else: 597 packages.append("apache2-bin") 598 599 php_versions = [ 600 ("php8.1-cgi", "libapache2-mod-php8.1"), 601 ("php8.0-cgi", "libapache2-mod-php8.0"), 602 ("php7.4-cgi", "libapache2-mod-php7.4"), 603 ("php7.3-cgi", "libapache2-mod-php7.3"), 604 ("php7.2-cgi", "libapache2-mod-php7.2"), 605 ("php7.1-cgi", "libapache2-mod-php7.1"), 606 ("php7.0-cgi", "libapache2-mod-php7.0"), 607 ("php5-cgi", "libapache2-mod-php5"), 608 ] 609 610 for php_cgi, mod_php in php_versions: 611 if package_exists(php_cgi): 612 packages.extend([php_cgi, mod_php]) 613 break 614 615 return [package for package in packages if package_exists(package)] 616 617 618def arm_list(options): 619 if not options.arm: 620 print("Skipping ARM cross toolchain.", file=sys.stderr) 621 return [] 622 print("Including ARM cross toolchain.", file=sys.stderr) 623 624 # arm cross toolchain packages needed to build chrome on armhf 625 packages = [ 626 "g++-arm-linux-gnueabihf", 627 "gcc-arm-linux-gnueabihf", 628 "libc6-dev-armhf-cross", 629 "linux-libc-dev-armhf-cross", 630 ] 631 632 # Work around an Ubuntu dependency issue. 633 # TODO(https://crbug.com/40549424): Remove this when support for Focal 634 # and Jammy are dropped. 635 if distro_codename() == "focal": 636 packages.extend([ 637 "g++-10-multilib-arm-linux-gnueabihf", 638 "gcc-10-multilib-arm-linux-gnueabihf", 639 ]) 640 elif distro_codename() == "jammy": 641 packages.extend([ 642 "g++-11-arm-linux-gnueabihf", 643 "gcc-11-arm-linux-gnueabihf", 644 ]) 645 646 return packages 647 648 649def nacl_list(options): 650 if not options.nacl: 651 print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies.", 652 file=sys.stderr) 653 return [] 654 655 packages = [ 656 "g++-mingw-w64-i686", 657 "lib32z1-dev", 658 "libasound2:i386", 659 "libcap2:i386", 660 "libelf-dev:i386", 661 "libfontconfig1:i386", 662 "libglib2.0-0:i386", 663 "libgpm2:i386", 664 "libncurses5:i386", 665 "libnss3:i386", 666 "libpango-1.0-0:i386", 667 "libssl-dev:i386", 668 "libtinfo-dev", 669 "libtinfo-dev:i386", 670 "libtool", 671 "libudev1:i386", 672 "libuuid1:i386", 673 "libxcomposite1:i386", 674 "libxcursor1:i386", 675 "libxdamage1:i386", 676 "libxi6:i386", 677 "libxrandr2:i386", 678 "libxss1:i386", 679 "libxtst6:i386", 680 "texinfo", 681 "xvfb", 682 # Packages to build NaCl, its toolchains, and its ports. 683 "ant", 684 "autoconf", 685 "bison", 686 "cmake", 687 "gawk", 688 "intltool", 689 "libtinfo5", 690 "xutils-dev", 691 "xsltproc", 692 ] 693 694 for package in packages: 695 if not package_exists(package): 696 print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies because %s " 697 "is not available" % package, 698 file=sys.stderr) 699 return [] 700 701 print("Including NaCl, NaCl toolchain, NaCl ports dependencies.", 702 file=sys.stderr) 703 704 # Prefer lib32ncurses5-dev to match libncurses5:i386 if it exists. 705 # In some Ubuntu releases, lib32ncurses5-dev is a transition package to 706 # lib32ncurses-dev, so use that as a fallback. 707 if package_exists("lib32ncurses5-dev"): 708 packages.append("lib32ncurses5-dev") 709 else: 710 packages.append("lib32ncurses-dev") 711 712 return packages 713 714 715# Packages suffixed with t64 are "transition packages" and should be preferred. 716def maybe_append_t64(package): 717 name = package.split(":") 718 name[0] += "t64" 719 renamed = ":".join(name) 720 return renamed if package_exists(renamed) else package 721 722 723# Debian is in the process of transitioning to automatic debug packages, which 724# have the -dbgsym suffix (https://wiki.debian.org/AutomaticDebugPackages). 725# Untransitioned packages have the -dbg suffix. And on some systems, neither 726# will be available, so exclude the ones that are missing. 727def dbg_package_name(package): 728 package = maybe_append_t64(package) 729 if package_exists(package + "-dbgsym"): 730 return [package + "-dbgsym"] 731 if package_exists(package + "-dbg"): 732 return [package + "-dbg"] 733 return [] 734 735 736def dbg_list(options): 737 if not options.syms: 738 print("Skipping debugging symbols.", file=sys.stderr) 739 return [] 740 print("Including debugging symbols.", file=sys.stderr) 741 742 packages = [ 743 dbg_package for package in lib_list() 744 for dbg_package in dbg_package_name(package) 745 ] 746 747 # Debugging symbols packages not following common naming scheme 748 if not dbg_package_name("libstdc++6"): 749 for version in ["8", "7", "6", "5", "4.9", "4.8", "4.7", "4.6"]: 750 if package_exists("libstdc++6-%s-dbg" % version): 751 packages.append("libstdc++6-%s-dbg" % version) 752 break 753 754 if not dbg_package_name("libatk1.0-0"): 755 packages.extend(dbg_package_name("libatk1.0")) 756 757 if not dbg_package_name("libpango-1.0-0"): 758 packages.extend(dbg_package_name("libpango1.0-dev")) 759 760 return packages 761 762 763def package_list(options): 764 packages = (dev_list() + lib_list() + dbg_list(options) + 765 lib32_list(options) + arm_list(options) + nacl_list(options) + 766 backwards_compatible_list(options)) 767 packages = [maybe_append_t64(package) for package in set(packages)] 768 769 # Sort all the :i386 packages to the front, to avoid confusing dpkg-query 770 # (https://crbug.com/446172). 771 return sorted(packages, key=lambda x: (not x.endswith(":i386"), x)) 772 773 774def missing_packages(packages): 775 try: 776 subprocess.run( 777 ["dpkg-query", "-W", "-f", " "] + packages, 778 check=True, 779 capture_output=True, 780 ) 781 return [] 782 except subprocess.CalledProcessError as e: 783 return [ 784 line.split(" ")[-1] for line in e.stderr.decode().strip().splitlines() 785 ] 786 787 788def package_is_installable(package): 789 result = subprocess.run(["apt-cache", "show", package], capture_output=True) 790 return result.returncode == 0 791 792 793def quick_check(options): 794 if not options.quick_check: 795 return 796 797 missing = missing_packages(package_list(options)) 798 if not missing: 799 sys.exit(0) 800 801 not_installed = [] 802 unknown = [] 803 for p in missing: 804 if package_is_installable(p): 805 not_installed.append(p) 806 else: 807 unknown.append(p) 808 809 if not_installed: 810 print("WARNING: The following packages are not installed:", file=sys.stderr) 811 print(" ".join(not_installed), file=sys.stderr) 812 813 if unknown: 814 print("WARNING: The following packages are unknown to your system", 815 file=sys.stderr) 816 print("(maybe missing a repo or need to 'sudo apt-get update'):", 817 file=sys.stderr) 818 print(" ".join(unknown), file=sys.stderr) 819 820 sys.exit(1) 821 822 823def find_missing_packages(options): 824 print("Finding missing packages...", file=sys.stderr) 825 826 packages = package_list(options) 827 packages_str = " ".join(packages) 828 print("Packages required: " + packages_str, file=sys.stderr) 829 830 query_cmd = ["apt-get", "--just-print", "install"] + packages 831 env = os.environ.copy() 832 env["LANGUAGE"] = "en" 833 env["LANG"] = "C" 834 cmd_output = subprocess.check_output(query_cmd, env=env).decode() 835 lines = cmd_output.splitlines() 836 837 install = [] 838 for pattern in ( 839 "The following NEW packages will be installed:", 840 "The following packages will be upgraded:", 841 ): 842 if pattern in lines: 843 for line in lines[lines.index(pattern) + 1:]: 844 if not line.startswith(" "): 845 break 846 install += line.strip().split(" ") 847 return install 848 849 850def install_packages(options): 851 try: 852 packages = find_missing_packages(options) 853 if packages: 854 quiet = ["-qq", "--assume-yes"] if options.no_prompt else [] 855 subprocess.check_call(["sudo", "apt-get", "install"] + quiet + packages) 856 print(file=sys.stderr) 857 else: 858 print("No missing packages, and the packages are up to date.", 859 file=sys.stderr) 860 861 except subprocess.CalledProcessError as e: 862 # An apt-get exit status of 100 indicates that a real error has occurred. 863 print("`apt-get --just-print install ...` failed", file=sys.stderr) 864 print("It produced the following output:", file=sys.stderr) 865 print(file=sys.stderr) 866 print("You will have to install the above packages yourself.", 867 file=sys.stderr) 868 print(file=sys.stderr) 869 sys.exit(100) 870 871 872# Install the Chrome OS default fonts. This must go after running 873# apt-get, since install-chromeos-fonts depends on curl. 874def install_chromeos_fonts(options): 875 if not options.chromeos_fonts: 876 print("Skipping installation of Chrome OS fonts.", file=sys.stderr) 877 return 878 print("Installing Chrome OS fonts.", file=sys.stderr) 879 880 dir = os.path.abspath(os.path.dirname(__file__)) 881 882 try: 883 subprocess.check_call( 884 ["sudo", 885 os.path.join(dir, "linux", "install-chromeos-fonts.py")]) 886 except subprocess.CalledProcessError: 887 print("ERROR: The installation of the Chrome OS default fonts failed.", 888 file=sys.stderr) 889 if (subprocess.check_output( 890 ["stat", "-f", "-c", "%T", dir], ).decode().startswith("nfs")): 891 print( 892 "The reason is that your repo is installed on a remote file system.", 893 file=sys.stderr) 894 else: 895 print( 896 "This is expected if your repo is installed on a remote file system.", 897 file=sys.stderr) 898 899 print("It is recommended to install your repo on a local file system.", 900 file=sys.stderr) 901 print("You can skip the installation of the Chrome OS default fonts with", 902 file=sys.stderr) 903 print("the command line option: --no-chromeos-fonts.", file=sys.stderr) 904 sys.exit(1) 905 906 907def install_locales(): 908 print("Installing locales.", file=sys.stderr) 909 CHROMIUM_LOCALES = [ 910 "da_DK.UTF-8", "en_US.UTF-8", "fr_FR.UTF-8", "he_IL.UTF-8", "zh_TW.UTF-8" 911 ] 912 LOCALE_GEN = "/etc/locale.gen" 913 if os.path.exists(LOCALE_GEN): 914 old_locale_gen = open(LOCALE_GEN).read() 915 for locale in CHROMIUM_LOCALES: 916 subprocess.check_call( 917 ["sudo", "sed", "-i", 918 "s/^# %s/%s/" % (locale, locale), LOCALE_GEN]) 919 920 # Regenerating locales can take a while, so only do it if we need to. 921 locale_gen = open(LOCALE_GEN).read() 922 if locale_gen != old_locale_gen: 923 subprocess.check_call(["sudo", "locale-gen"]) 924 else: 925 print("Locales already up-to-date.", file=sys.stderr) 926 else: 927 for locale in CHROMIUM_LOCALES: 928 subprocess.check_call(["sudo", "locale-gen", locale]) 929 930 931def main(): 932 options = parse_args(sys.argv[1:]) 933 check_lsb_release() 934 check_distro(options) 935 check_architecture() 936 quick_check(options) 937 check_root() 938 apt_update(options) 939 install_packages(options) 940 install_chromeos_fonts(options) 941 install_locales() 942 return 0 943 944 945if __name__ == "__main__": 946 sys.exit(main()) 947