[M120 Migration][Gamepad]Add gamepad event latency Test code
[platform/framework/web/chromium-efl.git] / build / install-build-deps.py
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
11 import argparse
12 import functools
13 import os
14 import re
15 import shutil
16 import subprocess
17 import sys
18
19
20 @functools.lru_cache(maxsize=1)
21 def 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
31 def package_exists(package_name: str) -> bool:
32   return package_name in build_apt_package_list()
33
34
35 def 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
131 def 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)
139 def distro_codename():
140   return subprocess.check_output(["lsb_release", "--codename",
141                                   "--short"]).decode().strip()
142
143
144 def 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
170 def 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
177 def 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
185 def 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
192 def 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
327 def 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
418 def 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.
474 def 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
589 def 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
625 def 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.
696 def 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
704 def 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
731 def 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
741 def missing_packages(packages):
742   try:
743     subprocess.run(
744         ["dpkg-query", "-W", "-f", " "] + packages,
745         check=True,
746         capture_output=True,
747     )
748     return []
749   except subprocess.CalledProcessError as e:
750     return [
751         line.split(" ")[-1] for line in e.stderr.decode().strip().splitlines()
752     ]
753
754
755 def package_is_installable(package):
756   result = subprocess.run(["apt-cache", "show", package], capture_output=True)
757   return result.returncode == 0
758
759
760 def quick_check(options):
761   if not options.quick_check:
762     return
763
764   missing = missing_packages(package_list(options))
765   if not missing:
766     sys.exit(0)
767
768   not_installed = []
769   unknown = []
770   for p in missing:
771     if package_is_installable(p):
772       not_installed.append(p)
773     else:
774       unknown.append(p)
775
776   if not_installed:
777     print("WARNING: The following packages are not installed:", file=sys.stderr)
778     print(" ".join(not_installed), file=sys.stderr)
779
780   if unknown:
781     print("WARNING: The following packages are unknown to your system",
782           file=sys.stderr)
783     print("(maybe missing a repo or need to 'sudo apt-get update'):",
784           file=sys.stderr)
785     print(" ".join(unknown), file=sys.stderr)
786
787   sys.exit(1)
788
789
790 def find_missing_packages(options):
791   print("Finding missing packages...", file=sys.stderr)
792
793   packages = package_list(options)
794   packages_str = " ".join(packages)
795   print("Packages required: " + packages_str, file=sys.stderr)
796
797   query_cmd = ["apt-get", "--just-print", "install"] + packages
798   env = os.environ.copy()
799   env["LANGUAGE"] = "en"
800   env["LANG"] = "C"
801   cmd_output = subprocess.check_output(query_cmd, env=env).decode()
802   lines = cmd_output.splitlines()
803
804   install = []
805   for pattern in (
806       "The following NEW packages will be installed:",
807       "The following packages will be upgraded:",
808   ):
809     if pattern in lines:
810       for line in lines[lines.index(pattern) + 1:]:
811         if not line.startswith("  "):
812           break
813         install += line.strip().split(" ")
814   return install
815
816
817 def install_packages(options):
818   try:
819     packages = find_missing_packages(options)
820     if packages:
821       quiet = ["-qq", "--assume-yes"] if options.no_prompt else []
822       subprocess.check_call(["sudo", "apt-get", "install"] + quiet + packages)
823       print(file=sys.stderr)
824     else:
825       print("No missing packages, and the packages are up to date.",
826             file=sys.stderr)
827
828   except subprocess.CalledProcessError as e:
829     # An apt-get exit status of 100 indicates that a real error has occurred.
830     print("`apt-get --just-print install ...` failed", file=sys.stderr)
831     print("It produced the following output:", file=sys.stderr)
832     print(e.output.decode(), file=sys.stderr)
833     print(file=sys.stderr)
834     print("You will have to install the above packages yourself.",
835           file=sys.stderr)
836     print(file=sys.stderr)
837     sys.exit(100)
838
839
840 # Install the Chrome OS default fonts. This must go after running
841 # apt-get, since install-chromeos-fonts depends on curl.
842 def install_chromeos_fonts(options):
843   if not options.chromeos_fonts:
844     print("Skipping installation of Chrome OS fonts.", file=sys.stderr)
845     return
846   print("Installing Chrome OS fonts.", file=sys.stderr)
847
848   dir = os.path.abspath(os.path.dirname(__file__))
849
850   try:
851     subprocess.check_call(
852         ["sudo",
853          os.path.join(dir, "linux", "install-chromeos-fonts.py")])
854   except subprocess.CalledProcessError:
855     print("ERROR: The installation of the Chrome OS default fonts failed.",
856           file=sys.stderr)
857     if (subprocess.check_output(
858         ["stat", "-f", "-c", "%T", dir], ).decode().startswith("nfs")):
859       print(
860           "The reason is that your repo is installed on a remote file system.",
861           file=sys.stderr)
862     else:
863       print(
864           "This is expected if your repo is installed on a remote file system.",
865           file=sys.stderr)
866
867     print("It is recommended to install your repo on a local file system.",
868           file=sys.stderr)
869     print("You can skip the installation of the Chrome OS default fonts with",
870           file=sys.stderr)
871     print("the command line option: --no-chromeos-fonts.", file=sys.stderr)
872     sys.exit(1)
873
874
875 def install_locales():
876   print("Installing locales.", file=sys.stderr)
877   CHROMIUM_LOCALES = [
878       "da_DK.UTF-8", "en_US.utf8", "fr_FR.UTF-8", "he_IL.UTF-8", "zh_TW.UTF-8"
879   ]
880   LOCALE_GEN = "/etc/locale.gen"
881   if os.path.exists(LOCALE_GEN):
882     old_locale_gen = open(LOCALE_GEN).read()
883     for locale in CHROMIUM_LOCALES:
884       subprocess.check_call(
885           ["sudo", "sed", "-i",
886            "s/^# %s/%s/" % (locale, locale), LOCALE_GEN])
887
888     # Regenerating locales can take a while, so only do it if we need to.
889     locale_gen = open(LOCALE_GEN).read()
890     if locale_gen != old_locale_gen:
891       subprocess.check_call(["sudo", "locale-gen"])
892     else:
893       print("Locales already up-to-date.", file=sys.stderr)
894   else:
895     for locale in CHROMIUM_LOCALES:
896       subprocess.check_call(["sudo", "locale-gen", locale])
897
898
899 def main():
900   options = parse_args(sys.argv[1:])
901   check_lsb_release()
902   check_distro(options)
903   check_architecture()
904   quick_check(options)
905   check_root()
906   apt_update(options)
907   install_packages(options)
908   install_chromeos_fonts(options)
909   install_locales()
910   return 0
911
912
913 if __name__ == "__main__":
914   sys.exit(main())