Files
BPI-CM9-Linux/amlbian/scripts/chromium/build-chromium.sh
T

486 lines
21 KiB
Bash
Raw Normal View History

2026-11-11 08:50:43 +08:00
#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2026 AMLOGIC
#
# Chromium browser build script for AMLbian
# Hardware video decoding (V4L2) + DMA-BUF zero-copy
#
# Controlled by BUILD_UNITS: add "chromium" to enable
# NOTE: set -e is NOT used here. This script modifies PATH/env and runs in a
# subshell (line ~450) to isolate side-effects from the parent build system.
# Error handling is explicit (|| return 1) throughout.
# ==============================================================================
# Section 1: Default Configuration
# ==============================================================================
CHROMIUM_VERSION="132.0.6834.83"
# -amlN: bump whenever the package content changes
DEB_VERSION="${CHROMIUM_VERSION}-aml2"
DEB_NAME="chromium-browser-aml"
# Get script directory (scripts/chromium/ — two levels below AMLBIAN_DIR)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AMLBIAN_DIR="$(dirname "$(dirname "${SCRIPT_DIR}")")"
PATCHES_DIR="${SCRIPT_DIR}/patches"
OVERLAY_DIR="${SCRIPT_DIR}/overlay"
CONFIG_DIR="${SCRIPT_DIR}/config"
: "${CHROMIUM_BUILD_ROOT:=${AMLBIAN_DIR}/../chromium}"
: "${DEPOT_TOOLS_DIR:=${CHROMIUM_BUILD_ROOT}/depot_tools}"
# CHROMIUM_DL_DIR: optional env var (Jenkins node config or manual).
# Tarball search order: CHROMIUM_DL_DIR → CHROMIUM_MIRROR_DIR → local cache → CDN.
: "${CHROMIUM_MIRROR_DIR:=/mnt/fileroot/Public/mirror}"
CHROMIUM_SRC="${CHROMIUM_BUILD_ROOT}/chromium-${CHROMIUM_VERSION}"
OUT_DIR="${CHROMIUM_SRC}/out/arm64"
# Armbian sets SRC to the build dir. BUILD_DEBS must match ${SRC}/output/
# which is where install_aml_deb looks for debs in Docker.
if [[ -n "${SRC}" ]] && [[ -d "${SRC}/output" ]]; then
BUILD_DEBS="${SRC}/output"
else
BUILD_DEBS="${AMLBIAN_DIR}/../output"
fi
: "${NINJA_JOBS:=$(nproc)}"
# Fallback display_alert for standalone testing
if ! command -v display_alert &>/dev/null; then
display_alert() { echo "[$2] $1 ${3:+($3)}"; }
fi
# ==============================================================================
# Section 2: Internal Helper
# ==============================================================================
# Find a file in multiple directories. Sets REPLY to the first hit.
function __chromium_find_in_dirs() {
local filename="$1"; shift
REPLY=""
local dir
for dir in "$@"; do
[[ -z "${dir}" ]] && continue
if [[ -f "${dir}/${filename}" ]]; then
REPLY="${dir}/${filename}"
return 0
fi
done
return 1
}
# ==============================================================================
# Section 3: Build Functions
# ==============================================================================
function __chromium_check_enabled() {
# When running standalone (no BUILD_UNITS), always build
if ! command -v is_unit_enabled &>/dev/null; then
return 0
fi
if ! is_unit_enabled "chromium"; then
display_alert "CHROMIUM" "Not in BUILD_UNITS, skipping" "info"
return 1
fi
return 0
}
function __chromium_fetch_source() {
display_alert "CHROMIUM" "Fetching source ${CHROMIUM_VERSION}" "info"
mkdir -p "${CHROMIUM_BUILD_ROOT}"
# If source dir exists but is incomplete (any state: empty, only out/, partial),
# remove it so we get a clean extraction.
if [[ -d "${CHROMIUM_SRC}" ]] && [[ ! -f "${CHROMIUM_SRC}/.gn" ]]; then
display_alert "CHROMIUM" "Incomplete source tree at ${CHROMIUM_SRC} — removing" "wrn"
rm -rf "${CHROMIUM_SRC}"
fi
# Source already complete
if [[ -f "${CHROMIUM_SRC}/.gn" ]]; then
display_alert "CHROMIUM" "Source complete at ${CHROMIUM_SRC}" "info"
return 0
fi
# Find tarball: CHROMIUM_DL_DIR (Jenkins) → mirror → local cache
local tarball="chromium-${CHROMIUM_VERSION}.tar.xz"
local tarball_path=""
if __chromium_find_in_dirs "${tarball}" \
"${CHROMIUM_DL_DIR}" \
"${CHROMIUM_MIRROR_DIR}" \
"${CHROMIUM_BUILD_ROOT}"; then
tarball_path="${REPLY}"
display_alert "CHROMIUM" "Found tarball: ${tarball_path}" "info"
else
# Last resort: download from Google CDN
tarball_path="${CHROMIUM_BUILD_ROOT}/${tarball}"
display_alert "CHROMIUM" "Downloading ${tarball} from Google CDN (6.4 GB) ..." "info"
if ! wget -q --show-progress \
"https://commondatastorage.googleapis.com/chromium-browser-official/${tarball}" \
-O "${tarball_path}"; then
display_alert "CHROMIUM" "ERROR: download failed" "err"
rm -f "${tarball_path}"
return 1
fi
fi
display_alert "CHROMIUM" "Extracting ${tarball_path} ..." "info"
if ! tar -xf "${tarball_path}" -C "${CHROMIUM_BUILD_ROOT}"; then
display_alert "CHROMIUM" "ERROR: tar extraction failed" "err"
rm -rf "${CHROMIUM_SRC}"
return 1
fi
if [[ ! -f "${CHROMIUM_SRC}/.gn" ]]; then
display_alert "CHROMIUM" "ERROR: .gn missing after extraction (CHROMIUM_SRC=${CHROMIUM_SRC})" "err"
return 1
fi
display_alert "CHROMIUM" "Source ready at ${CHROMIUM_SRC}" "info"
}
function __chromium_apply_patches() {
display_alert "CHROMIUM" "Applying patches" "info"
cd "${CHROMIUM_SRC}" || { display_alert "CHROMIUM" "ERROR: Cannot cd to ${CHROMIUM_SRC}" "err"; return 1; }
# Apply patches in order (patch -p1, no git repo); -F0 -f: exact context only, never auto-reverse.
# Only "already applied" is skipped; a missing or non-applying patch fails the build.
for p in minimal-hwdecode-final.patch wayland-disable-implicit-sync-interop.patch \
mpris-skip-infinite-duration-position.patch; do
if patch -p1 -F0 -f --dry-run -s < "${PATCHES_DIR}/${p}" > /dev/null 2>&1; then
patch -p1 -F0 -f < "${PATCHES_DIR}/${p}" || \
{ display_alert "CHROMIUM" "ERROR: patch failed: ${p}" "err"; return 1; }
display_alert "CHROMIUM" "Patch applied: ${p}" "info"
elif patch -p1 -R -F0 -f --dry-run -s < "${PATCHES_DIR}/${p}" > /dev/null 2>&1; then
display_alert "CHROMIUM" "Patch already applied, skipping: ${p}" "info"
else
display_alert "CHROMIUM" "ERROR: patch does not apply: ${p}" "err"
patch -p1 -F0 -f --dry-run < "${PATCHES_DIR}/${p}" 2>&1 | head -40 || true
return 1
fi
done
# Copy overlay files
if [[ -d "${OVERLAY_DIR}" ]]; then
cp -rv "${OVERLAY_DIR}/." "${CHROMIUM_SRC}/"
display_alert "CHROMIUM" "Overlay applied" "info"
fi
}
function __chromium_configure() {
display_alert "CHROMIUM" "Configuring build" "info"
# Force system Python — depot_tools bundled Python has wrong prefix (/work/out)
export PATH="/usr/bin:${DEPOT_TOOLS_DIR}:${PATH}"
unset PYTHONHOME PYTHONPATH
export GN_ARGS_USE_SYSTEM_PYTHON=1
export VPYTHON_BYPASS="manually managed vpython not supported by nickel"
export DEPOT_TOOLS_UPDATE=0
cd "${CHROMIUM_SRC}" || {
display_alert "CHROMIUM" "ERROR: Cannot cd to ${CHROMIUM_SRC}" "err"
return 1
}
# Resolve canonical path — CHROMIUM_SRC may contain ".." when sourced by Armbian
# (e.g. amlbian/../chromium/...). gn fails to find .gn with ".." in OUT_DIR.
local src_abs
src_abs="$(pwd)"
local out_abs="${src_abs}/out/arm64"
if [[ ! -f "${src_abs}/.gn" ]]; then
display_alert "CHROMIUM" "ERROR: .gn not found at ${src_abs} — source tree missing" "err"
return 1
fi
# Toolchains: clang, rust, sysroot.
# The source tarball doesn't include these. Strategy:
# 1. Reuse from existing build tree at ${CHROMIUM_BUILD_ROOT}/src/ (same chromium version)
# 2. Download from Google CDN via update scripts
local existing_src="${CHROMIUM_BUILD_ROOT}/src"
local clang_dir="${src_abs}/third_party/llvm-build"
local rust_dir="${src_abs}/third_party/rust-toolchain"
local sysroot_dir="${src_abs}/build/linux/debian_bullseye_arm64-sysroot"
# Expected clang revision (used to verify correct version)
local expected_clang_rev
expected_clang_rev=$(grep "^CLANG_REVISION = " "${src_abs}/tools/clang/scripts/update.py" | cut -d"'" -f2)
# --- Clang ---
if ! grep -q "${expected_clang_rev}" "${clang_dir}/Release+Asserts/cr_build_revision" 2>/dev/null; then
if [[ -d "${existing_src}/third_party/llvm-build/Release+Asserts/bin" ]] && \
grep -q "${expected_clang_rev}" "${existing_src}/third_party/llvm-build/Release+Asserts/cr_build_revision" 2>/dev/null; then
display_alert "CHROMIUM" "Linking clang from existing build" "info"
rm -rf "${clang_dir}"
ln -snf "${existing_src}/third_party/llvm-build" "${clang_dir}"
else
display_alert "CHROMIUM" "Downloading clang via update.py" "info"
python3 "${src_abs}/tools/clang/scripts/update.py" || {
display_alert "CHROMIUM" "ERROR: clang setup failed" "err"; return 1; }
fi
fi
# --- Rust ---
if [[ ! -x "${rust_dir}/bin/rustc" ]]; then
if [[ -x "${existing_src}/third_party/rust-toolchain/bin/rustc" ]]; then
display_alert "CHROMIUM" "Linking rust from existing build" "info"
rm -rf "${rust_dir}"
ln -snf "${existing_src}/third_party/rust-toolchain" "${rust_dir}"
else
display_alert "CHROMIUM" "Downloading rust via update_rust.py" "info"
python3 "${src_abs}/tools/rust/update_rust.py" || {
display_alert "CHROMIUM" "ERROR: rust setup failed" "err"; return 1; }
fi
fi
# --- Sysroot (arm64 cross-compilation) ---
if [[ ! -d "${sysroot_dir}" ]]; then
if [[ -d "${existing_src}/build/linux/debian_bullseye_arm64-sysroot" ]]; then
display_alert "CHROMIUM" "Linking arm64 sysroot from existing build" "info"
ln -snf "${existing_src}/build/linux/debian_bullseye_arm64-sysroot" "${sysroot_dir}"
else
display_alert "CHROMIUM" "Installing arm64 sysroot via install-sysroot.py" "info"
python3 "${src_abs}/build/linux/sysroot_scripts/install-sysroot.py" --arch=arm64 || {
display_alert "CHROMIUM" "ERROR: sysroot installation failed" "err"; return 1; }
fi
fi
mkdir -p "${out_abs}"
cp "${CONFIG_DIR}/args.gn" "${out_abs}/args.gn"
local gn_bin="${src_abs}/buildtools/linux64/gn"
if [[ ! -x "${gn_bin}" ]]; then
display_alert "CHROMIUM" "ERROR: gn not found at ${gn_bin}" "err"
return 1
fi
"${gn_bin}" gen "${out_abs}" || {
display_alert "CHROMIUM" "ERROR: gn gen failed" "err"
return 1
}
display_alert "CHROMIUM" "Configured: ${out_abs}" "info"
}
function __chromium_compile() {
display_alert "CHROMIUM" "Building (jobs=${NINJA_JOBS})" "info"
cd "${CHROMIUM_SRC}" || { display_alert "CHROMIUM" "ERROR: Cannot cd to ${CHROMIUM_SRC}" "err"; return 1; }
local src_abs
src_abs="$(pwd)"
"${src_abs}/third_party/ninja/ninja" -C "${src_abs}/out/arm64" -j "${NINJA_JOBS}" chrome || {
display_alert "CHROMIUM" "ERROR: ninja build failed" "err"
return 1
}
display_alert "CHROMIUM" "Build complete" "info"
}
function __chromium_package_deb() {
display_alert "CHROMIUM" "Packaging ${DEB_NAME} ${DEB_VERSION}" "info"
mkdir -p "${BUILD_DEBS}"
local pkg_root="${BUILD_DEBS}/.chromium-pkg-$$"
rm -rf "${pkg_root}"
mkdir -p "${pkg_root}"
local install_dir="${pkg_root}/usr/lib/chromium"
local bin_dir="${pkg_root}/usr/bin"
local app_dir="${pkg_root}/usr/share/applications"
local limits_dir="${pkg_root}/etc/security/limits.d"
local profile_dir="${pkg_root}/etc/profile.d"
mkdir -p "${install_dir}/locales" "${bin_dir}" "${app_dir}" \
"${limits_dir}" "${profile_dir}"
local src
src="$(realpath "${OUT_DIR}")"
display_alert "CHROMIUM" "Packaging from: ${src}" "info"
# Binary + resources
if [[ ! -f "${src}/chrome" ]]; then
display_alert "CHROMIUM" "ERROR: ${src}/chrome not found!" "err"
return 1
fi
install -m 755 "${src}/chrome" "${install_dir}/chrome"
for f in "${src}"/icudtl.dat "${src}"/*.pak "${src}"/*.bin; do
[[ -f "${f}" ]] && install -m 644 "${f}" "${install_dir}/"
done
# ANGLE libraries (required for GPU process)
for f in "${src}"/libGLESv2.so "${src}"/libEGL.so; do
[[ -f "${f}" ]] && install -m 644 "${f}" "${install_dir}/"
done
# Crashpad handler
[[ -f "${src}/chrome_crashpad_handler" ]] && \
install -m 755 "${src}/chrome_crashpad_handler" "${install_dir}/"
[[ -f "${src}/locales/en-US.pak" ]] && \
install -m 644 "${src}/locales/en-US.pak" "${install_dir}/locales/"
# Launcher. The fence wait switch (HasSwitch() in patches/wayland-disable-implicit-sync-interop.patch)
# fixes tearing on Mali. Video drops: no SingleVideoFrameRateThrottling, 3 buffers instead of
# --double-buffer-compositing. HandleChildThreadTypeChangesInBrowser: see chromium-nice.conf below.
cat > "${bin_dir}/chromium-browser" << 'LAUNCHER'
#!/bin/sh
export PULSE_SERVER=unix:/run/user/1000/pulse/native
ulimit -l unlimited 2>/dev/null
exec /usr/lib/chromium/chrome \
--ozone-platform=wayland --no-sandbox --use-gl=angle --use-angle=gles \
--ignore-gpu-blocklist --disable-gpu-process-crash-limit \
--disable-wayland-implicit-sync-interop \
--disable-features=WebGPU,SingleVideoFrameRateThrottling,HandleChildThreadTypeChangesInBrowser \
--enable-features=AcceleratedVideoDecodeLinux,AcceleratedVideoDecodeLinuxGL,ChromeOSHWAV1Decoder \
--disable-partial-raster --lang=en-US \
"$@" 2>/tmp/chrome_stderr.log
LAUNCHER
chmod 755 "${bin_dir}/chromium-browser"
# Desktop entry
cat > "${app_dir}/chromium-browser.desktop" << 'DESKTOP'
[Desktop Entry]
Version=1.0
Name=Chromium Browser
Exec=chromium-browser %U
Terminal=false
Type=Application
Icon=chromium-browser
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;
DESKTOP
# memlock limit
echo "* - memlock unlimited" > "${limits_dir}/chromium-memlock.conf"
# Chromium renices its display threads to -8 in each process (platform_thread_linux.cc); the browser
# cannot do it for children without CONFIG_PID_NS, hence HandleChildThreadTypeChangesInBrowser off.
# Desktop users only (lib/function-img.sh creates the user in the video group).
echo "@video - nice -8" > "${limits_dir}/chromium-nice.conf"
# HW decode sysfs setup (runs at login)
cat > "${profile_dir}/aml-hwdecode.sh" << 'HWDEC'
#!/bin/sh
HWDEC
chmod 644 "${profile_dir}/aml-hwdecode.sh"
# Systemd service for DW mode + GPU freq (needs root)
local svc_dir="${pkg_root}/etc/systemd/system"
mkdir -p "${svc_dir}"
cat > "${svc_dir}/chromium-hwdecode.service" << 'SVC'
[Unit]
Description=Chromium HW decode setup (DW mode + GPU freq)
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
SVC
# Enable service via postinst
mkdir -p "${pkg_root}/DEBIAN"
cat > "${pkg_root}/DEBIAN/postinst" << 'POSTINST'
#!/bin/sh
systemctl daemon-reload
systemctl enable chromium-hwdecode.service
systemctl start chromium-hwdecode.service 2>/dev/null || true
POSTINST
chmod 755 "${pkg_root}/DEBIAN/postinst"
# DEBIAN control
cat > "${pkg_root}/DEBIAN/control" << EOF
Package: ${DEB_NAME}
Version: ${DEB_VERSION}
Architecture: arm64
Maintainer: AMLbian <haijun.li@amlogic.com>
Depends: libc6, libglib2.0-0, libnss3, libatk1.0-0, libatk-bridge2.0-0, libcups2, libdrm2, libgbm1, libpango-1.0-0, libcairo2, libasound2, libxkbcommon0, libwayland-client0, pipewire-pulse | pulseaudio, aml-libgles-eabihf-avalon-wayland-drm
Description: Chromium ${CHROMIUM_VERSION} with AML HW video decode
V4L2 hardware decoding with DMA-BUF zero-copy for Amlogic SoCs.
Supports H.264, VP9, HEVC up to 4K@60fps.
EOF
# Build deb
mkdir -p "${BUILD_DEBS}"
local deb_file="${BUILD_DEBS}/${DEB_NAME}_${DEB_VERSION}_arm64.deb"
# Written as .part and renamed when complete, so a cut-off dpkg-deb never leaves a deb to reuse
if ! dpkg-deb --build --root-owner-group "${pkg_root}" "${deb_file}.part" || \
! mv -f "${deb_file}.part" "${deb_file}"; then
rm -rf "${pkg_root}"; display_alert "CHROMIUM" "ERROR: cannot build ${deb_file}" "err"; return 1
fi
rm -rf "${pkg_root}"
display_alert "CHROMIUM" "Package: ${deb_file}" "info"
ls -lh "${deb_file}"
}
# ==============================================================================
# Section 4: Main Build Function
# ==============================================================================
function amlbian_build_chromium() {
__chromium_check_enabled || return 0
# A reused tree keeps the patches it was built with, so the tree records a checksum of patches/,
# overlay/ and the code extracting and patching it, and is rebuilt from the tarball when that changes.
# The deb records that checksum plus config/ and the build and packaging code, and the deb of this
# version is reused only while all of it matches. Otherwise no deb of any version stays at the top of
# BUILD_DEBS (lib/function-img.sh installs any). Code is hashed via declare -f: comments are ignored,
# but a bash upgrade may change its output once. After a tree rebuild clang and the sysroot are linked
# from ${CHROMIUM_BUILD_ROOT}/src when it has them, else fetched (see __chromium_configure).
local sum key old deb_file="${BUILD_DEBS}/${DEB_NAME}_${DEB_VERSION}_arm64.deb"
if ! sum=$(set -o pipefail; cd "${SCRIPT_DIR}" &&
{ declare -f __chromium_fetch_source __chromium_apply_patches &&
find patches overlay -type f -print0 | LC_ALL=C sort -z | xargs -0 -r sha256sum; } | sha256sum) ||
! key=$(set -o pipefail; cd "${SCRIPT_DIR}" &&
{ echo "${sum}" && declare -f __chromium_configure __chromium_compile __chromium_package_deb &&
find config -type f -print0 | LC_ALL=C sort -z | xargs -0 -r sha256sum; } | sha256sum); then
display_alert "CHROMIUM" "ERROR: cannot checksum the chromium build inputs" "err"; return 1
fi
if [[ -s "${deb_file}" && "$(cat "${deb_file}.sum" 2>/dev/null || true)" == "${key}" ]]; then
display_alert "CHROMIUM" "Pre-built deb found: ${deb_file##*/}, skipping build" "info"
return 0
fi
old=$(cat "${CHROMIUM_SRC}/.aml-patches.sum" 2>/dev/null || true)
if [[ -d "${BUILD_DEBS}" ]]; then
find -H "${BUILD_DEBS}" -maxdepth 1 \( -name "${DEB_NAME}_*.deb" -o -name "${DEB_NAME}_*.deb.*" \) \
-printf "Removing %p\n" -delete || \
{ display_alert "CHROMIUM" "ERROR: cannot remove old debs in ${BUILD_DEBS}" "err"; return 1; }
fi
if [[ "${old}" != "${sum}" ]]; then
local why="not recorded"; [[ -z "${old}" ]] || why="inputs changed"
display_alert "CHROMIUM" "Tree ${why}: rebuilding ${CHROMIUM_SRC} from the tarball" "wrn"
[[ "${CHROMIUM_SRC##*/}" == "chromium-${CHROMIUM_VERSION}" ]] || \
{ display_alert "CHROMIUM" "ERROR: unexpected CHROMIUM_SRC ${CHROMIUM_SRC}" "err"; return 1; }
# The record goes first, so an interrupted removal never leaves a recorded but partial tree
if ! rm -f "${CHROMIUM_SRC}/.aml-patches.sum" || ! rm -rf "${CHROMIUM_SRC}"; then
display_alert "CHROMIUM" "ERROR: cannot remove ${CHROMIUM_SRC}" "err"; return 1
fi
fi
display_alert "CHROMIUM" "No deb with matching inputs, compiling from source..." "info"
# A step that changes the tree or the deb must be in one of the declare -f lists above
__chromium_fetch_source || { display_alert "CHROMIUM" "ABORT: fetch failed" "err"; return 1; }
__chromium_apply_patches || { display_alert "CHROMIUM" "ABORT: patch failed" "err"; return 1; }
echo "${sum}" > "${CHROMIUM_SRC}/.aml-patches.sum" || \
{ display_alert "CHROMIUM" "ERROR: cannot write ${CHROMIUM_SRC}/.aml-patches.sum" "err"; return 1; }
__chromium_configure || { display_alert "CHROMIUM" "ABORT: configure failed" "err"; return 1; }
__chromium_compile || { display_alert "CHROMIUM" "ABORT: compile failed" "err"; return 1; }
__chromium_package_deb || { display_alert "CHROMIUM" "ABORT: package failed" "err"; return 1; }
echo "${key}" > "${deb_file}.sum" || \
{ display_alert "CHROMIUM" "ERROR: cannot write ${deb_file}.sum" "err"; return 1; }
}
# ==============================================================================
# Section 5: Main
# ==============================================================================
display_alert "AMLbian Chromium Build Script" "Version: ${CHROMIUM_VERSION}" "info"
# Run in subshell to isolate PATH/env changes from parent build system.
# Other build scripts (kernel, uboot) run directly with set -e, but chromium
# modifies PATH (depot_tools), PYTHONHOME, etc. which would affect subsequent
# scripts if not isolated.
(amlbian_build_chromium)
# ==============================================================================
# End of build-chromium.sh
# ==============================================================================