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 # Run-time packages required by interactive_ui_tests on mutter 440 if package_exists("libgraphene-1.0-0"): 441 packages.append("libgraphene-1.0-0") 442 if package_exists("mutter-common"): 443 packages.append("mutter-common") 444 445 return packages 446 447 448def lib32_list(options): 449 if not options.lib32: 450 print("Skipping 32-bit libraries.", file=sys.stderr) 451 return [] 452 print("Including 32-bit libraries.", file=sys.stderr) 453 454 packages = [ 455 # 32-bit libraries needed for a 32-bit build 456 # includes some 32-bit libraries required by the Android SDK 457 # See https://developer.android.com/sdk/installing/index.html?pkg=tools 458 "libasound2:i386", 459 "libatk-bridge2.0-0:i386", 460 "libatk1.0-0:i386", 461 "libatspi2.0-0:i386", 462 "libdbus-1-3:i386", 463 "libegl1:i386", 464 "libgl1:i386", 465 "libglib2.0-0:i386", 466 "libnss3:i386", 467 "libpango-1.0-0:i386", 468 "libpangocairo-1.0-0:i386", 469 "libstdc++6:i386", 470 "libwayland-egl1:i386", 471 "libx11-xcb1:i386", 472 "libxcomposite1:i386", 473 "libxdamage1:i386", 474 "libxkbcommon0:i386", 475 "libxrandr2:i386", 476 "libxtst6:i386", 477 "zlib1g:i386", 478 # 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf 479 "linux-libc-dev:i386", 480 "libexpat1:i386", 481 "libpci3:i386", 482 ] 483 484 # When cross building for arm/Android on 64-bit systems the host binaries 485 # that are part of v8 need to be compiled with -m32 which means 486 # that basic multilib support is needed. 487 if "ELF 64-bit" in subprocess.check_output(["file", "-L", 488 "/sbin/init"]).decode(): 489 # gcc-multilib conflicts with the arm cross compiler but 490 # g++-X.Y-multilib gives us the 32-bit support that we need. Find out the 491 # appropriate value of X and Y by seeing what version the current 492 # distribution's g++-multilib package depends on. 493 lines = subprocess.check_output( 494 ["apt-cache", "depends", "g++-multilib", "--important"]).decode() 495 pattern = re.compile(r"g\+\+-[0-9.]+-multilib") 496 packages += re.findall(pattern, lines) 497 498 if package_exists("libncurses6:i386"): 499 packages.append("libncurses6:i386") 500 else: 501 packages.append("libncurses5:i386") 502 503 return packages 504 505 506# Packages that have been removed from this script. Regardless of configuration 507# or options passed to this script, whenever a package is removed, it should be 508# added here. 509def backwards_compatible_list(options): 510 if not options.backwards_compatible: 511 print("Skipping backwards compatible packages.", file=sys.stderr) 512 return [] 513 print("Including backwards compatible packages.", file=sys.stderr) 514 515 packages = [ 516 "7za", 517 "fonts-indic", 518 "fonts-ipafont", 519 "fonts-stix", 520 "fonts-thai-tlwg", 521 "fonts-tlwg-garuda", 522 "g++", 523 "g++-4.8-multilib-arm-linux-gnueabihf", 524 "gcc-4.8-multilib-arm-linux-gnueabihf", 525 "g++-9-multilib-arm-linux-gnueabihf", 526 "gcc-9-multilib-arm-linux-gnueabihf", 527 "gcc-arm-linux-gnueabihf", 528 "g++-10-multilib-arm-linux-gnueabihf", 529 "gcc-10-multilib-arm-linux-gnueabihf", 530 "g++-10-arm-linux-gnueabihf", 531 "gcc-10-arm-linux-gnueabihf", 532 "git-svn", 533 "language-pack-da", 534 "language-pack-fr", 535 "language-pack-he", 536 "language-pack-zh-hant", 537 "libappindicator-dev", 538 "libappindicator1", 539 "libappindicator3-1", 540 "libappindicator3-dev", 541 "libdconf-dev", 542 "libdconf1", 543 "libdconf1:i386", 544 "libexif-dev", 545 "libexif12", 546 "libexif12:i386", 547 "libgbm-dev", 548 "libgbm-dev-lts-trusty", 549 "libgbm-dev-lts-xenial", 550 "libgconf-2-4:i386", 551 "libgconf2-dev", 552 "libgl1-mesa-dev", 553 "libgl1-mesa-dev-lts-trusty", 554 "libgl1-mesa-dev-lts-xenial", 555 "libgl1-mesa-glx:i386", 556 "libgl1-mesa-glx-lts-trusty:i386", 557 "libgl1-mesa-glx-lts-xenial:i386", 558 "libgles2-mesa-dev", 559 "libgles2-mesa-dev-lts-trusty", 560 "libgles2-mesa-dev-lts-xenial", 561 "libgtk-3-0:i386", 562 "libgtk2.0-0", 563 "libgtk2.0-0:i386", 564 "libgtk2.0-dev", 565 "mesa-common-dev", 566 "mesa-common-dev-lts-trusty", 567 "mesa-common-dev-lts-xenial", 568 "msttcorefonts", 569 "python-dev", 570 "python-setuptools", 571 "snapcraft", 572 "ttf-dejavu-core", 573 "ttf-indic-fonts", 574 "ttf-kochi-gothic", 575 "ttf-kochi-mincho", 576 "ttf-mscorefonts-installer", 577 "xfonts-mathml", 578 ] 579 580 if package_exists("python-is-python2"): 581 packages.extend(["python-is-python2", "python2-dev"]) 582 else: 583 packages.append("python") 584 585 if package_exists("python-crypto"): 586 packages.append("python-crypto") 587 588 if package_exists("python-numpy"): 589 packages.append("python-numpy") 590 591 if package_exists("python-openssl"): 592 packages.append("python-openssl") 593 594 if package_exists("python-psutil"): 595 packages.append("python-psutil") 596 597 if package_exists("python-yaml"): 598 packages.append("python-yaml") 599 600 if package_exists("apache2.2-bin"): 601 packages.append("apache2.2-bin") 602 else: 603 packages.append("apache2-bin") 604 605 php_versions = [ 606 ("php8.1-cgi", "libapache2-mod-php8.1"), 607 ("php8.0-cgi", "libapache2-mod-php8.0"), 608 ("php7.4-cgi", "libapache2-mod-php7.4"), 609 ("php7.3-cgi", "libapache2-mod-php7.3"), 610 ("php7.2-cgi", "libapache2-mod-php7.2"), 611 ("php7.1-cgi", "libapache2-mod-php7.1"), 612 ("php7.0-cgi", "libapache2-mod-php7.0"), 613 ("php5-cgi", "libapache2-mod-php5"), 614 ] 615 616 for php_cgi, mod_php in php_versions: 617 if package_exists(php_cgi): 618 packages.extend([php_cgi, mod_php]) 619 break 620 621 return [package for package in packages if package_exists(package)] 622 623 624def arm_list(options): 625 if not options.arm: 626 print("Skipping ARM cross toolchain.", file=sys.stderr) 627 return [] 628 print("Including ARM cross toolchain.", file=sys.stderr) 629 630 # arm cross toolchain packages needed to build chrome on armhf 631 packages = [ 632 "g++-arm-linux-gnueabihf", 633 "gcc-arm-linux-gnueabihf", 634 "libc6-dev-armhf-cross", 635 "linux-libc-dev-armhf-cross", 636 ] 637 638 # Work around an Ubuntu dependency issue. 639 # TODO(https://crbug.com/40549424): Remove this when support for Focal 640 # and Jammy are dropped. 641 if distro_codename() == "focal": 642 packages.extend([ 643 "g++-10-multilib-arm-linux-gnueabihf", 644 "gcc-10-multilib-arm-linux-gnueabihf", 645 ]) 646 elif distro_codename() == "jammy": 647 packages.extend([ 648 "g++-11-arm-linux-gnueabihf", 649 "gcc-11-arm-linux-gnueabihf", 650 ]) 651 652 return packages 653 654 655def nacl_list(options): 656 if not options.nacl: 657 print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies.", 658 file=sys.stderr) 659 return [] 660 661 packages = [ 662 "g++-mingw-w64-i686", 663 "lib32z1-dev", 664 "libasound2:i386", 665 "libcap2:i386", 666 "libelf-dev:i386", 667 "libfontconfig1:i386", 668 "libglib2.0-0:i386", 669 "libgpm2:i386", 670 "libncurses5:i386", 671 "libnss3:i386", 672 "libpango-1.0-0:i386", 673 "libssl-dev:i386", 674 "libtinfo-dev", 675 "libtinfo-dev:i386", 676 "libtool", 677 "libudev1:i386", 678 "libuuid1:i386", 679 "libxcomposite1:i386", 680 "libxcursor1:i386", 681 "libxdamage1:i386", 682 "libxi6:i386", 683 "libxrandr2:i386", 684 "libxss1:i386", 685 "libxtst6:i386", 686 "texinfo", 687 "xvfb", 688 # Packages to build NaCl, its toolchains, and its ports. 689 "ant", 690 "autoconf", 691 "bison", 692 "cmake", 693 "gawk", 694 "intltool", 695 "libtinfo5", 696 "xutils-dev", 697 "xsltproc", 698 ] 699 700 for package in packages: 701 if not package_exists(package): 702 print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies because %s " 703 "is not available" % package, 704 file=sys.stderr) 705 return [] 706 707 print("Including NaCl, NaCl toolchain, NaCl ports dependencies.", 708 file=sys.stderr) 709 710 # Prefer lib32ncurses5-dev to match libncurses5:i386 if it exists. 711 # In some Ubuntu releases, lib32ncurses5-dev is a transition package to 712 # lib32ncurses-dev, so use that as a fallback. 713 if package_exists("lib32ncurses5-dev"): 714 packages.append("lib32ncurses5-dev") 715 else: 716 packages.append("lib32ncurses-dev") 717 718 return packages 719 720 721# Packages suffixed with t64 are "transition packages" and should be preferred. 722def maybe_append_t64(package): 723 name = package.split(":") 724 name[0] += "t64" 725 renamed = ":".join(name) 726 return renamed if package_exists(renamed) else package 727 728 729# Debian is in the process of transitioning to automatic debug packages, which 730# have the -dbgsym suffix (https://wiki.debian.org/AutomaticDebugPackages). 731# Untransitioned packages have the -dbg suffix. And on some systems, neither 732# will be available, so exclude the ones that are missing. 733def dbg_package_name(package): 734 package = maybe_append_t64(package) 735 if package_exists(package + "-dbgsym"): 736 return [package + "-dbgsym"] 737 if package_exists(package + "-dbg"): 738 return [package + "-dbg"] 739 return [] 740 741 742def dbg_list(options): 743 if not options.syms: 744 print("Skipping debugging symbols.", file=sys.stderr) 745 return [] 746 print("Including debugging symbols.", file=sys.stderr) 747 748 packages = [ 749 dbg_package for package in lib_list() 750 for dbg_package in dbg_package_name(package) 751 ] 752 753 # Debugging symbols packages not following common naming scheme 754 if not dbg_package_name("libstdc++6"): 755 for version in ["8", "7", "6", "5", "4.9", "4.8", "4.7", "4.6"]: 756 if package_exists("libstdc++6-%s-dbg" % version): 757 packages.append("libstdc++6-%s-dbg" % version) 758 break 759 760 if not dbg_package_name("libatk1.0-0"): 761 packages.extend(dbg_package_name("libatk1.0")) 762 763 if not dbg_package_name("libpango-1.0-0"): 764 packages.extend(dbg_package_name("libpango1.0-dev")) 765 766 return packages 767 768 769def package_list(options): 770 packages = (dev_list() + lib_list() + dbg_list(options) + 771 lib32_list(options) + arm_list(options) + nacl_list(options) + 772 backwards_compatible_list(options)) 773 packages = [maybe_append_t64(package) for package in set(packages)] 774 775 # Sort all the :i386 packages to the front, to avoid confusing dpkg-query 776 # (https://crbug.com/446172). 777 return sorted(packages, key=lambda x: (not x.endswith(":i386"), x)) 778 779 780def missing_packages(packages): 781 try: 782 subprocess.run( 783 ["dpkg-query", "-W", "-f", " "] + packages, 784 check=True, 785 capture_output=True, 786 ) 787 return [] 788 except subprocess.CalledProcessError as e: 789 return [ 790 line.split(" ")[-1] for line in e.stderr.decode().strip().splitlines() 791 ] 792 793 794def package_is_installable(package): 795 result = subprocess.run(["apt-cache", "show", package], capture_output=True) 796 return result.returncode == 0 797 798 799def quick_check(options): 800 if not options.quick_check: 801 return 802 803 missing = missing_packages(package_list(options)) 804 if not missing: 805 sys.exit(0) 806 807 not_installed = [] 808 unknown = [] 809 for p in missing: 810 if package_is_installable(p): 811 not_installed.append(p) 812 else: 813 unknown.append(p) 814 815 if not_installed: 816 print("WARNING: The following packages are not installed:", file=sys.stderr) 817 print(" ".join(not_installed), file=sys.stderr) 818 819 if unknown: 820 print("WARNING: The following packages are unknown to your system", 821 file=sys.stderr) 822 print("(maybe missing a repo or need to 'sudo apt-get update'):", 823 file=sys.stderr) 824 print(" ".join(unknown), file=sys.stderr) 825 826 sys.exit(1) 827 828 829def find_missing_packages(options): 830 print("Finding missing packages...", file=sys.stderr) 831 832 packages = package_list(options) 833 packages_str = " ".join(packages) 834 print("Packages required: " + packages_str, file=sys.stderr) 835 836 query_cmd = ["apt-get", "--just-print", "install"] + packages 837 env = os.environ.copy() 838 env["LANGUAGE"] = "en" 839 env["LANG"] = "C" 840 cmd_output = subprocess.check_output(query_cmd, env=env).decode() 841 lines = cmd_output.splitlines() 842 843 install = [] 844 for pattern in ( 845 "The following NEW packages will be installed:", 846 "The following packages will be upgraded:", 847 ): 848 if pattern in lines: 849 for line in lines[lines.index(pattern) + 1:]: 850 if not line.startswith(" "): 851 break 852 install += line.strip().split(" ") 853 return install 854 855 856def install_packages(options): 857 try: 858 packages = find_missing_packages(options) 859 if packages: 860 quiet = ["-qq", "--assume-yes"] if options.no_prompt else [] 861 subprocess.check_call(["sudo", "apt-get", "install"] + quiet + packages) 862 print(file=sys.stderr) 863 else: 864 print("No missing packages, and the packages are up to date.", 865 file=sys.stderr) 866 867 except subprocess.CalledProcessError as e: 868 # An apt-get exit status of 100 indicates that a real error has occurred. 869 print("`apt-get --just-print install ...` failed", file=sys.stderr) 870 print("It produced the following output:", file=sys.stderr) 871 print(file=sys.stderr) 872 print("You will have to install the above packages yourself.", 873 file=sys.stderr) 874 print(file=sys.stderr) 875 sys.exit(100) 876 877 878# Install the Chrome OS default fonts. This must go after running 879# apt-get, since install-chromeos-fonts depends on curl. 880def install_chromeos_fonts(options): 881 if not options.chromeos_fonts: 882 print("Skipping installation of Chrome OS fonts.", file=sys.stderr) 883 return 884 print("Installing Chrome OS fonts.", file=sys.stderr) 885 886 dir = os.path.abspath(os.path.dirname(__file__)) 887 888 try: 889 subprocess.check_call( 890 ["sudo", 891 os.path.join(dir, "linux", "install-chromeos-fonts.py")]) 892 except subprocess.CalledProcessError: 893 print("ERROR: The installation of the Chrome OS default fonts failed.", 894 file=sys.stderr) 895 if (subprocess.check_output( 896 ["stat", "-f", "-c", "%T", dir], ).decode().startswith("nfs")): 897 print( 898 "The reason is that your repo is installed on a remote file system.", 899 file=sys.stderr) 900 else: 901 print( 902 "This is expected if your repo is installed on a remote file system.", 903 file=sys.stderr) 904 905 print("It is recommended to install your repo on a local file system.", 906 file=sys.stderr) 907 print("You can skip the installation of the Chrome OS default fonts with", 908 file=sys.stderr) 909 print("the command line option: --no-chromeos-fonts.", file=sys.stderr) 910 sys.exit(1) 911 912 913def install_locales(): 914 print("Installing locales.", file=sys.stderr) 915 CHROMIUM_LOCALES = [ 916 "da_DK.UTF-8", "en_US.UTF-8", "fr_FR.UTF-8", "he_IL.UTF-8", "zh_TW.UTF-8" 917 ] 918 LOCALE_GEN = "/etc/locale.gen" 919 if os.path.exists(LOCALE_GEN): 920 old_locale_gen = open(LOCALE_GEN).read() 921 for locale in CHROMIUM_LOCALES: 922 subprocess.check_call( 923 ["sudo", "sed", "-i", 924 "s/^# %s/%s/" % (locale, locale), LOCALE_GEN]) 925 926 # Regenerating locales can take a while, so only do it if we need to. 927 locale_gen = open(LOCALE_GEN).read() 928 if locale_gen != old_locale_gen: 929 subprocess.check_call(["sudo", "locale-gen"]) 930 else: 931 print("Locales already up-to-date.", file=sys.stderr) 932 else: 933 for locale in CHROMIUM_LOCALES: 934 subprocess.check_call(["sudo", "locale-gen", locale]) 935 936 937def main(): 938 options = parse_args(sys.argv[1:]) 939 check_lsb_release() 940 check_distro(options) 941 check_architecture() 942 quick_check(options) 943 check_root() 944 apt_update(options) 945 install_packages(options) 946 install_chromeos_fonts(options) 947 install_locales() 948 return 0 949 950 951if __name__ == "__main__": 952 sys.exit(main()) 953