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