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