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