tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

install-build-deps.py (27513B)


      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 
     11 import argparse
     12 import functools
     13 import os
     14 import re
     15 import shutil
     16 import subprocess
     17 import sys
     18 
     19 
     20 @functools.lru_cache(maxsize=1)
     21 def 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 
     31 def package_exists(package_name: str) -> bool:
     32  return package_name in build_apt_package_list()
     33 
     34 
     35 def 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 
    133 def 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)
    141 def distro_codename():
    142  return subprocess.check_output(["lsb_release", "--codename",
    143                                  "--short"]).decode().strip()
    144 
    145 
    146 def 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 
    174 def 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 
    182 def 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 
    190 def 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
    197 def 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
    335 def 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 
    448 def 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.
    509 def 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 
    624 def 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 
    655 def 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.
    722 def 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.
    733 def 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 
    742 def 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 
    769 def 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 
    780 def 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 
    794 def package_is_installable(package):
    795  result = subprocess.run(["apt-cache", "show", package], capture_output=True)
    796  return result.returncode == 0
    797 
    798 
    799 def 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 
    829 def 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 
    856 def 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.
    880 def 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 
    913 def 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 
    937 def 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 
    951 if __name__ == "__main__":
    952  sys.exit(main())