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.
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
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: (.+?)$",
27 return set(package + arch_map.get(arch, "")
28 for package, arch in re.findall(package_regex, output))
31 def package_exists(package_name: str) -> bool:
32 return package_name in build_apt_package_list()
36 parser = argparse.ArgumentParser(
37 description="Install Chromium build dependencies.")
38 parser.add_argument("--syms",
40 help="Enable installation of debugging symbols")
45 help="Disable installation of debugging symbols",
50 help="Enable installation of 32-bit libraries, e.g. for V8 snapshot",
55 help="Enable installation of android dependencies",
61 help="Disable installation of android dependencies",
63 parser.add_argument("--arm",
65 help="Enable installation of arm cross toolchain")
70 help="Disable installation of arm cross toolchain",
75 help="Enable installation of Chrome OS fonts",
78 "--no-chromeos-fonts",
80 dest="chromeos_fonts",
81 help="Disable installation of Chrome OS fonts",
86 help="Enable installation of prerequisites for building NaCl",
92 help="Disable installation of prerequisites for building NaCl",
95 "--backwards-compatible",
98 "Enable installation of packages that are no longer currently needed and"
99 + "have been removed from this script. Useful for bisection.",
102 "--no-backwards-compatible",
103 action="store_false",
104 dest="backwards_compatible",
106 "Disable installation of packages that are no longer currently needed and"
107 + "have been removed from this script.",
109 parser.add_argument("--no-prompt",
111 help="Automatic yes to prompts")
115 help="Quickly try to determine if dependencies are installed",
120 help="Attempt installation even on unsupported systems",
123 options = parser.parse_args(argv)
125 if options.arm or options.android:
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)
138 @functools.lru_cache(maxsize=1)
139 def distro_codename():
140 return subprocess.check_output(["lsb_release", "--codename",
141 "--short"]).decode().strip()
144 def check_distro(options):
145 if options.unsupported or options.quick_check:
148 distro_id = subprocess.check_output(["lsb_release", "--id",
149 "--short"]).decode().strip()
151 supported_codenames = ["bionic", "focal", "jammy"]
152 supported_ids = ["Debian"]
154 if (distro_codename() not in supported_codenames
155 and distro_id not in supported_ids):
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",
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)
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'.",
182 print(file=sys.stderr)
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"])
191 # Packages needed for development
215 "libcurl4-gnutls-dev",
262 # Packages needed for chromeos only
270 if package_exists("realpath"):
271 packages.append("realpath")
273 if package_exists("libjpeg-dev"):
274 packages.append("libjpeg-dev")
276 packages.append("libjpeg62-dev")
278 if package_exists("libudev1"):
279 packages.append("libudev1")
281 packages.append("libudev0")
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")
290 packages.append("libbrlapi0.5")
292 if package_exists("libav-tools"):
293 packages.append("libav-tools")
295 if package_exists("libvulkan-dev"):
296 packages.append("libvulkan-dev")
298 if package_exists("libinput-dev"):
299 packages.append("libinput-dev")
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")
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"])
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")
326 # List of required run-time libraries
336 "libcgi-session-perl",
351 "libpangocairo-1.0-0",
360 "libwayland-egl1-mesa",
381 # Run-time libraries required by chromeos only
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")
394 if package_exists("libpng16-16"):
395 packages.append("libpng16-16")
397 packages.append("libpng12-0")
399 if package_exists("libnspr4"):
400 packages.extend(["libnspr4", "libnss3"])
402 packages.extend(["libnspr4-0d", "libnss3-1d"])
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")
418 def lib32_list(options):
419 if not options.lib32:
420 print("Skipping 32-bit libraries.", file=sys.stderr)
422 print("Including 32-bit libraries.", file=sys.stderr)
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
429 "libatk-bridge2.0-0:i386",
431 "libatspi2.0-0:i386",
438 "libpango-1.0-0:i386",
439 "libpangocairo-1.0-0:i386",
441 "libwayland-egl1:i386",
443 "libxcomposite1:i386",
445 "libxkbcommon0:i386",
449 # 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf
450 "linux-libc-dev:i386",
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)
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
474 def backwards_compatible_list(options):
475 if not options.backwards_compatible:
476 print("Skipping backwards compatible packages.", file=sys.stderr)
478 print("Including backwards compatible packages.", file=sys.stderr)
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",
501 "language-pack-zh-hant",
502 "libappindicator-dev",
504 "libappindicator3-1",
505 "libappindicator3-dev",
513 "libgbm-dev-lts-trusty",
514 "libgbm-dev-lts-xenial",
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",
524 "libgles2-mesa-dev-lts-trusty",
525 "libgles2-mesa-dev-lts-xenial",
531 "mesa-common-dev-lts-trusty",
532 "mesa-common-dev-lts-xenial",
541 "ttf-mscorefonts-installer",
545 if package_exists("python-is-python2"):
546 packages.extend(["python-is-python2", "python2-dev"])
548 packages.append("python")
550 if package_exists("python-crypto"):
551 packages.append("python-crypto")
553 if package_exists("python-numpy"):
554 packages.append("python-numpy")
556 if package_exists("python-openssl"):
557 packages.append("python-openssl")
559 if package_exists("python-psutil"):
560 packages.append("python-psutil")
562 if package_exists("python-yaml"):
563 packages.append("python-yaml")
565 if package_exists("apache2.2-bin"):
566 packages.append("apache2.2-bin")
568 packages.append("apache2-bin")
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"),
581 for php_cgi, mod_php in php_versions:
582 if package_exists(php_cgi):
583 packages.extend([php_cgi, mod_php])
586 return [package for package in packages if package_exists(package)]
589 def arm_list(options):
591 print("Skipping ARM cross toolchain.", file=sys.stderr)
593 print("Including ARM cross toolchain.", file=sys.stderr)
595 # arm cross toolchain packages needed to build chrome on armhf
597 "libc6-dev-armhf-cross",
598 "linux-libc-dev-armhf-cross",
599 "g++-arm-linux-gnueabihf",
602 # Work around for dependency issue Ubuntu: http://crbug.com/435056
603 if distro_codename() == "bionic":
605 "g++-5-multilib-arm-linux-gnueabihf",
606 "gcc-5-multilib-arm-linux-gnueabihf",
607 "gcc-arm-linux-gnueabihf",
609 elif distro_codename() == "focal":
611 "g++-10-multilib-arm-linux-gnueabihf",
612 "gcc-10-multilib-arm-linux-gnueabihf",
613 "gcc-arm-linux-gnueabihf",
615 elif distro_codename() == "jammy":
617 "gcc-arm-linux-gnueabihf",
618 "g++-11-arm-linux-gnueabihf",
619 "gcc-11-arm-linux-gnueabihf",
625 def nacl_list(options):
627 print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies.",
630 print("Including NaCl, NaCl toolchain, NaCl ports dependencies.",
634 "g++-mingw-w64-i686",
639 "libfontconfig1:i386",
645 "libpango-1.0-0:i386",
651 "libxcomposite1:i386",
660 # Packages to build NaCl, its toolchains, and its ports.
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")
679 packages.append("libssl1.0.0:i386")
681 if package_exists("libtinfo5"):
682 packages.append("libtinfo5")
684 if package_exists("libudev1"):
685 packages.append("libudev1:i386")
687 packages.append("libudev0:i386")
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"]
704 def dbg_list(options):
706 print("Skipping debugging symbols.", file=sys.stderr)
708 print("Including debugging symbols.", file=sys.stderr)
711 dbg_package for package in lib_list()
712 for dbg_package in dbg_package_name(package)
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)
722 if not dbg_package_name("libatk1.0-0"):
723 packages.extend(dbg_package_name("libatk1.0"))
725 if not dbg_package_name("libpango-1.0-0"):
726 packages.extend(dbg_package_name("libpango1.0-dev"))
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))
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))
741 def missing_packages(packages):
744 ["dpkg-query", "-W", "-f", " "] + packages,
749 except subprocess.CalledProcessError as e:
751 line.split(" ")[-1] for line in e.stderr.decode().strip().splitlines()
755 def package_is_installable(package):
756 result = subprocess.run(["apt-cache", "show", package], capture_output=True)
757 return result.returncode == 0
760 def quick_check(options):
761 if not options.quick_check:
764 missing = missing_packages(package_list(options))
771 if package_is_installable(p):
772 not_installed.append(p)
777 print("WARNING: The following packages are not installed:", file=sys.stderr)
778 print(" ".join(not_installed), file=sys.stderr)
781 print("WARNING: The following packages are unknown to your system",
783 print("(maybe missing a repo or need to 'sudo apt-get update'):",
785 print(" ".join(unknown), file=sys.stderr)
790 def find_missing_packages(options):
791 print("Finding missing packages...", file=sys.stderr)
793 packages = package_list(options)
794 packages_str = " ".join(packages)
795 print("Packages required: " + packages_str, file=sys.stderr)
797 query_cmd = ["apt-get", "--just-print", "install"] + packages
798 env = os.environ.copy()
799 env["LANGUAGE"] = "en"
801 cmd_output = subprocess.check_output(query_cmd, env=env).decode()
802 lines = cmd_output.splitlines()
806 "The following NEW packages will be installed:",
807 "The following packages will be upgraded:",
810 for line in lines[lines.index(pattern) + 1:]:
811 if not line.startswith(" "):
813 install += line.strip().split(" ")
817 def install_packages(options):
819 packages = find_missing_packages(options)
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)
825 print("No missing packages, and the packages are up to date.",
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.",
836 print(file=sys.stderr)
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)
846 print("Installing Chrome OS fonts.", file=sys.stderr)
848 dir = os.path.abspath(os.path.dirname(__file__))
851 subprocess.check_call(
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.",
857 if (subprocess.check_output(
858 ["stat", "-f", "-c", "%T", dir], ).decode().startswith("nfs")):
860 "The reason is that your repo is installed on a remote file system.",
864 "This is expected if your repo is installed on a remote file system.",
867 print("It is recommended to install your repo on a local file system.",
869 print("You can skip the installation of the Chrome OS default fonts with",
871 print("the command line option: --no-chromeos-fonts.", file=sys.stderr)
875 def install_locales():
876 print("Installing locales.", file=sys.stderr)
878 "da_DK.UTF-8", "en_US.utf8", "fr_FR.UTF-8", "he_IL.UTF-8", "zh_TW.UTF-8"
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])
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"])
893 print("Locales already up-to-date.", file=sys.stderr)
895 for locale in CHROMIUM_LOCALES:
896 subprocess.check_call(["sudo", "locale-gen", locale])
900 options = parse_args(sys.argv[1:])
902 check_distro(options)
907 install_packages(options)
908 install_chromeos_fonts(options)
913 if __name__ == "__main__":