#!/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 on Arch Linux # including items requiring sudo privileges. import argparse import functools import logging import os import shutil import subprocess import sys logging.basicConfig(stream=sys.stderr, level=logging.INFO, format='%(name)s [%(levelname)s]: %(message)s') logger = logging.getLogger(os.path.basename(sys.argv[0])) @functools.lru_cache(maxsize=1) def build_pacman_package_list(): logger.info("Building pacman package list.") output = subprocess.check_output(["pacman", "-Q"]).decode() return set(line.split()[0] for line in output.strip().splitlines()) def package_exists_in_repos(package_name: str) -> bool: """Check if package exists in repositories""" result = subprocess.run(["pacman", "-Si", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return result.returncode == 0 def package_exists(package_name: str) -> bool: # Check if package is installed if package_name in build_pacman_package_list(): return True # Check if package exists in repos return package_exists_in_repos(package_name) def parse_args(argv): parser = argparse.ArgumentParser( description="Install Chromium build dependencies on Arch Linux.") 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("--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("--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( "--aur-helper", default="yay", help="AUR helper to use (default: yay)", ) options = parser.parse_args(argv) if options.arm: options.lib32 = True return options def check_arch_linux(): try: with open("/etc/os-release") as f: content = f.read() if "ID=arch" not in content and "ID_LIKE=arch" not in content: logger.error("This script is designed for Arch Linux and derivatives") sys.exit(1) except FileNotFoundError: logger.error("Cannot determine OS. This script is for Arch Linux.") sys.exit(1) def check_architecture(): architecture = subprocess.check_output(["uname", "-m"]).decode().strip() if architecture not in ["i686", "x86_64", 'aarch64']: logger.error("Only x86 and ARM64 architectures are currently supported") sys.exit(1) def check_root(): if os.geteuid() != 0: logger.info("Running as non-root user.") logger.info( "You might have to enter your password one or more times for 'sudo'.\n") def check_aur_helper(options): if not shutil.which(options.aur_helper): logger.error(f"AUR helper '{options.aur_helper}' not found.") logger.error("Please install an AUR helper like 'yay' or 'paru'.") logger.error("You can install yay with:") logger.error(" git clone https://aur.archlinux.org/yay.git") logger.error(" cd yay && makepkg -si") sys.exit(1) def pacman_update(options): if options.lib32: # Enable multilib repository logger.info("Checking multilib repository...") with open("/etc/pacman.conf", "r") as f: config = f.read() if "[multilib]" not in config or config.find("[multilib]") > config.find("#[multilib]"): logger.info("Enabling multilib repository...") subprocess.check_call([ "sudo", "sed", "-i", "/^#\\[multilib\\]/,/^#Include = \\/etc\\/pacman.d\\/mirrorlist/ s/^#//", "/etc/pacman.conf" ]) subprocess.check_call(["sudo", "pacman", "-Sy"]) # Core packages needed for Chromium development - verified Arch package names only def get_core_packages(): """Return core packages that definitely exist in Arch repos""" return [ # Build essentials "base-devel", "git", "python", "curl", "wget", "unzip", "zip", "p7zip", "bzip2", "xz", "patch", "perl", "ruby", "nodejs", "npm", "bison", "flex", "gperf", "pkgconf", "nasm", "yasm", # Core libraries "alsa-lib", "at-spi2-core", "cairo", "cups", "dbus", "expat", "fontconfig", "freetype2", "glib2", "gtk3", "krb5", "libcap", "libdrm", "libelf", "libevdev", "libffi", "mesa", "libglvnd", "libice", "libinput", "libjpeg-turbo", "libpng", "libpulse", "libsm", "libx11", "libxau", "libxcb", "libxcomposite", "libxcursor", "libxdamage", "libxdmcp", "libxext", "libxfixes", "libxi", "libxinerama", "libxrandr", "libxrender", "libxshmfence", "libxt", "libxtst", "nspr", "nss", "pam", "pango", "sqlite", "systemd-libs", "util-linux", "vulkan-headers", "vulkan-icd-loader", "wayland", "xorg-server", "xorg-server-xvfb", "zlib", ] def get_optional_packages(): """Return optional packages - only add if they exist""" candidates = [ "bluez-libs", "speech-dispatcher", "libxss", # X11 screensaver "pciutils", # PCI utilities "xcompmgr", # Composite manager "fd", # Find alternative "ripgrep", # Fast grep "lighttpd", # Web server "openbox", # Window manager ] packages = [] for pkg in candidates: if package_exists_in_repos(pkg): packages.append(pkg) else: logger.debug(f"Optional package '{pkg}' not found, skipping.") return packages def dev_list(): """Get development packages""" packages = get_core_packages() + get_optional_packages() return packages def lib_list(): """Get runtime library packages""" packages = [ "atk", "at-spi2-atk", "cairo", "cups", "dbus", "expat", "fontconfig", "freetype2", "glib2", "gtk3", "libdrm", "libglvnd", "libpulse", "libx11", "libxcb", "libxcomposite", "libxcursor", "libxdamage", "libxext", "libxfixes", "libxi", "libxinerama", "libxrandr", "libxrender", "libxtst", "mesa", "pango", "wayland", "zlib", ] return packages def lib32_list(options): if not options.lib32: logger.info("Skipping 32-bit libraries.") return [] logger.info("Including 32-bit libraries.") packages = [ "lib32-alsa-lib", "lib32-atk", "lib32-cairo", "lib32-dbus", "lib32-expat", "lib32-fontconfig", "lib32-freetype2", "lib32-gcc-libs", "lib32-glib2", "lib32-glibc", "lib32-gtk3", "lib32-libdrm", "lib32-libglvnd", "lib32-libx11", "lib32-libxcb", "lib32-libxcomposite", "lib32-libxcursor", "lib32-libxdamage", "lib32-libxext", "lib32-libxfixes", "lib32-libxi", "lib32-libxinerama", "lib32-libxrandr", "lib32-libxrender", "lib32-libxtst", "lib32-mesa", "lib32-nspr", "lib32-nss", "lib32-pango", "lib32-zlib", ] return packages def arm_list(options): if not options.arm: logger.info("Skipping ARM cross toolchain.") return [] logger.info("Including ARM cross toolchain.") # ARM cross toolchain packages (these are AUR packages) packages = [ "arm-linux-gnueabihf-gcc", "arm-linux-gnueabihf-glibc", "arm-linux-gnueabihf-binutils", "arm-linux-gnueabihf-linux-api-headers", ] return packages def dbg_list(options): if not options.syms: logger.info("Skipping debugging symbols.") return [] logger.info("Including debugging symbols.") # Arch doesn't have separate debug packages like Debian/Ubuntu logger.info("Note: Debug symbols in Arch are typically included in packages") logger.info("or available through debug repositories. No additional packages needed.") return [] def package_list(options): packages = (dev_list() + lib_list() + dbg_list(options) + lib32_list(options)) # ARM packages are from AUR, handle separately arm_packages = arm_list(options) if options.arm else [] return list(set(packages)), arm_packages def missing_packages(packages): installed = build_pacman_package_list() missing = [] for pkg in packages: if pkg not in installed: # Double-check that the package actually exists in repos if package_exists_in_repos(pkg): missing.append(pkg) else: logger.warning(f"Package '{pkg}' does not exist in repositories, skipping.") return missing def quick_check(options): if not options.quick_check: return regular_packages, aur_packages = package_list(options) missing_regular = missing_packages(regular_packages) missing_aur = missing_packages(aur_packages) if not missing_regular and not missing_aur: sys.exit(0) if missing_regular: logger.warning("The following official packages are not installed:") logger.warning(" ".join(missing_regular)) if missing_aur: logger.warning("The following AUR packages are not installed:") logger.warning(" ".join(missing_aur)) sys.exit(1) def install_packages(options): regular_packages, aur_packages = package_list(options) # Install regular packages with pacman missing_regular = missing_packages(regular_packages) if missing_regular: logger.info(f"Installing {len(missing_regular)} regular packages...") cmd = ["sudo", "pacman", "-S", "--needed"] if options.no_prompt: cmd.append("--noconfirm") subprocess.check_call(cmd + missing_regular) else: logger.info("All regular packages are already installed.") # Install AUR packages missing_aur = missing_packages(aur_packages) if missing_aur: logger.info(f"Installing {len(missing_aur)} AUR packages...") cmd = [options.aur_helper, "-S", "--needed"] if options.no_prompt: cmd.append("--noconfirm") subprocess.check_call(cmd + missing_aur) else: if aur_packages: logger.info("All AUR packages are already installed.") def install_chromeos_fonts(options): if not options.chromeos_fonts: logger.info("Skipping installation of Chrome OS fonts.") return logger.info("Installing Chrome OS fonts.") # Check if we have the font installation script script_dir = os.path.abspath(os.path.dirname(__file__)) font_script = os.path.join(script_dir, "linux", "install-chromeos-fonts.py") if os.path.exists(font_script): try: subprocess.check_call(["sudo", font_script]) except subprocess.CalledProcessError: logger.error("The installation of the Chrome OS default fonts failed.") logger.info("You can skip this with --no-chromeos-fonts.") else: logger.warning("Chrome OS font installation script not found.") logger.info("This is normal if you're not building from the full Chromium source tree.") def install_locales(): logger.info("Installing locales.") # Arch Linux locale setup is different from Debian/Ubuntu CHROMIUM_LOCALES = [ "da_DK.UTF-8", "en_US.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): with open(LOCALE_GEN, 'r') as f: current_config = f.read() needs_update = False for locale in CHROMIUM_LOCALES: if f"#{locale}" in current_config and f"\n{locale}" not in current_config: subprocess.check_call([ "sudo", "sed", "-i", f"s/^#{locale}/{locale}/", LOCALE_GEN ]) needs_update = True if needs_update: subprocess.check_call(["sudo", "locale-gen"]) else: logger.info("Locales already up-to-date.") else: logger.warning("locale.gen not found. Locales may need manual configuration.") def main(): options = parse_args(sys.argv[1:]) check_arch_linux() check_architecture() quick_check(options) check_root() check_aur_helper(options) pacman_update(options) install_packages(options) install_chromeos_fonts(options) install_locales() logger.info("Chromium build dependencies installation complete!") return 0 if __name__ == "__main__": sys.exit(main())