Files
openwrt-build-helper/prepare-openwrt-env.sh
Zhe Yuan de7f3f12fd Rewrite the prepare scripts for current OpenWrt and more distros
prepare-openwrt-env.sh
- Dispatch on the package manager instead of guessing from the distro
  version: apt, dnf/yum, pacman, zypper and apk are all supported.
- Probe every package against the running release and skip the ones that
  no longer exist. Previously a single dropped package (on Ubuntu 26.04
  arm64, gcc-multilib-s390x-linux-gnu) failed the whole apt command and
  set -e aborted the script, so nothing was installed at all.
- Align the package lists with the official build system guide, adding
  libelf-dev, python3-dev, bc, xsltproc, zstd, u-boot-tools and others.
- Make Go opt-in via --with-go rather than forced on arm64.
- Add -y/--yes, -n/--dry-run and -h/--help; allow running as root.

prepare-openwrt.sh
- Split read-only discovery from mutation. Versions are resolved over
  git ls-remote before anything is moved, so aborting at the prompt
  leaves the helper checkout untouched.
- Resume an interrupted first run instead of failing forever: a root
  with .git but no source tree was previously unrecoverable without a
  manual rm -rf. Also restore the worktree when HEAD already points at
  the target ref, where checkout is a no-op.
- Detect the relocated layout by content rather than directory name, so
  renaming the root no longer triggers a second, nested relocation.
- Replace the staged in-place relocation with a resumable content move,
  removing the window that could strand the checkout in a hidden dir.
- Filter release candidates before sorting; sort -V orders v25.12.0-rc1
  after v25.12.0, so the newest stable tag could be an rc.
- Follow upstream: snapshots use main (master as fallback) and the dead
  -SNAPSHOT tag lookup is gone.
- Use a partial clone (--filter=blob:none) by default.
- Guard version switches on a dirty tree and back up .config first.
- Add /helper/ to .git/info/exclude and warn that git clean -xdff still
  deletes it, since -x bypasses ignore rules by design.
- Leave a symlink at the old checkout path so shells and editors whose
  working directory still points there keep working.
- Add --stable/--branch/--snapshot plus -y, -n, --force, --full-clone,
  --allow-rc, --root, --skip-feeds, --skip-defconfig, --repo-url and
  --no-compat-link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:31:26 -04:00

371 lines
11 KiB
Bash
Executable File

#!/bin/bash
# Prepare the OpenWrt build dependencies.
#
# Supported distributions:
# - Debian / Ubuntu / Linux Mint / Raspberry Pi OS (apt)
# - Fedora / RHEL / CentOS / Rocky / AlmaLinux (dnf, yum)
# - Arch / Manjaro / EndeavourOS (pacman)
# - openSUSE / SLES (zypper)
# - Alpine (apk)
#
# Package lists follow https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
# Packages that do not exist on the running release are skipped instead of
# aborting the whole installation.
#
# Usage: ./prepare-openwrt-env.sh [-y] [-n] [--with-go]
# -y, --yes do not ask for confirmation
# -n, --dry-run only print what would be installed
# --with-go also install the Go toolchain
#
# Author: Zhe Yuan
set -euo pipefail
umask 022
ASSUME_YES=false
DRY_RUN=false
WITH_GO=false
while [ $# -gt 0 ]; do
case "$1" in
-y|--yes) ASSUME_YES=true ;;
-n|--dry-run) DRY_RUN=true ;;
--with-go) WITH_GO=true ;;
-h|--help)
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "[ERROR] Unknown option: $1" >&2
exit 1
;;
esac
shift
done
echo "========================================"
echo " OpenWrt Build Environment Preparation"
echo "========================================"
# --- Detect architecture ---
ARCH="$(uname -m)"
echo "[INFO] Architecture: $ARCH"
# --- Detect distribution ---
DISTRO="unknown"
DISTRO_LIKE=""
VERSION_ID="unknown"
PRETTY_NAME="unknown"
if [ -r /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release
DISTRO="${ID:-unknown}"
DISTRO_LIKE="${ID_LIKE:-}"
VERSION_ID="${VERSION_ID:-unknown}"
PRETTY_NAME="${PRETTY_NAME:-$DISTRO $VERSION_ID}"
else
echo "[ERROR] Cannot detect the distribution (no /etc/os-release)." >&2
exit 1
fi
echo "[INFO] Distribution: $PRETTY_NAME (id=$DISTRO)"
# --- Detect package manager ---
PKG_MGR=""
for candidate in apt-get dnf yum pacman zypper apk; do
if command -v "$candidate" >/dev/null 2>&1; then
PKG_MGR="$candidate"
break
fi
done
if [ -z "$PKG_MGR" ]; then
echo "[ERROR] No supported package manager found." >&2
echo "[ERROR] Supported: apt-get, dnf, yum, pacman, zypper, apk." >&2
exit 1
fi
echo "[INFO] Package manager: $PKG_MGR"
# --- Privilege escalation ---
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
echo "[ERROR] Not running as root and 'sudo' is not available." >&2
exit 1
fi
fi
is_x86_64() {
[ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]
}
is_arm64() {
[ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]
}
# ---------------------------------------------------------------------------
# Package lists
#
# REQUIRED_PACKAGES installation fails if one of them cannot be installed
# OPTIONAL_PACKAGES nice to have; silently skipped when unavailable
# ---------------------------------------------------------------------------
REQUIRED_PACKAGES=""
OPTIONAL_PACKAGES=""
REFRESH_CMD=""
INSTALL_CMD=""
case "$PKG_MGR" in
apt-get)
REFRESH_CMD="$SUDO apt-get update"
INSTALL_CMD="$SUDO apt-get install -y --no-install-recommends"
REQUIRED_PACKAGES="build-essential clang flex bison g++ gawk gcc gettext git \
file wget unzip rsync swig time python3 python3-dev python3-setuptools \
libncurses-dev libssl-dev zlib1g-dev libelf-dev make patch diffutils"
# Extras from the OpenWrt 25.xx / Debian Trixie list. Names come and go
# between releases, so everything here is filtered before installing.
OPTIONAL_PACKAGES="bc binutils-gold ccache ecj fastjar help2man libbsd-dev \
liblzma-dev meson mold mtd-utils ninja-build pbzip2 pigz pkg-config \
subversion texinfo u-boot-tools xsltproc xxd zstd bzip2 quilt asciidoc \
device-tree-compiler java-propose-classpath python3-distutils \
python3-distutils-extra libncurses5-dev"
if is_x86_64; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES gcc-multilib g++-multilib glibc-source"
elif is_arm64; then
# arm64 has no native multilib; the cross variants are optional and the
# available set differs a lot between releases (e.g. the s390x variants
# were dropped in Ubuntu 26.04).
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES \
gcc-multilib-i686-linux-gnu g++-multilib-i686-linux-gnu \
gcc-multilib-x86-64-linux-gnu g++-multilib-x86-64-linux-gnu \
gcc-multilib-x86-64-linux-gnux32 g++-multilib-x86-64-linux-gnux32 \
gcc-multilib-s390x-linux-gnu g++-multilib-s390x-linux-gnu \
libc6-dev-i386-cross libc6-dev-i386-amd64-cross libc6-dev-i386-x32-cross \
glibc-source"
fi
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES golang golang-go"
fi
;;
dnf|yum)
REFRESH_CMD="$SUDO $PKG_MGR makecache"
INSTALL_CMD="$SUDO $PKG_MGR install -y --setopt=install_weak_deps=False"
REQUIRED_PACKAGES="gcc gcc-c++ make git-core patch diffutils file wget \
unzip rsync tar which bzip2 ncurses-devel openssl-devel zlib-devel \
python3 python3-devel python3-setuptools"
OPTIONAL_PACKAGES="bash-completion elfutils-libelf-devel zlib-static \
glibc-static swig quilt ccache perl-base perl-Data-Dumper \
perl-File-Compare perl-File-Copy perl-FindBin perl-IPC-Cmd perl-JSON-PP \
perl-lib perl-Thread-Queue perl-Time-Piece libxslt bc texinfo \
dtc zstd pkgconf-pkg-config"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES golang"
fi
;;
pacman)
REFRESH_CMD="$SUDO pacman -Sy"
INSTALL_CMD="$SUDO pacman -S --needed --noconfirm"
REQUIRED_PACKAGES="base-devel bash bzip2 git libelf libxslt ncurses openssl \
rsync swig time unzip util-linux wget which zlib python python-setuptools"
OPTIONAL_PACKAGES="ccache quilt pkgconf diffutils patch tar make gawk \
subversion asciidoc dtc zstd"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go"
fi
;;
zypper)
REFRESH_CMD="$SUDO zypper --non-interactive refresh"
INSTALL_CMD="$SUDO zypper --non-interactive install --no-recommends"
REQUIRED_PACKAGES="bash bc binutils bzip2 flex gawk gcc gcc-c++ \
gettext-tools git-core make ncurses-devel patch rsync unzip util-linux \
wget zlib-devel libopenssl-devel python3-devel python3-setuptools"
OPTIONAL_PACKAGES="asciidoc fastjar intltool libxslt-tools mercurial \
perl-ExtUtils-MakeMaker sdcc quilt ccache libelf-devel swig zstd dtc"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go"
fi
;;
apk)
REFRESH_CMD="$SUDO apk update"
INSTALL_CMD="$SUDO apk add"
REQUIRED_PACKAGES="argp-standalone bash bc binutils bzip2 coreutils \
diffutils elfutils-dev findutils flex g++ gawk gcc gettext git grep gzip \
linux-headers make ncurses-dev openssl-dev patch perl python3-dev rsync \
tar unzip util-linux wget zlib-dev"
OPTIONAL_PACKAGES="asciidoc cdrkit intltool libxslt musl-fts-dev \
musl-libintl musl-obstack-dev swig quilt ccache zstd"
if [ "$WITH_GO" = true ]; then
OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go"
fi
;;
esac
# ---------------------------------------------------------------------------
# Availability filtering
# ---------------------------------------------------------------------------
pkg_available() {
local pkg="$1"
case "$PKG_MGR" in
apt-get)
# apt-cache policy prints no "Candidate:" line for purely virtual names
# and "(none)" when no installable version exists in the repositories.
local policy=""
policy="$(apt-cache policy "$pkg" 2>/dev/null || true)"
case "$policy" in
*"Candidate: (none)"*) return 1 ;;
*"Candidate:"*) return 0 ;;
*) return 1 ;;
esac
;;
dnf) dnf -q info "$pkg" >/dev/null 2>&1 ;;
yum) yum -q info "$pkg" >/dev/null 2>&1 ;;
pacman) pacman -Si "$pkg" >/dev/null 2>&1 || pacman -Sg "$pkg" >/dev/null 2>&1 ;;
zypper) zypper --non-interactive --quiet search --match-exact "$pkg" >/dev/null 2>&1 ;;
apk) [ -n "$(apk search -x "$pkg" 2>/dev/null)" ] ;;
*) return 0 ;;
esac
}
filter_available() {
local pkg
local kept=""
local dropped=""
for pkg in $1; do
if pkg_available "$pkg"; then
kept="$kept $pkg"
else
dropped="$dropped $pkg"
fi
done
FILTER_KEPT="${kept# }"
FILTER_DROPPED="${dropped# }"
}
echo
echo "[INFO] Refreshing package metadata..."
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] $REFRESH_CMD"
else
$REFRESH_CMD
fi
echo "[INFO] Checking package availability for this release..."
filter_available "$REQUIRED_PACKAGES"
REQUIRED_AVAILABLE="$FILTER_KEPT"
REQUIRED_MISSING="$FILTER_DROPPED"
filter_available "$OPTIONAL_PACKAGES"
OPTIONAL_AVAILABLE="$FILTER_KEPT"
OPTIONAL_MISSING="$FILTER_DROPPED"
INSTALL_PACKAGES="$REQUIRED_AVAILABLE $OPTIONAL_AVAILABLE"
INSTALL_PACKAGES="$(echo "$INSTALL_PACKAGES" | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//')"
if [ -z "$INSTALL_PACKAGES" ]; then
echo "[ERROR] No installable package found. Are the repositories configured?" >&2
exit 1
fi
echo
echo "========================================"
echo "📦 Packages to install:"
echo "$INSTALL_PACKAGES" | tr ' ' '\n' | sed 's/^/ /'
echo "========================================"
if [ -n "$REQUIRED_MISSING" ]; then
echo
echo "[WARN] Required packages not offered by this release (build may fail):"
echo " $REQUIRED_MISSING"
fi
if [ -n "$OPTIONAL_MISSING" ]; then
echo
echo "[INFO] Optional packages unavailable here, skipped:"
echo " $OPTIONAL_MISSING"
fi
if [ "$DRY_RUN" = true ]; then
echo
echo "[DRY-RUN] $INSTALL_CMD $INSTALL_PACKAGES"
exit 0
fi
if [ "$ASSUME_YES" != true ]; then
echo
read -r -p "Proceed with installation? [Y/n]: " reply
case "$reply" in
[nN]|[nN][oO]) echo "[INFO] Aborted by user."; exit 0 ;;
esac
fi
echo
echo "[INFO] Installing packages..."
FAILED_PACKAGES=""
if ! $INSTALL_CMD $INSTALL_PACKAGES; then
echo
echo "[WARN] Bulk installation failed. Retrying package by package..."
for pkg in $INSTALL_PACKAGES; do
if ! $INSTALL_CMD "$pkg" >/dev/null 2>&1; then
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
echo " [FAIL] $pkg"
fi
done
FAILED_PACKAGES="${FAILED_PACKAGES# }"
fi
echo
echo "========================================"
if [ -n "$FAILED_PACKAGES" ]; then
echo "⚠️ Setup finished with errors."
echo " Failed packages: $FAILED_PACKAGES"
else
echo "✅ OpenWrt build environment setup complete!"
fi
echo "========================================"
echo
if [ "$WITH_GO" != true ]; then
echo "👉 [NOTE] The Go toolchain is only needed for Go based packages"
echo " (e.g. xray-core, v2ray). Install it with:"
echo " ./prepare-openwrt-env.sh --with-go"
echo
fi
if is_arm64; then
echo "👉 [NOTE] arm64 has no native multilib. The cross-multilib packages are"
echo " optional and only needed for x86 targets; missing ones were skipped."
echo
fi
echo "👉 Next step:"
echo " ./prepare-openwrt.sh # clone and set up the OpenWrt source"
echo