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