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