blob: 81a78d0dfeae33239a73dca5c9599fce2a80038e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Script to install everything needed to build chromium
# including items requiring sudo privileges.
# See https://chromium.googlesource.com/chromium/src/+/main/docs/linux/build_instructions.md
import argparse
import functools
import os
import re
import shutil
import subprocess
import sys
@functools.lru_cache(maxsize=1)
def build_apt_package_list():
print("Building apt package list.", file=sys.stderr)
output = subprocess.check_output(["apt-cache", "dumpavail"]).decode()
arch_map = {"i386": ":i386"}
package_regex = re.compile(r"^Package: (.+?)$.+?^Architecture: (.+?)$",
re.M | re.S)
return set(package + arch_map.get(arch, "")
for package, arch in re.findall(package_regex, output))
def package_exists(package_name: str) -> bool:
return package_name in build_apt_package_list()
def parse_args(argv):
parser = argparse.ArgumentParser(
description="Install Chromium build dependencies.")
parser.add_argument("--syms",
action="store_true",
help="Enable installation of debugging symbols")
parser.add_argument(
"--no-syms",
action="store_false",
dest="syms",
help="Disable installation of debugging symbols",
)
parser.add_argument(
"--lib32",
action="store_true",
help="Enable installation of 32-bit libraries, e.g. for V8 snapshot",
)
parser.add_argument(
"--android",
action="store_true",
help="Enable installation of android dependencies",
)
parser.add_argument(
"--no-android",
action="store_false",
dest="android",
help="Disable installation of android dependencies",
)
parser.add_argument("--arm",
action="store_true",
help="Enable installation of arm cross toolchain")
parser.add_argument(
"--no-arm",
action="store_false",
dest="arm",
help="Disable installation of arm cross toolchain",
)
parser.add_argument(
"--chromeos-fonts",
action="store_true",
help="Enable installation of Chrome OS fonts",
)
parser.add_argument(
"--no-chromeos-fonts",
action="store_false",
dest="chromeos_fonts",
help="Disable installation of Chrome OS fonts",
)
parser.add_argument(
"--nacl",
action="store_true",
help="Enable installation of prerequisites for building NaCl",
)
parser.add_argument(
"--no-nacl",
action="store_false",
dest="nacl",
help="Disable installation of prerequisites for building NaCl",
)
parser.add_argument(
"--backwards-compatible",
action="store_true",
help=
"Enable installation of packages that are no longer currently needed and"
+ "have been removed from this script. Useful for bisection.",
)
parser.add_argument(
"--no-backwards-compatible",
action="store_false",
dest="backwards_compatible",
help=
"Disable installation of packages that are no longer currently needed and"
+ "have been removed from this script.",
)
parser.add_argument("--no-prompt",
action="store_true",
help="Automatic yes to prompts")
parser.add_argument(
"--quick-check",
action="store_true",
help="Quickly try to determine if dependencies are installed",
)
parser.add_argument(
"--unsupported",
action="store_true",
help="Attempt installation even on unsupported systems",
)
options = parser.parse_args(argv)
if options.arm or options.android:
options.lib32 = True
return options
def check_lsb_release():
if not shutil.which("lsb_release"):
print("ERROR: lsb_release not found in $PATH", file=sys.stderr)
print("try: sudo apt-get install lsb-release", file=sys.stderr)
sys.exit(1)
@functools.lru_cache(maxsize=1)
def distro_codename():
return subprocess.check_output(["lsb_release", "--codename",
"--short"]).decode().strip()
def check_distro(options):
if options.unsupported or options.quick_check:
return
distro_id = subprocess.check_output(["lsb_release", "--id",
"--short"]).decode().strip()
supported_codenames = ["bionic", "focal", "jammy"]
supported_ids = ["Debian"]
if (distro_codename() not in supported_codenames
and distro_id not in supported_ids):
print(
"WARNING: The following distributions are supported,",
"but distributions not in the list below can also try to install",
"dependencies by passing the `--unsupported` parameter",
"\tUbuntu 18.04 LTS (bionic with EoL April 2028)",
"\tUbuntu 20.04 LTS (focal with EoL April 2030)",
"\tUbuntu 22.04 LTS (jammy with EoL April 2032)",
"\tDebian 10 (buster) or later",
sep="\n",
file=sys.stderr,
)
sys.exit(1)
def check_architecture():
architecture = subprocess.check_output(["uname", "-m"]).decode().strip()
if architecture not in ["i686", "x86_64"]:
print("Only x86 architectures are currently supported", file=sys.stderr)
sys.exit(1)
def check_root():
if os.geteuid() != 0:
print("Running as non-root user.", file=sys.stderr)
print("You might have to enter your password one or more times for 'sudo'.",
file=sys.stderr)
print(file=sys.stderr)
def apt_update(options):
if options.lib32 or options.nacl:
subprocess.check_call(["sudo", "dpkg", "--add-architecture", "i386"])
subprocess.check_call(["sudo", "apt-get", "update"])
# Packages needed for development
def dev_list():
packages = [
"binutils",
"bison",
"bzip2",
"cdbs",
"curl",
"dbus-x11",
"devscripts",
"dpkg-dev",
"elfutils",
"fakeroot",
"flex",
"git-core",
"gperf",
"libasound2-dev",
"libatspi2.0-dev",
"libbrlapi-dev",
"libbz2-dev",
"libc6-dev",
"libcairo2-dev",
"libcap-dev",
"libcups2-dev",
"libcurl4-gnutls-dev",
"libdrm-dev",
"libelf-dev",
"libevdev-dev",
"libffi-dev",
"libgbm-dev",
"libglib2.0-dev",
"libglu1-mesa-dev",
"libgtk-3-dev",
"libkrb5-dev",
"libnspr4-dev",
"libnss3-dev",
"libpam0g-dev",
"libpci-dev",
"libpulse-dev",
"libsctp-dev",
"libspeechd-dev",
"libsqlite3-dev",
"libssl-dev",
"libsystemd-dev",
"libudev-dev",
"libva-dev",
"libwww-perl",
"libxshmfence-dev",
"libxslt1-dev",
"libxss-dev",
"libxt-dev",
"libxtst-dev",
"lighttpd",
"locales",
"openbox",
"p7zip",
"patch",
"perl",
"pkg-config",
"rpm",
"ruby",
"subversion",
"uuid-dev",
"wdiff",
"x11-utils",
"xcompmgr",
"xz-utils",
"zip",
]
# Packages needed for chromeos only
packages += [
"libbluetooth-dev",
"libxkbcommon-dev",
"mesa-common-dev",
"zstd",
]
if package_exists("realpath"):
packages.append("realpath")
if package_exists("libjpeg-dev"):
packages.append("libjpeg-dev")
else:
packages.append("libjpeg62-dev")
if package_exists("libudev1"):
packages.append("libudev1")
else:
packages.append("libudev0")
if package_exists("libbrlapi0.8"):
packages.append("libbrlapi0.8")
elif package_exists("libbrlapi0.7"):
packages.append("libbrlapi0.7")
elif package_exists("libbrlapi0.6"):
packages.append("libbrlapi0.6")
else:
packages.append("libbrlapi0.5")
if package_exists("libav-tools"):
packages.append("libav-tools")
if package_exists("libvulkan-dev"):
packages.append("libvulkan-dev")
if package_exists("libinput-dev"):
packages.append("libinput-dev")
# Cross-toolchain strip is needed for building the sysroots.
if package_exists("binutils-arm-linux-gnueabihf"):
packages.append("binutils-arm-linux-gnueabihf")
if package_exists("binutils-aarch64-linux-gnu"):
packages.append("binutils-aarch64-linux-gnu")
if package_exists("binutils-mipsel-linux-gnu"):
packages.append("binutils-mipsel-linux-gnu")
if package_exists("binutils-mips64el-linux-gnuabi64"):
packages.append("binutils-mips64el-linux-gnuabi64")
# 64-bit systems need a minimum set of 32-bit compat packages for the
# pre-built NaCl binaries.
if "ELF 64-bit" in subprocess.check_output(["file", "-L",
"/sbin/init"]).decode():
packages.extend(["libc6-i386", "lib32stdc++6"])
# lib32gcc-s1 used to be called lib32gcc1 in older distros.
if package_exists("lib32gcc-s1"):
packages.append("lib32gcc-s1")
elif package_exists("lib32gcc1"):
packages.append("lib32gcc1")
return packages
# List of required run-time libraries
def lib_list():
packages = [
"lib32z1",
"libasound2",
"libatk1.0-0",
"libatspi2.0-0",
"libc6",
"libcairo2",
"libcap2",
"libcgi-session-perl",
"libcups2",
"libdrm2",
"libegl1",
"libevdev2",
"libexpat1",
"libfontconfig1",
"libfreetype6",
"libgbm1",
"libglib2.0-0",
"libgl1",
"libgtk-3-0",
"libncurses5",
"libpam0g",
"libpango-1.0-0",
"libpangocairo-1.0-0",
"libpci3",
"libpcre3",
"libpixman-1-0",
"libspeechd2",
"libstdc++6",
"libsqlite3-0",
"libuuid1",
"libwayland-egl1",
"libwayland-egl1-mesa",
"libx11-6",
"libx11-xcb1",
"libxau6",
"libxcb1",
"libxcomposite1",
"libxcursor1",
"libxdamage1",
"libxdmcp6",
"libxext6",
"libxfixes3",
"libxi6",
"libxinerama1",
"libxrandr2",
"libxrender1",
"libxtst6",
"x11-utils",
"xvfb",
"zlib1g",
]
# Run-time libraries required by chromeos only
packages += [
"libpulse0",
"libbz2-1.0",
]
if package_exists("libffi8"):
packages.append("libffi8")
elif package_exists("libffi7"):
packages.append("libffi7")
elif package_exists("libffi6"):
packages.append("libffi6")
if package_exists("libpng16-16"):
packages.append("libpng16-16")
else:
packages.append("libpng12-0")
if package_exists("libnspr4"):
packages.extend(["libnspr4", "libnss3"])
else:
packages.extend(["libnspr4-0d", "libnss3-1d"])
if package_exists("appmenu-gtk"):
packages.append("appmenu-gtk")
if package_exists("libgnome-keyring0"):
packages.append("libgnome-keyring0")
if package_exists("libgnome-keyring-dev"):
packages.append("libgnome-keyring-dev")
if package_exists("libvulkan1"):
packages.append("libvulkan1")
if package_exists("libinput10"):
packages.append("libinput10")
return packages
def lib32_list(options):
if not options.lib32:
print("Skipping 32-bit libraries.", file=sys.stderr)
return []
print("Including 32-bit libraries.", file=sys.stderr)
packages = [
# 32-bit libraries needed for a 32-bit build
# includes some 32-bit libraries required by the Android SDK
# See https://developer.android.com/sdk/installing/index.html?pkg=tools
"libasound2:i386",
"libatk-bridge2.0-0:i386",
"libatk1.0-0:i386",
"libatspi2.0-0:i386",
"libdbus-1-3:i386",
"libegl1:i386",
"libgl1:i386",
"libglib2.0-0:i386",
"libncurses5:i386",
"libnss3:i386",
"libpango-1.0-0:i386",
"libpangocairo-1.0-0:i386",
"libstdc++6:i386",
"libwayland-egl1:i386",
"libx11-xcb1:i386",
"libxcomposite1:i386",
"libxdamage1:i386",
"libxkbcommon0:i386",
"libxrandr2:i386",
"libxtst6:i386",
"zlib1g:i386",
# 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf
"linux-libc-dev:i386",
"libpci3:i386",
]
# When cross building for arm/Android on 64-bit systems the host binaries
# that are part of v8 need to be compiled with -m32 which means
# that basic multilib support is needed.
if "ELF 64-bit" in subprocess.check_output(["file", "-L",
"/sbin/init"]).decode():
# gcc-multilib conflicts with the arm cross compiler but
# g++-X.Y-multilib gives us the 32-bit support that we need. Find out the
# appropriate value of X and Y by seeing what version the current
# distribution's g++-multilib package depends on.
lines = subprocess.check_output(
["apt-cache", "depends", "g++-multilib", "--important"]).decode()
pattern = re.compile(r"g\+\+-[0-9.]+-multilib")
packages += re.findall(pattern, lines)
return packages
# Packages that have been removed from this script. Regardless of configuration
# or options passed to this script, whenever a package is removed, it should be
# added here.
def backwards_compatible_list(options):
if not options.backwards_compatible:
print("Skipping backwards compatible packages.", file=sys.stderr)
return []
print("Including backwards compatible packages.", file=sys.stderr)
packages = [
"7za",
"fonts-indic",
"fonts-ipafont",
"fonts-stix",
"fonts-thai-tlwg",
"fonts-tlwg-garuda",
"g++",
"g++-4.8-multilib-arm-linux-gnueabihf",
"gcc-4.8-multilib-arm-linux-gnueabihf",
"g++-9-multilib-arm-linux-gnueabihf",
"gcc-9-multilib-arm-linux-gnueabihf",
"gcc-arm-linux-gnueabihf",
"g++-10-multilib-arm-linux-gnueabihf",
"gcc-10-multilib-arm-linux-gnueabihf",
"g++-10-arm-linux-gnueabihf",
"gcc-10-arm-linux-gnueabihf",
"git-svn",
"language-pack-da",
"language-pack-fr",
"language-pack-he",
"language-pack-zh-hant",
"libappindicator-dev",
"libappindicator1",
"libappindicator3-1",
"libappindicator3-dev",
"libdconf-dev",
"libdconf1",
"libdconf1:i386",
"libexif-dev",
"libexif12",
"libexif12:i386",
"libgbm-dev",
"libgbm-dev-lts-trusty",
"libgbm-dev-lts-xenial",
"libgconf-2-4:i386",
"libgconf2-dev",
"libgl1-mesa-dev",
"libgl1-mesa-dev-lts-trusty",
"libgl1-mesa-dev-lts-xenial",
"libgl1-mesa-glx:i386",
"libgl1-mesa-glx-lts-trusty:i386",
"libgl1-mesa-glx-lts-xenial:i386",
"libgles2-mesa-dev",
"libgles2-mesa-dev-lts-trusty",
"libgles2-mesa-dev-lts-xenial",
"libgtk-3-0:i386",
"libgtk2.0-0",
"libgtk2.0-0:i386",
"libgtk2.0-dev",
"mesa-common-dev",
"mesa-common-dev-lts-trusty",
"mesa-common-dev-lts-xenial",
"msttcorefonts",
"python-dev",
"python-setuptools",
"snapcraft",
"ttf-dejavu-core",
"ttf-indic-fonts",
"ttf-kochi-gothic",
"ttf-kochi-mincho",
"ttf-mscorefonts-installer",
"xfonts-mathml",
]
if package_exists("python-is-python2"):
packages.extend(["python-is-python2", "python2-dev"])
else:
packages.append("python")
if package_exists("python-crypto"):
packages.append("python-crypto")
if package_exists("python-numpy"):
packages.append("python-numpy")
if package_exists("python-openssl"):
packages.append("python-openssl")
if package_exists("python-psutil"):
packages.append("python-psutil")
if package_exists("python-yaml"):
packages.append("python-yaml")
if package_exists("apache2.2-bin"):
packages.append("apache2.2-bin")
else:
packages.append("apache2-bin")
php_versions = [
("php8.1-cgi", "libapache2-mod-php8.1"),
("php8.0-cgi", "libapache2-mod-php8.0"),
("php7.4-cgi", "libapache2-mod-php7.4"),
("php7.3-cgi", "libapache2-mod-php7.3"),
("php7.2-cgi", "libapache2-mod-php7.2"),
("php7.1-cgi", "libapache2-mod-php7.1"),
("php7.0-cgi", "libapache2-mod-php7.0"),
("php5-cgi", "libapache2-mod-php5"),
]
for php_cgi, mod_php in php_versions:
if package_exists(php_cgi):
packages.extend([php_cgi, mod_php])
break
return [package for package in packages if package_exists(package)]
def arm_list(options):
if not options.arm:
print("Skipping ARM cross toolchain.", file=sys.stderr)
return []
print("Including ARM cross toolchain.", file=sys.stderr)
# arm cross toolchain packages needed to build chrome on armhf
packages = [
"libc6-dev-armhf-cross",
"linux-libc-dev-armhf-cross",
"g++-arm-linux-gnueabihf",
]
# Work around for dependency issue Ubuntu: http://crbug.com/435056
if distro_codename() == "bionic":
packages.extend([
"g++-5-multilib-arm-linux-gnueabihf",
"gcc-5-multilib-arm-linux-gnueabihf",
"gcc-arm-linux-gnueabihf",
])
elif distro_codename() == "focal":
packages.extend([
"g++-10-multilib-arm-linux-gnueabihf",
"gcc-10-multilib-arm-linux-gnueabihf",
"gcc-arm-linux-gnueabihf",
])
elif distro_codename() == "jammy":
packages.extend([
"gcc-arm-linux-gnueabihf",
"g++-11-arm-linux-gnueabihf",
"gcc-11-arm-linux-gnueabihf",
])
return packages
def nacl_list(options):
if not options.nacl:
print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies.",
file=sys.stderr)
return []
print("Including NaCl, NaCl toolchain, NaCl ports dependencies.",
file=sys.stderr)
packages = [
"g++-mingw-w64-i686",
"lib32z1-dev",
"libasound2:i386",
"libcap2:i386",
"libelf-dev:i386",
"libfontconfig1:i386",
"libglib2.0-0:i386",
"libgpm2:i386",
"libncurses5:i386",
"lib32ncurses5-dev",
"libnss3:i386",
"libpango-1.0-0:i386",
"libssl-dev:i386",
"libtinfo-dev",
"libtinfo-dev:i386",
"libtool",
"libuuid1:i386",
"libxcomposite1:i386",
"libxcursor1:i386",
"libxdamage1:i386",
"libxi6:i386",
"libxrandr2:i386",
"libxss1:i386",
"libxtst6:i386",
"texinfo",
"xvfb",
# Packages to build NaCl, its toolchains, and its ports.
"ant",
"autoconf",
"bison",
"cmake",
"gawk",
"intltool",
"xutils-dev",
"xsltproc",
]
# Some package names have changed over time
if package_exists("libssl-dev"):
packages.append("libssl-dev:i386")
elif package_exists("libssl1.1"):
packages.append("libssl1.1:i386")
elif package_exists("libssl1.0.2"):
packages.append("libssl1.0.2:i386")
else:
packages.append("libssl1.0.0:i386")
if package_exists("libtinfo5"):
packages.append("libtinfo5")
if package_exists("libudev1"):
packages.append("libudev1:i386")
else:
packages.append("libudev0:i386")
return packages
# Debian is in the process of transitioning to automatic debug packages, which
# have the -dbgsym suffix (https://wiki.debian.org/AutomaticDebugPackages).
# Untransitioned packages have the -dbg suffix. And on some systems, neither
# will be available, so exclude the ones that are missing.
def dbg_package_name(package):
if package_exists(package + "-dbgsym"):
return [package + "-dbgsym"]
if package_exists(package + "-dbg"):
return [package + "-dbg"]
return []
def dbg_list(options):
if not options.syms:
print("Skipping debugging symbols.", file=sys.stderr)
return []
print("Including debugging symbols.", file=sys.stderr)
packages = [
dbg_package for package in lib_list()
for dbg_package in dbg_package_name(package)
]
# Debugging symbols packages not following common naming scheme
if not dbg_package_name("libstdc++6"):
for version in ["8", "7", "6", "5", "4.9", "4.8", "4.7", "4.6"]:
if package_exists("libstdc++6-%s-dbg" % version):
packages.append("libstdc++6-%s-dbg" % version)
break
if not dbg_package_name("libatk1.0-0"):
packages.extend(dbg_package_name("libatk1.0"))
if not dbg_package_name("libpango-1.0-0"):
packages.extend(dbg_package_name("libpango1.0-dev"))
return packages
def package_list(options):
packages = (dev_list() + lib_list() + dbg_list(options) +
lib32_list(options) + arm_list(options) + nacl_list(options) +
backwards_compatible_list(options))
# Sort all the :i386 packages to the front, to avoid confusing dpkg-query
# (https://crbug.com/446172).
return sorted(set(packages), key=lambda x: (not x.endswith(":i386"), x))
def missing_packages(packages):
try:
subprocess.run(
["dpkg-query", "-W", "-f", " "] + packages,
check=True,
capture_output=True,
).decode()
return []
except subprocess.CalledProcessError as e:
return [line.split(" ")[-1] for line in e.stderr.strip().splitlines()]
def package_is_installable(package):
result = subprocess.run(["apt-cache", "show", package],
capture_output=True).decode()
return result.returncode == 0
def quick_check(options):
if not options.quick_check:
return
missing = missing_packages(package_list(options))
if not missing:
sys.exit(0)
not_installed = []
unknown = []
for p in missing:
if package_is_installable(p):
not_installed.append(p)
else:
unknown.append(p)
if not_installed:
print("WARNING: The following packages are not installed:", file=sys.stderr)
print(" ".join(not_installed), file=sys.stderr)
if unknown:
print("WARNING: The following packages are unknown to your system",
file=sys.stderr)
print("(maybe missing a repo or need to 'sudo apt-get update'):",
file=sys.stderr)
print(" ".join(unknown), file=sys.stderr)
sys.exit(1)
def find_missing_packages(options):
print("Finding missing packages...", file=sys.stderr)
packages = package_list(options)
packages_str = " ".join(packages)
print("Packages required: " + packages_str, file=sys.stderr)
query_cmd = ["apt-get", "--just-print", "install"] + packages
env = os.environ.copy()
env["LANGUAGE"] = "en"
env["LANG"] = "C"
cmd_output = subprocess.check_output(query_cmd, env=env).decode()
lines = cmd_output.splitlines()
install = []
for pattern in (
"The following NEW packages will be installed:",
"The following packages will be upgraded:",
):
if pattern in lines:
for line in lines[lines.index(pattern) + 1:]:
if not line.startswith(" "):
break
install += line.strip().split(" ")
return install
def install_packages(options):
try:
packages = find_missing_packages(options)
if packages:
quiet = ["-qq", "--assume-yes"] if options.no_prompt else []
subprocess.check_call(["sudo", "apt-get", "install"] + quiet + packages)
print(file=sys.stderr)
else:
print("No missing packages, and the packages are up to date.",
file=sys.stderr)
except subprocess.CalledProcessError as e:
# An apt-get exit status of 100 indicates that a real error has occurred.
print("`apt-get --just-print install ...` failed", file=sys.stderr)
print("It produced the following output:", file=sys.stderr)
print(e.output.decode(), file=sys.stderr)
print(file=sys.stderr)
print("You will have to install the above packages yourself.",
file=sys.stderr)
print(file=sys.stderr)
sys.exit(100)
# Install the Chrome OS default fonts. This must go after running
# apt-get, since install-chromeos-fonts depends on curl.
def install_chromeos_fonts(options):
if not options.chromeos_fonts:
print("Skipping installation of Chrome OS fonts.", file=sys.stderr)
return
print("Installing Chrome OS fonts.", file=sys.stderr)
dir = os.path.abspath(os.path.dirname(__file__))
try:
subprocess.check_call(
["sudo",
os.path.join(dir, "linux", "install-chromeos-fonts.py")])
except subprocess.CalledProcessError:
print("ERROR: The installation of the Chrome OS default fonts failed.",
file=sys.stderr)
if (subprocess.check_output(
["stat", "-f", "-c", "%T", dir], ).decode().startswith("nfs")):
print(
"The reason is that your repo is installed on a remote file system.",
file=sys.stderr)
else:
print(
"This is expected if your repo is installed on a remote file system.",
file=sys.stderr)
print("It is recommended to install your repo on a local file system.",
file=sys.stderr)
print("You can skip the installation of the Chrome OS default fonts with",
file=sys.stderr)
print("the command line option: --no-chromeos-fonts.", file=sys.stderr)
sys.exit(1)
def install_locales():
print("Installing locales.", file=sys.stderr)
CHROMIUM_LOCALES = [
"da_DK.UTF-8", "fr_FR.UTF-8", "he_IL.UTF-8", "zh_TW.UTF-8"
]
LOCALE_GEN = "/etc/locale.gen"
if os.path.exists(LOCALE_GEN):
old_locale_gen = open(LOCALE_GEN).read()
for locale in CHROMIUM_LOCALES:
subprocess.check_call(
["sudo", "sed", "-i",
"s/^# %s/%s/" % (locale, locale), LOCALE_GEN])
# Regenerating locales can take a while, so only do it if we need to.
locale_gen = open(LOCALE_GEN).read()
if locale_gen != old_locale_gen:
subprocess.check_call(["sudo", "locale-gen"])
else:
print("Locales already up-to-date.", file=sys.stderr)
else:
for locale in CHROMIUM_LOCALES:
subprocess.check_call(["sudo", "locale-gen", locale])
def main():
options = parse_args(sys.argv[1:])
check_lsb_release()
check_distro(options)
check_architecture()
quick_check(options)
check_root()
apt_update(options)
install_packages(options)
install_chromeos_fonts(options)
install_locales()
return 0
if __name__ == "__main__":
sys.exit(main())