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