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