• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  # 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
448def 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.
509def 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
624def 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
655def 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.
722def 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.
733def 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
742def 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
769def 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
780def 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
794def package_is_installable(package):
795  result = subprocess.run(["apt-cache", "show", package], capture_output=True)
796  return result.returncode == 0
797
798
799def 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
829def 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
856def 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.
880def 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
913def 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
937def 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
951if __name__ == "__main__":
952  sys.exit(main())
953