• 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      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 = ["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        "EoS refers to end of standard support and does not include",
161        "extended security support.",
162        "\tUbuntu 20.04 LTS (focal with EoS April 2025)",
163        "\tUbuntu 22.04 LTS (jammy with EoS June 2027)",
164        "\tUbuntu 24.04 LTS (noble with EoS June 2029)",
165        "\tDebian 10 (buster) or later",
166        sep="\n",
167        file=sys.stderr,
168    )
169    sys.exit(1)
170
171
172def check_architecture():
173  architecture = subprocess.check_output(["uname", "-m"]).decode().strip()
174  if architecture not in ["i686", "x86_64", 'aarch64']:
175    print("Only x86 and ARM64 architectures are currently supported",
176          file=sys.stderr)
177    sys.exit(1)
178
179
180def check_root():
181  if os.geteuid() != 0:
182    print("Running as non-root user.", file=sys.stderr)
183    print("You might have to enter your password one or more times for 'sudo'.",
184          file=sys.stderr)
185    print(file=sys.stderr)
186
187
188def apt_update(options):
189  if options.lib32 or options.nacl:
190    subprocess.check_call(["sudo", "dpkg", "--add-architecture", "i386"])
191  subprocess.check_call(["sudo", "apt-get", "update"])
192
193
194# Packages needed for development
195def dev_list():
196  packages = [
197      "binutils",
198      "bison",
199      "bzip2",
200      "cdbs",
201      "curl",
202      "dbus-x11",
203      "devscripts",
204      "dpkg-dev",
205      "elfutils",
206      "fakeroot",
207      "flex",
208      "git-core",
209      "gperf",
210      "libasound2-dev",
211      "libatspi2.0-dev",
212      "libbrlapi-dev",
213      "libbz2-dev",
214      "libc6-dev",
215      "libcairo2-dev",
216      "libcap-dev",
217      "libcups2-dev",
218      "libcurl4-gnutls-dev",
219      "libdrm-dev",
220      "libelf-dev",
221      "libevdev-dev",
222      "libffi-dev",
223      "libfuse2",
224      "libgbm-dev",
225      "libglib2.0-dev",
226      "libglu1-mesa-dev",
227      "libgtk-3-dev",
228      "libkrb5-dev",
229      "libnspr4-dev",
230      "libnss3-dev",
231      "libpam0g-dev",
232      "libpci-dev",
233      "libpulse-dev",
234      "libsctp-dev",
235      "libspeechd-dev",
236      "libsqlite3-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  # TODO(b/339091434): Remove this workaround once this bug is fixed.  This
330  # workaround ensures the newer libssl-dev is used to prevent package conficts.
331  apt_cache_cmd = ["apt-cache", "show", "libssl-dev"]
332  output = subprocess.check_output(apt_cache_cmd).decode()
333  pattern = re.compile(r'^Version: (.+?)$', re.M)
334  versions = re.findall(pattern, output)
335  if set(versions) == {"3.2.1-3", "3.0.10-1"}:
336    packages.append("libssl-dev=3.2.1-3")
337  else:
338    packages.append("libssl-dev")
339
340  return packages
341
342
343# List of required run-time libraries
344def lib_list():
345  packages = [
346      "libatk1.0-0",
347      "libatspi2.0-0",
348      "libc6",
349      "libcairo2",
350      "libcap2",
351      "libcgi-session-perl",
352      "libcups2",
353      "libdrm2",
354      "libegl1",
355      "libevdev2",
356      "libexpat1",
357      "libfontconfig1",
358      "libfreetype6",
359      "libgbm1",
360      "libglib2.0-0",
361      "libgl1",
362      "libgtk-3-0",
363      "libpam0g",
364      "libpango-1.0-0",
365      "libpangocairo-1.0-0",
366      "libpci3",
367      "libpcre3",
368      "libpixman-1-0",
369      "libspeechd2",
370      "libstdc++6",
371      "libsqlite3-0",
372      "libuuid1",
373      "libwayland-egl1",
374      "libwayland-egl1-mesa",
375      "libx11-6",
376      "libx11-xcb1",
377      "libxau6",
378      "libxcb1",
379      "libxcomposite1",
380      "libxcursor1",
381      "libxdamage1",
382      "libxdmcp6",
383      "libxext6",
384      "libxfixes3",
385      "libxi6",
386      "libxinerama1",
387      "libxrandr2",
388      "libxrender1",
389      "libxtst6",
390      "x11-utils",
391      "x11-xserver-utils",
392      "xserver-xorg-core",
393      "xserver-xorg-video-dummy",
394      "xvfb",
395      "zlib1g",
396  ]
397
398  # Run-time libraries required by chromeos only
399  packages += [
400      "libpulse0",
401      "libbz2-1.0",
402  ]
403
404  # May not exist (e.g. ARM64)
405  if package_exists("lib32z1"):
406    packages.append("lib32z1")
407
408  if package_exists("libffi8"):
409    packages.append("libffi8")
410  elif package_exists("libffi7"):
411    packages.append("libffi7")
412  elif package_exists("libffi6"):
413    packages.append("libffi6")
414
415  # Workaround for dependency On Ubuntu 24.04 LTS (noble)
416  if distro_codename() == "noble":
417    packages.append("libpng16-16t64")
418  elif package_exists("libpng16-16"):
419    packages.append("libpng16-16")
420  else:
421    packages.append("libpng12-0")
422
423  if package_exists("libnspr4"):
424    packages.extend(["libnspr4", "libnss3"])
425  else:
426    packages.extend(["libnspr4-0d", "libnss3-1d"])
427
428  if package_exists("appmenu-gtk"):
429    packages.append("appmenu-gtk")
430  if package_exists("libgnome-keyring0"):
431    packages.append("libgnome-keyring0")
432  if package_exists("libgnome-keyring-dev"):
433    packages.append("libgnome-keyring-dev")
434  if package_exists("libvulkan1"):
435    packages.append("libvulkan1")
436  if package_exists("libinput10"):
437    packages.append("libinput10")
438
439  # Work around for dependency On Ubuntu 24.04 LTS (noble)
440  if distro_codename() == "noble":
441    packages.append("libncurses6")
442    packages.append("libasound2t64")
443  else:
444    packages.append("libncurses5")
445    packages.append("libasound2")
446
447  return packages
448
449
450def lib32_list(options):
451  if not options.lib32:
452    print("Skipping 32-bit libraries.", file=sys.stderr)
453    return []
454  print("Including 32-bit libraries.", file=sys.stderr)
455
456  packages = [
457      # 32-bit libraries needed for a 32-bit build
458      # includes some 32-bit libraries required by the Android SDK
459      # See https://developer.android.com/sdk/installing/index.html?pkg=tools
460      "libasound2:i386",
461      "libatk-bridge2.0-0:i386",
462      "libatk1.0-0:i386",
463      "libatspi2.0-0:i386",
464      "libdbus-1-3:i386",
465      "libegl1:i386",
466      "libgl1:i386",
467      "libglib2.0-0:i386",
468      "libnss3:i386",
469      "libpango-1.0-0:i386",
470      "libpangocairo-1.0-0:i386",
471      "libstdc++6:i386",
472      "libwayland-egl1:i386",
473      "libx11-xcb1:i386",
474      "libxcomposite1:i386",
475      "libxdamage1:i386",
476      "libxkbcommon0:i386",
477      "libxrandr2:i386",
478      "libxtst6:i386",
479      "zlib1g:i386",
480      # 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf
481      "linux-libc-dev:i386",
482      "libexpat1:i386",
483      "libpci3:i386",
484  ]
485
486  # When cross building for arm/Android on 64-bit systems the host binaries
487  # that are part of v8 need to be compiled with -m32 which means
488  # that basic multilib support is needed.
489  if "ELF 64-bit" in subprocess.check_output(["file", "-L",
490                                              "/sbin/init"]).decode():
491    # gcc-multilib conflicts with the arm cross compiler but
492    # g++-X.Y-multilib gives us the 32-bit support that we need. Find out the
493    # appropriate value of X and Y by seeing what version the current
494    # distribution's g++-multilib package depends on.
495    lines = subprocess.check_output(
496        ["apt-cache", "depends", "g++-multilib", "--important"]).decode()
497    pattern = re.compile(r"g\+\+-[0-9.]+-multilib")
498    packages += re.findall(pattern, lines)
499
500  # Work around for 32-bit dependency On Ubuntu 24.04 LTS (noble)
501  if distro_codename() == "noble":
502    packages.append("libncurses6:i386")
503  else:
504    packages.append("libncurses5:i386")
505
506  return packages
507
508
509# Packages that have been removed from this script. Regardless of configuration
510# or options passed to this script, whenever a package is removed, it should be
511# added here.
512def backwards_compatible_list(options):
513  if not options.backwards_compatible:
514    print("Skipping backwards compatible packages.", file=sys.stderr)
515    return []
516  print("Including backwards compatible packages.", file=sys.stderr)
517
518  packages = [
519      "7za",
520      "fonts-indic",
521      "fonts-ipafont",
522      "fonts-stix",
523      "fonts-thai-tlwg",
524      "fonts-tlwg-garuda",
525      "g++",
526      "g++-4.8-multilib-arm-linux-gnueabihf",
527      "gcc-4.8-multilib-arm-linux-gnueabihf",
528      "g++-9-multilib-arm-linux-gnueabihf",
529      "gcc-9-multilib-arm-linux-gnueabihf",
530      "gcc-arm-linux-gnueabihf",
531      "g++-10-multilib-arm-linux-gnueabihf",
532      "gcc-10-multilib-arm-linux-gnueabihf",
533      "g++-10-arm-linux-gnueabihf",
534      "gcc-10-arm-linux-gnueabihf",
535      "git-svn",
536      "language-pack-da",
537      "language-pack-fr",
538      "language-pack-he",
539      "language-pack-zh-hant",
540      "libappindicator-dev",
541      "libappindicator1",
542      "libappindicator3-1",
543      "libappindicator3-dev",
544      "libdconf-dev",
545      "libdconf1",
546      "libdconf1:i386",
547      "libexif-dev",
548      "libexif12",
549      "libexif12:i386",
550      "libgbm-dev",
551      "libgbm-dev-lts-trusty",
552      "libgbm-dev-lts-xenial",
553      "libgconf-2-4:i386",
554      "libgconf2-dev",
555      "libgl1-mesa-dev",
556      "libgl1-mesa-dev-lts-trusty",
557      "libgl1-mesa-dev-lts-xenial",
558      "libgl1-mesa-glx:i386",
559      "libgl1-mesa-glx-lts-trusty:i386",
560      "libgl1-mesa-glx-lts-xenial:i386",
561      "libgles2-mesa-dev",
562      "libgles2-mesa-dev-lts-trusty",
563      "libgles2-mesa-dev-lts-xenial",
564      "libgtk-3-0:i386",
565      "libgtk2.0-0",
566      "libgtk2.0-0:i386",
567      "libgtk2.0-dev",
568      "mesa-common-dev",
569      "mesa-common-dev-lts-trusty",
570      "mesa-common-dev-lts-xenial",
571      "msttcorefonts",
572      "python-dev",
573      "python-setuptools",
574      "snapcraft",
575      "ttf-dejavu-core",
576      "ttf-indic-fonts",
577      "ttf-kochi-gothic",
578      "ttf-kochi-mincho",
579      "ttf-mscorefonts-installer",
580      "xfonts-mathml",
581  ]
582
583  if package_exists("python-is-python2"):
584    packages.extend(["python-is-python2", "python2-dev"])
585  else:
586    packages.append("python")
587
588  if package_exists("python-crypto"):
589    packages.append("python-crypto")
590
591  if package_exists("python-numpy"):
592    packages.append("python-numpy")
593
594  if package_exists("python-openssl"):
595    packages.append("python-openssl")
596
597  if package_exists("python-psutil"):
598    packages.append("python-psutil")
599
600  if package_exists("python-yaml"):
601    packages.append("python-yaml")
602
603  if package_exists("apache2.2-bin"):
604    packages.append("apache2.2-bin")
605  else:
606    packages.append("apache2-bin")
607
608  php_versions = [
609      ("php8.1-cgi", "libapache2-mod-php8.1"),
610      ("php8.0-cgi", "libapache2-mod-php8.0"),
611      ("php7.4-cgi", "libapache2-mod-php7.4"),
612      ("php7.3-cgi", "libapache2-mod-php7.3"),
613      ("php7.2-cgi", "libapache2-mod-php7.2"),
614      ("php7.1-cgi", "libapache2-mod-php7.1"),
615      ("php7.0-cgi", "libapache2-mod-php7.0"),
616      ("php5-cgi", "libapache2-mod-php5"),
617  ]
618
619  for php_cgi, mod_php in php_versions:
620    if package_exists(php_cgi):
621      packages.extend([php_cgi, mod_php])
622      break
623
624  return [package for package in packages if package_exists(package)]
625
626
627def arm_list(options):
628  if not options.arm:
629    print("Skipping ARM cross toolchain.", file=sys.stderr)
630    return []
631  print("Including ARM cross toolchain.", file=sys.stderr)
632
633  # arm cross toolchain packages needed to build chrome on armhf
634  packages = [
635      "g++-arm-linux-gnueabihf",
636      "gcc-arm-linux-gnueabihf",
637      "libc6-dev-armhf-cross",
638      "linux-libc-dev-armhf-cross",
639  ]
640
641  return packages
642
643
644def nacl_list(options):
645  if not options.nacl:
646    print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies.",
647          file=sys.stderr)
648    return []
649  print("Including NaCl, NaCl toolchain, NaCl ports dependencies.",
650        file=sys.stderr)
651
652  packages = [
653      "g++-mingw-w64-i686",
654      "lib32z1-dev",
655      "libasound2:i386",
656      "libcap2:i386",
657      "libelf-dev:i386",
658      "libfontconfig1:i386",
659      "libglib2.0-0:i386",
660      "libgpm2:i386",
661      "libnss3:i386",
662      "libpango-1.0-0:i386",
663      "libssl-dev:i386",
664      "libtinfo-dev",
665      "libtinfo-dev:i386",
666      "libtool",
667      "libuuid1:i386",
668      "libxcomposite1:i386",
669      "libxcursor1:i386",
670      "libxdamage1:i386",
671      "libxi6:i386",
672      "libxrandr2:i386",
673      "libxss1:i386",
674      "libxtst6:i386",
675      "texinfo",
676      "xvfb",
677      # Packages to build NaCl, its toolchains, and its ports.
678      "ant",
679      "autoconf",
680      "bison",
681      "cmake",
682      "gawk",
683      "intltool",
684      "xutils-dev",
685      "xsltproc",
686  ]
687
688  # Some package names have changed over time
689  if package_exists("libssl-dev"):
690    packages.append("libssl-dev:i386")
691  elif package_exists("libssl1.1"):
692    packages.append("libssl1.1:i386")
693  elif package_exists("libssl1.0.2"):
694    packages.append("libssl1.0.2:i386")
695  else:
696    packages.append("libssl1.0.0:i386")
697
698  if package_exists("libtinfo5"):
699    packages.append("libtinfo5")
700
701  if package_exists("libudev1"):
702    packages.append("libudev1:i386")
703  else:
704    packages.append("libudev0:i386")
705
706  # Work around for nacl dependency On Ubuntu 24.04 LTS (noble)
707  if distro_codename() == "noble":
708    packages.append("libncurses6:i386")
709    packages.append("lib32ncurses-dev")
710  else:
711    packages.append("libncurses5:i386")
712    packages.append("lib32ncurses5-dev")
713
714  return packages
715
716
717# Debian is in the process of transitioning to automatic debug packages, which
718# have the -dbgsym suffix (https://wiki.debian.org/AutomaticDebugPackages).
719# Untransitioned packages have the -dbg suffix.  And on some systems, neither
720# will be available, so exclude the ones that are missing.
721def dbg_package_name(package):
722  if package_exists(package + "-dbgsym"):
723    return [package + "-dbgsym"]
724  if package_exists(package + "-dbg"):
725    return [package + "-dbg"]
726  return []
727
728
729def dbg_list(options):
730  if not options.syms:
731    print("Skipping debugging symbols.", file=sys.stderr)
732    return []
733  print("Including debugging symbols.", file=sys.stderr)
734
735  packages = [
736      dbg_package for package in lib_list()
737      for dbg_package in dbg_package_name(package)
738  ]
739
740  # Debugging symbols packages not following common naming scheme
741  if not dbg_package_name("libstdc++6"):
742    for version in ["8", "7", "6", "5", "4.9", "4.8", "4.7", "4.6"]:
743      if package_exists("libstdc++6-%s-dbg" % version):
744        packages.append("libstdc++6-%s-dbg" % version)
745        break
746
747  if not dbg_package_name("libatk1.0-0"):
748    packages.extend(dbg_package_name("libatk1.0"))
749
750  if not dbg_package_name("libpango-1.0-0"):
751    packages.extend(dbg_package_name("libpango1.0-dev"))
752
753  return packages
754
755
756def package_list(options):
757  packages = (dev_list() + lib_list() + dbg_list(options) +
758              lib32_list(options) + arm_list(options) + nacl_list(options) +
759              backwards_compatible_list(options))
760
761  # Sort all the :i386 packages to the front, to avoid confusing dpkg-query
762  # (https://crbug.com/446172).
763  return sorted(set(packages), key=lambda x: (not x.endswith(":i386"), x))
764
765
766def missing_packages(packages):
767  try:
768    subprocess.run(
769        ["dpkg-query", "-W", "-f", " "] + packages,
770        check=True,
771        capture_output=True,
772    )
773    return []
774  except subprocess.CalledProcessError as e:
775    return [
776        line.split(" ")[-1] for line in e.stderr.decode().strip().splitlines()
777    ]
778
779
780def package_is_installable(package):
781  result = subprocess.run(["apt-cache", "show", package], capture_output=True)
782  return result.returncode == 0
783
784
785def quick_check(options):
786  if not options.quick_check:
787    return
788
789  missing = missing_packages(package_list(options))
790  if not missing:
791    sys.exit(0)
792
793  not_installed = []
794  unknown = []
795  for p in missing:
796    if package_is_installable(p):
797      not_installed.append(p)
798    else:
799      unknown.append(p)
800
801  if not_installed:
802    print("WARNING: The following packages are not installed:", file=sys.stderr)
803    print(" ".join(not_installed), file=sys.stderr)
804
805  if unknown:
806    print("WARNING: The following packages are unknown to your system",
807          file=sys.stderr)
808    print("(maybe missing a repo or need to 'sudo apt-get update'):",
809          file=sys.stderr)
810    print(" ".join(unknown), file=sys.stderr)
811
812  sys.exit(1)
813
814
815def find_missing_packages(options):
816  print("Finding missing packages...", file=sys.stderr)
817
818  packages = package_list(options)
819  packages_str = " ".join(packages)
820  print("Packages required: " + packages_str, file=sys.stderr)
821
822  query_cmd = ["apt-get", "--just-print", "install"] + packages
823  env = os.environ.copy()
824  env["LANGUAGE"] = "en"
825  env["LANG"] = "C"
826  cmd_output = subprocess.check_output(query_cmd, env=env).decode()
827  lines = cmd_output.splitlines()
828
829  install = []
830  for pattern in (
831      "The following NEW packages will be installed:",
832      "The following packages will be upgraded:",
833  ):
834    if pattern in lines:
835      for line in lines[lines.index(pattern) + 1:]:
836        if not line.startswith("  "):
837          break
838        install += line.strip().split(" ")
839  return install
840
841
842def install_packages(options):
843  try:
844    packages = find_missing_packages(options)
845    if packages:
846      quiet = ["-qq", "--assume-yes"] if options.no_prompt else []
847      subprocess.check_call(["sudo", "apt-get", "install"] + quiet + packages)
848      print(file=sys.stderr)
849    else:
850      print("No missing packages, and the packages are up to date.",
851            file=sys.stderr)
852
853  except subprocess.CalledProcessError as e:
854    # An apt-get exit status of 100 indicates that a real error has occurred.
855    print("`apt-get --just-print install ...` failed", file=sys.stderr)
856    print("It produced the following output:", file=sys.stderr)
857    print(e.output.decode(), file=sys.stderr)
858    print(file=sys.stderr)
859    print("You will have to install the above packages yourself.",
860          file=sys.stderr)
861    print(file=sys.stderr)
862    sys.exit(100)
863
864
865# Install the Chrome OS default fonts. This must go after running
866# apt-get, since install-chromeos-fonts depends on curl.
867def install_chromeos_fonts(options):
868  if not options.chromeos_fonts:
869    print("Skipping installation of Chrome OS fonts.", file=sys.stderr)
870    return
871  print("Installing Chrome OS fonts.", file=sys.stderr)
872
873  dir = os.path.abspath(os.path.dirname(__file__))
874
875  try:
876    subprocess.check_call(
877        ["sudo",
878         os.path.join(dir, "linux", "install-chromeos-fonts.py")])
879  except subprocess.CalledProcessError:
880    print("ERROR: The installation of the Chrome OS default fonts failed.",
881          file=sys.stderr)
882    if (subprocess.check_output(
883        ["stat", "-f", "-c", "%T", dir], ).decode().startswith("nfs")):
884      print(
885          "The reason is that your repo is installed on a remote file system.",
886          file=sys.stderr)
887    else:
888      print(
889          "This is expected if your repo is installed on a remote file system.",
890          file=sys.stderr)
891
892    print("It is recommended to install your repo on a local file system.",
893          file=sys.stderr)
894    print("You can skip the installation of the Chrome OS default fonts with",
895          file=sys.stderr)
896    print("the command line option: --no-chromeos-fonts.", file=sys.stderr)
897    sys.exit(1)
898
899
900def install_locales():
901  print("Installing locales.", file=sys.stderr)
902  CHROMIUM_LOCALES = [
903      "da_DK.UTF-8", "en_US.UTF-8", "fr_FR.UTF-8", "he_IL.UTF-8", "zh_TW.UTF-8"
904  ]
905  LOCALE_GEN = "/etc/locale.gen"
906  if os.path.exists(LOCALE_GEN):
907    old_locale_gen = open(LOCALE_GEN).read()
908    for locale in CHROMIUM_LOCALES:
909      subprocess.check_call(
910          ["sudo", "sed", "-i",
911           "s/^# %s/%s/" % (locale, locale), LOCALE_GEN])
912
913    # Regenerating locales can take a while, so only do it if we need to.
914    locale_gen = open(LOCALE_GEN).read()
915    if locale_gen != old_locale_gen:
916      subprocess.check_call(["sudo", "locale-gen"])
917    else:
918      print("Locales already up-to-date.", file=sys.stderr)
919  else:
920    for locale in CHROMIUM_LOCALES:
921      subprocess.check_call(["sudo", "locale-gen", locale])
922
923
924def main():
925  options = parse_args(sys.argv[1:])
926  check_lsb_release()
927  check_distro(options)
928  check_architecture()
929  quick_check(options)
930  check_root()
931  apt_update(options)
932  install_packages(options)
933  install_chromeos_fonts(options)
934  install_locales()
935  return 0
936
937
938if __name__ == "__main__":
939  sys.exit(main())
940