From 4fad920739e35eb07f3144b4186da24541752b30 Mon Sep 17 00:00:00 2001 From: xinlongchen Date: Wed, 8 Jul 2026 11:16:15 +0800 Subject: [PATCH] add yum-cross helper --- cross-toolchains.spec | 34 ++++- yum-cross | 337 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 370 insertions(+), 1 deletion(-) create mode 100755 yum-cross diff --git a/cross-toolchains.spec b/cross-toolchains.spec index 648c20e..2e276a6 100644 --- a/cross-toolchains.spec +++ b/cross-toolchains.spec @@ -13,11 +13,12 @@ Name: cross-toolchains Version: 20240715 -Release: 2%{?dist} +Release: 3%{?dist} Summary: Binutils GCC and GLibc cross-toolchain License: LGPL-2.1-or-later and LGPL-3.0-or-later and GPL-2.0-or-later and GPL-3.0-or-later and GPL-3.0-or-later with GCC-exception-3.1 URL: https://gitee.com/opencloudos-stream/cross-toolchains Source1: README.md +Source2: yum-cross ExclusiveArch: %{target_arch} ExcludeArch: loongarch64 # using these four source codes @@ -72,6 +73,7 @@ Requires: gcc-\${arch}-static-linux-gnu = %{version}-%{release} Requires: binutils-\${arch}-linux-gnu = %{version}-%{release} Requires: glibc-\${arch}-linux-gnu = %{version}-%{release} Requires: glibc-static-\${arch}-linux-gnu = %{version}-%{release} +Requires: yum-cross = %{version}-%{release} %description -n cross-toolchains-\${arch} meta package of cross-toolchains for \${arch} @@ -135,6 +137,21 @@ done")} # declared packages end ################################################################## +# arch-independent host helper to populate a cross sysroot with +# target-arch RPM dependencies (yumdownloader -> rpm2cpio | cpio -> sysroot) +%package -n yum-cross +Summary: yum-like helper to manage cross-compile sysroot dependencies +BuildArch: noarch +Requires: dnf-utils +Requires: rpm +Requires: cpio + +%description -n yum-cross +yum-cross wraps "yumdownloader (target-arch RPM) -> rpm2cpio | cpio -> +fix dangling symlinks -> drop into sysroot" into a single yum-like command, +so runtime/-devel libraries can be imported into a cross-toolchain sysroot for +aarch64 / riscv64 / loongarch64 targets. + %prep @@ -401,6 +418,9 @@ done %install export CURRENT_DIR="%{_builddir}/%{name}-%{version}" +# arch-independent host helper (shipped by the noarch yum-cross subpackage) +install -Dpm 0755 %{SOURCE2} %{buildroot}%{_bindir}/yum-cross + for TARGET_ARCH in %{target_arch};do echo "--------------------------------------------------------------" echo "starting installing target ${TARGET_ARCH}" @@ -1207,9 +1227,21 @@ done %_mandir/man1/riscv64-linux-gnu-strings* %_mandir/man1/riscv64-linux-gnu-strip* +# +# arch-independent helper +# +%files -n yum-cross +%doc README.md +%{_bindir}/yum-cross + %changelog +* Wed Jul 08 2026 Xinlong Chen - 20240715-3 +- [Type] sync +- [DESC] Add noarch yum-cross subpackage: yum-like helper to import target-arch +- RPM dependencies into a cross-toolchain sysroot + * Tue Mar 17 2026 Xinlong Chen - 20240715-2 - Exclude loongarch64 from build architectures diff --git a/yum-cross b/yum-cross new file mode 100755 index 0000000..956a9c5 --- /dev/null +++ b/yum-cross @@ -0,0 +1,337 @@ +#!/usr/bin/env bash +# +# yum-cross - manage cross-compile sysroot dependencies. +# +# Wraps the manual flow "yumdownloader (target arch RPM) -> rpm2cpio | cpio +# -> fix dangling symlinks -> drop into sysroot" into a single yum-like command: +# +# yum-cross --arch=aarch64 install zlib-devel openssl-devel libseccomp-devel +# +# Target arches : aarch64 / riscv64 / loongarch64 +# Host deps : yumdownloader (dnf-utils), rpm2cpio, cpio +# +set -euo pipefail + +PROG="${0##*/}" + +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- +ARCH="" +SYSROOT="" +KEEP_RPMS=0 # keep downloaded RPMs after extract +DRY_RUN=0 +WORKDIR="" # RPM download dir (default: mktemp) +SUDO="" # auto-filled with sudo when sysroot needs root + +# C/C++ runtime shipped by the cross toolchain; never pull the distro's target +# arch build of these into the sysroot: +# - glibc- : puts libc / ld-linux / headers into the sysroot, +# the distro package would overwrite and break it; +# - gcc-libs- : libgcc_s / libstdc++ live in /usr/lib/gcc// +# and are found by the compiler; a distro copy in the +# sysroot risks C++ runtime symbol mismatch. +# binutils is NOT guarded: its cross package only installs host tools under +# /usr/bin and ships nothing into the sysroot, so the distro's target-arch +# binutils/libbfd is a legitimate library to import. +TOOLCHAIN_PROVIDED=(glibc 'glibc-*' libgcc 'libgcc-*' libstdc++ 'libstdc++-*') + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +c_red() { printf '\033[31m%s\033[0m' "$*"; } +c_grn() { printf '\033[32m%s\033[0m' "$*"; } +c_ylw() { printf '\033[33m%s\033[0m' "$*"; } + +info() { echo "$(c_grn "[yum-cross]") $*"; } +warn() { echo "$(c_ylw "[yum-cross]") $*" >&2; } +die() { echo "$(c_red "[yum-cross] error:")" "$*" >&2; exit 1; } + +# print & run; print only under --dry-run +run() { + if [ "$DRY_RUN" -eq 1 ]; then + echo " + $*" + else + eval "$@" + fi +} + +# true if a name (package name or RPM filename) is toolchain-provided +is_toolchain_pkg() { + local name="$1" pat + for pat in "${TOOLCHAIN_PROVIDED[@]}"; do + # shellcheck disable=SC2254 # $pat is an intentional glob + case "$name" in $pat) return 0 ;; esac + done + return 1 +} + +# drop toolchain-provided entries from the given list (warn on each); +# result goes into the global array GUARDED_ITEMS +GUARDED_ITEMS=() +guard_toolchain_pkgs() { + GUARDED_ITEMS=() + local item base + for item in "$@"; do + base="${item##*/}" # match by basename for RPM file paths + if is_toolchain_pkg "$item" || is_toolchain_pkg "${base%.rpm}"; then + warn "skip '$item': provided by the cross toolchain (glibc-$TRIPLET, ...), must not go into the sysroot." + else + GUARDED_ITEMS+=("$item") + fi + done +} + +usage() { + cat < [options] [packages...] + +Subcommands: + install download target-arch RPMs, extract into sysroot, fix symlinks (default) + download only download RPMs into the workdir, no extract + extract extract existing RPM files into sysroot and fix symlinks + fix-links scan and fix dangling absolute symlinks in the sysroot + info print current arch / triplet / sysroot config + +Options: + --arch=ARCH target arch: aarch64 | riscv64 | loongarch64 (required) + --sysroot=DIR sysroot path (default: /usr/lib//sys-root) + --keep-rpms keep downloaded RPMs after extract (default: remove temp files) + --workdir=DIR RPM download/staging dir (default: a mktemp temp dir) + --dry-run only print the commands, do not run them + -h, --help show this help + +Notes: + Downloads exactly the packages you name; no dependency resolution. For a + cross sysroot, list the runtime lib together with its -devel (e.g. zlib + + zlib-devel). glibc / ld-linux etc. are provided by the toolchain package + glibc- and must not be downloaded here. + +Examples: + # list runtime libs + devel packages explicitly + $PROG --arch=aarch64 install zlib zlib-devel openssl-libs openssl-devel \\ + libseccomp libseccomp-devel + + # custom sysroot + $PROG --arch=riscv64 --sysroot=/opt/rv64-sysroot install \\ + openssl-libs openssl-devel + + # download only, no extract + $PROG --arch=aarch64 download glib2-devel + + # fix dangling symlinks in the sysroot + $PROG --arch=aarch64 fix-links +EOF +} + +# arch -> triplet +arch_to_triplet() { + case "$1" in + aarch64) echo "aarch64-linux-gnu" ;; + riscv64) echo "riscv64-linux-gnu" ;; + loongarch64) echo "loongarch64-linux-gnu" ;; + *) die "unsupported arch '$1' (choose: aarch64 / riscv64 / loongarch64)" ;; + esac +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || die "missing command '$1', install it with: $2" +} + +# decide whether writing the sysroot needs sudo +compute_sudo() { + local target="$1" + # walk up to the nearest existing parent to test writability + local probe="$target" + while [ ! -e "$probe" ] && [ "$probe" != "/" ]; do + probe="$(dirname "$probe")" + done + if [ ! -w "$probe" ]; then + if command -v sudo >/dev/null 2>&1; then + SUDO="sudo" + else + warn "no write permission on $target and sudo not found; this may fail." + fi + fi +} + +# rewrite dangling absolute symlinks (e.g. -> /lib64/xxx) to sysroot-relative +fix_broken_symlinks() { + local root="$1" + [ -d "$root" ] || { warn "sysroot not found, skip symlink fixup: $root"; return 0; } + info "fixing dangling absolute symlinks in sysroot: $root" + if [ "$DRY_RUN" -eq 1 ]; then + echo " + (scan & rewrite dangling absolute symlinks under $root)" + return 0 + fi + local fixed=0 + # every symlink whose target does not exist + while IFS= read -r -d '' link; do + local target + target="$(readlink "$link")" + case "$target" in + /*) + local rel="${root%/}/${target#/}" + if [ -e "$rel" ]; then + $SUDO ln -sf "$rel" "$link" + fixed=$((fixed + 1)) + fi + ;; + esac + done < <($SUDO find "$root" -type l ! -exec test -e {} \; -print0 2>/dev/null) + info "fixed $fixed symlink(s)." +} + +# extract a set of RPMs into the sysroot +extract_rpms() { + local root="$1"; shift + local rpms=("$@") + [ "${#rpms[@]}" -gt 0 ] || die "no RPM to extract." + + guard_toolchain_pkgs "${rpms[@]}" + rpms=("${GUARDED_ITEMS[@]}") + [ "${#rpms[@]}" -gt 0 ] || die "no RPM to extract: all given files are toolchain-provided packages." + + run "$SUDO mkdir -p '$root'" + local rpm + for rpm in "${rpms[@]}"; do + [ -f "$rpm" ] || { warn "skip missing file: $rpm"; continue; } + info "extract $(basename "$rpm") -> $root" + if [ "$DRY_RUN" -eq 1 ]; then + echo " + rpm2cpio '$rpm' | $SUDO cpio -idm -D '$root'" + else + rpm2cpio "$rpm" | $SUDO cpio -idm -D "$root" --quiet + fi + done + fix_broken_symlinks "$root" +} + +# download target-arch RPMs into destdir; result -> global DOWNLOADED_RPMS +DOWNLOADED_RPMS=() +download_rpms() { + local destdir="$1"; shift + local pkgs=("$@") + [ "${#pkgs[@]}" -gt 0 ] || die "no package specified to download." + + guard_toolchain_pkgs "${pkgs[@]}" + pkgs=("${GUARDED_ITEMS[@]}") + [ "${#pkgs[@]}" -gt 0 ] || die "nothing to download: all specified packages are toolchain-provided." + + run "mkdir -p '$destdir'" + + info "downloading $ARCH RPMs: ${pkgs[*]}" + run "yumdownloader --forcearch='$ARCH' --destdir='$destdir' ${pkgs[*]}" + + if [ "$DRY_RUN" -eq 1 ]; then + DOWNLOADED_RPMS=() + return 0 + fi + + mapfile -t DOWNLOADED_RPMS < <(find "$destdir" -maxdepth 1 -name '*.rpm' -type f 2>/dev/null) + [ "${#DOWNLOADED_RPMS[@]}" -gt 0 ] || die "no RPM downloaded; check package names or repo config." + info "downloaded ${#DOWNLOADED_RPMS[@]} RPM(s)." +} + +cleanup_workdir="" +cleanup() { + [ -n "$cleanup_workdir" ] && [ "$KEEP_RPMS" -eq 0 ] && rm -rf "$cleanup_workdir" 2>/dev/null || true +} +trap cleanup EXIT + +# --------------------------------------------------------------------------- +# Arg parsing +# --------------------------------------------------------------------------- +ARGS=() +while [ $# -gt 0 ]; do + case "$1" in + --arch=*) ARCH="${1#*=}" ;; + --arch) shift; ARCH="${1:-}" ;; + --sysroot=*) SYSROOT="${1#*=}" ;; + --sysroot) shift; SYSROOT="${1:-}" ;; + --workdir=*) WORKDIR="${1#*=}" ;; + --workdir) shift; WORKDIR="${1:-}" ;; + --keep-rpms) KEEP_RPMS=1 ;; + --dry-run) DRY_RUN=1 ;; + -h|--help) usage; exit 0 ;; + --) shift; while [ $# -gt 0 ]; do ARGS+=("$1"); shift; done; break ;; + -*) die "unknown option: $1 (see -h for help)" ;; + *) ARGS+=("$1") ;; + esac + shift +done + +[ "${#ARGS[@]}" -gt 0 ] || { usage; exit 1; } + +SUBCMD="${ARGS[0]}" +PKGS=("${ARGS[@]:1}") + +# --------------------------------------------------------------------------- +# Validate + derive config +# --------------------------------------------------------------------------- +[ -n "$ARCH" ] || die "target arch is required via --arch (aarch64 / riscv64 / loongarch64)" +TRIPLET="$(arch_to_triplet "$ARCH")" +[ -n "$SYSROOT" ] || SYSROOT="/usr/lib/${TRIPLET}/sys-root" +compute_sudo "$SYSROOT" + +# --------------------------------------------------------------------------- +# Dispatch +# --------------------------------------------------------------------------- +case "$SUBCMD" in + info) + echo " arch : $ARCH" + echo " triplet : $TRIPLET" + echo " sysroot : $SYSROOT" + echo " sudo : $([ -n "$SUDO" ] && echo "yes (sysroot needs root)" || echo "no")" + ;; + + install) + [ "${#PKGS[@]}" -gt 0 ] || die "install needs at least one package name." + need_cmd yumdownloader "yum install dnf-utils" + need_cmd rpm2cpio "yum install rpm" + need_cmd cpio "yum install cpio" + + if [ -n "$WORKDIR" ]; then + destdir="$WORKDIR" + else + destdir="$(mktemp -d /tmp/yum-cross.XXXXXX)" + cleanup_workdir="$destdir" + fi + + download_rpms "$destdir" "${PKGS[@]}" + if [ "$DRY_RUN" -eq 1 ]; then + info "(dry-run) would extract $destdir/*.rpm into $SYSROOT and fix symlinks." + else + extract_rpms "$SYSROOT" "${DOWNLOADED_RPMS[@]}" + fi + info "done. sysroot ready: $SYSROOT" + info "compile with: --sysroot=$SYSROOT ; run under QEMU with: -L $SYSROOT" + ;; + + download) + [ "${#PKGS[@]}" -gt 0 ] || die "download needs at least one package name." + need_cmd yumdownloader "yum install dnf-utils" + destdir="${WORKDIR:-$PWD}" + KEEP_RPMS=1 # pure download always keeps the RPMs + download_rpms "$destdir" "${PKGS[@]}" + info "RPMs saved to: $destdir" + ;; + + extract) + [ "${#PKGS[@]}" -gt 0 ] || die "extract needs at least one RPM file path." + need_cmd rpm2cpio "yum install rpm" + need_cmd cpio "yum install cpio" + extract_rpms "$SYSROOT" "${PKGS[@]}" + info "extract done: $SYSROOT" + ;; + + fix-links) + fix_broken_symlinks "$SYSROOT" + ;; + + *) + die "unknown subcommand '$SUBCMD' (available: install / download / extract / fix-links / info)" + ;; +esac -- Gitee