From e21e0bb84d973014857b2d42f782e20dd6b58556 Mon Sep 17 00:00:00 2001 From: yuming Date: Thu, 2 Jul 2026 15:57:30 +0800 Subject: [PATCH 1/2] feat(tools): initialize bootstartup scripts --- tools/build-blueos-rust-toolchain.sh | 246 ++++++++++++++++++++ tools/setup-blueos-base-env.sh | 323 +++++++++++++++++++++++++++ 2 files changed, 569 insertions(+) create mode 100755 tools/build-blueos-rust-toolchain.sh create mode 100755 tools/setup-blueos-base-env.sh diff --git a/tools/build-blueos-rust-toolchain.sh b/tools/build-blueos-rust-toolchain.sh new file mode 100755 index 000000000..bde75511e --- /dev/null +++ b/tools/build-blueos-rust-toolchain.sh @@ -0,0 +1,246 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +WORKSPACE="${SCRIPT_DIR}/blueos-dev" +BLUEOS_PREFIX="${HOME}/.local/opt/blueos" +RUST_PREFIX="${BLUEOS_PREFIX}/rust" +SRC_DIR="${HOME}/.cache/blueos-dev/rust-toolchain" +ENV_FILE="${SCRIPT_DIR}/blueos-env.sh" +LINK_RUSTUP=0 +DRY_RUN=0 +USE_SSH=0 + +TARGETS=( + aarch64-vivo-blueos-newlib + thumbv7m-vivo-blueos-newlibeabi + thumbv8m.main-vivo-blueos-newlibeabihf + riscv64-vivo-blueos + riscv32-vivo-blueos + riscv32imc-vivo-blueos +) + +usage() { + cat <<'EOF' +Usage: ./build-blueos-rust-toolchain.sh [options] + +Build the BlueOS forked Rust toolchain after the base environment and repo sync +are complete. + +Options: + --workspace PATH BlueOS repo workspace. Default: /blueos-dev + --prefix PATH Rust toolchain DESTDIR. Default: ~/.local/opt/blueos/rust + --src-dir PATH Source/build directory. Default: ~/.cache/blueos-dev/rust-toolchain + --env-file PATH Environment file to source if present. Default: /blueos-env.sh + --ssh Clone vivoblueos repos over SSH instead of HTTPS + --link-rustup Register the built toolchain as rustup toolchain "blueos-dev" + --dry-run Print commands without changing the system + -h, --help Show this help +EOF +} + +log() { + printf '[blueos-rust] %s\n' "$*" +} + +section() { + local title="[blueos-rust] $*" + local width=78 + local inner_width=$((width - 4)) + local line + line="$(printf '%*s' "$width" '' | tr ' ' '=')" + + printf '\n%s\n' "$line" + printf '| %-*.*s |\n' "$inner_width" "$inner_width" "$title" + printf '%s\n\n' "$line" +} + +die() { + printf '[blueos-rust] error: %s\n' "$*" >&2 + exit 1 +} + +run() { + if [[ "$DRY_RUN" == 1 ]]; then + printf '[dry-run]' + printf ' %q' "$@" + printf '\n' + else + "$@" + fi +} + +as_abs_path() { + local path="$1" + if [[ "$path" == /* ]]; then + printf '%s\n' "$path" + else + printf '%s/%s\n' "$PWD" "$path" + fi +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --workspace) + [[ $# -ge 2 ]] || die "--workspace requires a path" + WORKSPACE="$(as_abs_path "$2")" + shift 2 + ;; + --prefix) + [[ $# -ge 2 ]] || die "--prefix requires a path" + RUST_PREFIX="$(as_abs_path "$2")" + shift 2 + ;; + --src-dir) + [[ $# -ge 2 ]] || die "--src-dir requires a path" + SRC_DIR="$(as_abs_path "$2")" + shift 2 + ;; + --env-file) + [[ $# -ge 2 ]] || die "--env-file requires a path" + ENV_FILE="$(as_abs_path "$2")" + shift 2 + ;; + --ssh) + USE_SSH=1 + shift + ;; + --link-rustup) + LINK_RUSTUP=1 + shift + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown option: $1" + ;; + esac + done +} + +repo_url() { + local name="$1" + if [[ "$USE_SSH" == 1 ]]; then + printf 'git@github.com:vivoblueos/%s.git\n' "$name" + else + printf 'https://github.com/vivoblueos/%s.git\n' "$name" + fi +} + +load_env_if_present() { + section "Load environment" + if [[ -r "$ENV_FILE" ]]; then + # shellcheck disable=SC1090 + . "$ENV_FILE" + fi +} + +check_prereqs() { + section "Check prerequisites" + if [[ ! -d "${WORKSPACE}/.repo" ]]; then + if [[ "$DRY_RUN" == 1 ]]; then + log "Would require initialized BlueOS workspace: ${WORKSPACE}" + else + die "BlueOS workspace is not initialized: ${WORKSPACE}" + fi + fi + + for cmd in git python3 make cmake ninja clang lld; do + if ! command -v "$cmd" >/dev/null 2>&1; then + if [[ "$DRY_RUN" == 1 ]]; then + log "Would require command: ${cmd}" + else + die "missing command: ${cmd}" + fi + fi + done +} + +clone_or_update() { + local name="$1" + local dest="${SRC_DIR}/${name}" + local url + url="$(repo_url "$name")" + + if [[ -d "${dest}/.git" ]]; then + log "Updating ${name}" + run git -C "$dest" pull --ff-only + else + log "Cloning ${name}" + run git clone "$url" "$dest" + fi + + if [[ "$name" == "rust" ]]; then + run git -C "$dest" submodule update --init --recursive + fi +} + +prepare_sources() { + section "Clone or update Rust sources" + run mkdir -p "$SRC_DIR" "$RUST_PREFIX" + clone_or_update rust + clone_or_update cc-rs + clone_or_update libc +} + +build_toolchain() { + section "Build BlueOS Rust toolchain" + local rust_src="${SRC_DIR}/rust" + local host="x86_64-unknown-linux-gnu" + if [[ ! -f "${rust_src}/config.blueos.toml" ]]; then + if [[ "$DRY_RUN" == 1 ]]; then + log "Would require ${rust_src}/config.blueos.toml after cloning rust" + else + die "missing ${rust_src}/config.blueos.toml" + fi + fi + + log "Building BlueOS Rust toolchain into ${RUST_PREFIX}" + run cp "${rust_src}/config.blueos.toml" "${rust_src}/config.toml" + + run bash -lc "cd \"${rust_src}\" && CARGO_NET_GIT_FETCH_WITH_CLI=true DESTDIR=\"${RUST_PREFIX}\" ./x.py install -i --stage 1 compiler/rustc" + + local target + for target in "${TARGETS[@]}"; do + run bash -lc "cd \"${rust_src}\" && CARGO_NET_GIT_FETCH_WITH_CLI=true DESTDIR=\"${RUST_PREFIX}\" ./x.py install -i --stage 1 library/std --target \"${target}\"" + done + + run bash -lc "cd \"${rust_src}\" && CARGO_NET_GIT_FETCH_WITH_CLI=true DESTDIR=\"${RUST_PREFIX}\" ./x.py install -i --stage 1 library/std --target \"${host}\"" + run bash -lc "cd \"${rust_src}\" && CARGO_NET_GIT_FETCH_WITH_CLI=true DESTDIR=\"${RUST_PREFIX}\" ./x.py install -i --stage 0 rustfmt" + run bash -lc "cd \"${rust_src}\" && CARGO_NET_GIT_FETCH_WITH_CLI=true DESTDIR=\"${RUST_PREFIX}\" ./x.py install -i --stage 0 rust-analyzer" + run bash -lc "cd \"${rust_src}\" && CARGO_NET_GIT_FETCH_WITH_CLI=true DESTDIR=\"${RUST_PREFIX}\" ./x.py install -i --stage 0 clippy" + + run mkdir -p "${RUST_PREFIX}/usr/local" + run cp -rav "${rust_src}/build/${host}/llvm/bin" "${rust_src}/build/${host}/llvm/lib" "${RUST_PREFIX}/usr/local/" +} + +link_rustup_toolchain() { + section "Register rustup toolchain" + if [[ "$LINK_RUSTUP" != 1 ]]; then + log "Skipping rustup link. Re-run with --link-rustup to register blueos-dev." + return + fi + + command -v rustup >/dev/null 2>&1 || die "rustup is required for --link-rustup" + run rustup toolchain link blueos-dev "${RUST_PREFIX}/usr/local" +} + +main() { + section "Parse options" + parse_args "$@" + load_env_if_present + check_prereqs + prepare_sources + build_toolchain + link_rustup_toolchain + log "Use the toolchain with: source ${ENV_FILE}" +} + +main "$@" diff --git a/tools/setup-blueos-base-env.sh b/tools/setup-blueos-base-env.sh new file mode 100755 index 000000000..b9a4a6c3a --- /dev/null +++ b/tools/setup-blueos-base-env.sh @@ -0,0 +1,323 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +WORKSPACE="${SCRIPT_DIR}/blueos-dev" +BLUEOS_BIN_TOOLS_PREFIX="${HOME}/.local/opt/blueos" +ENV_FILE="${SCRIPT_DIR}/blueos-env.sh" +CACHE_DIR="${HOME}/.cache/blueos-dev/downloads" +REPO_URL="https://github.com/vivoblueos/manifests.git" +DRY_RUN=0 +SKIP_APT=0 +SKIP_QEMU=0 +SKIP_REPO_SYNC=0 +ARM_VERSION="14.3.rel1" +QEMU_VERSION="10.0.2" + +APT_PACKAGES=( + build-essential cmake ninja-build pkg-config libssl-dev gdb-multiarch + curl git wget libslirp-dev python3 python3-pip meson libglib2.0-dev + flex bison libfdt-dev gcc-riscv64-unknown-elf clang llvm lld + python3-kconfiglib python3-tomli clang-format yapf3 xz-utils + ca-certificates tar make file repo generate-ninja +) + +usage() { + cat <<'EOF' +Usage: ./setup-blueos-base-env.sh [options] + +Prepare the Linux development environment documented by BlueOS. + +Options: + --workspace PATH BlueOS repo workspace. Default: /blueos-dev + --prefix PATH Tool install prefix. Default: ~/.local/opt/blueos + --env-file PATH Environment file to generate. Default: /blueos-env.sh + --ssh Use git@github.com:vivoblueos/manifests.git for repo init + --https Use https://github.com/vivoblueos/manifests.git for repo init + --skip-apt Do not run apt install + --skip-qemu Do not build/install QEMU + --skip-repo-sync Run repo init if needed, but skip repo sync + --dry-run Print commands without changing the system + -h, --help Show this help +EOF +} + +log() { + printf '[blueos-base] %s\n' "$*" +} + +section() { + local title="[blueos-base] $*" + local width=78 + local inner_width=$((width - 4)) + local line + line="$(printf '%*s' "$width" '' | tr ' ' '=')" + + printf '\n%s\n' "$line" + printf '| %-*.*s |\n' "$inner_width" "$inner_width" "$title" + printf '%s\n\n' "$line" +} + +die() { + printf '[blueos-base] error: %s\n' "$*" >&2 + exit 1 +} + +run() { + if [[ "$DRY_RUN" == 1 ]]; then + printf '[dry-run]' + printf ' %q' "$@" + printf '\n' + else + "$@" + fi +} + +as_abs_path() { + local path="$1" + if [[ "$path" == /* ]]; then + printf '%s\n' "$path" + else + printf '%s/%s\n' "$PWD" "$path" + fi +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --workspace) + [[ $# -ge 2 ]] || die "--workspace requires a path" + WORKSPACE="$(as_abs_path "$2")" + shift 2 + ;; + --prefix) + [[ $# -ge 2 ]] || die "--prefix requires a path" + BLUEOS_BIN_TOOLS_PREFIX="$(as_abs_path "$2")" + shift 2 + ;; + --env-file) + [[ $# -ge 2 ]] || die "--env-file requires a path" + ENV_FILE="$(as_abs_path "$2")" + shift 2 + ;; + --ssh) + REPO_URL="git@github.com:vivoblueos/manifests.git" + shift + ;; + --https) + REPO_URL="https://github.com/vivoblueos/manifests.git" + shift + ;; + --skip-apt) + SKIP_APT=1 + shift + ;; + --skip-qemu) + SKIP_QEMU=1 + shift + ;; + --skip-repo-sync) + SKIP_REPO_SYNC=1 + shift + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown option: $1" + ;; + esac + done +} + +check_platform() { + section "Check platform" + [[ -r /etc/os-release ]] || die "cannot read /etc/os-release" + # shellcheck disable=SC1091 + . /etc/os-release + + case "${ID:-}" in + ubuntu) + [[ "${VERSION_ID%%.*}" -ge 24 ]] || die "Ubuntu 24.04+ is required; found ${VERSION_ID:-unknown}" + ;; + debian) + [[ "${VERSION_ID%%.*}" -ge 12 ]] || die "Debian 12+ is required; found ${VERSION_ID:-unknown}" + ;; + *) + die "Debian 12+ or Ubuntu 24.04+ is required; found ${PRETTY_NAME:-unknown}" + ;; + esac +} + +install_apt_packages() { + section "Install distribution packages" + if [[ "$SKIP_APT" == 1 ]]; then + log "Skipping apt install" + return + fi + + log "Installing distribution packages" + run sudo apt-get update + run sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y "${APT_PACKAGES[@]}" +} + +ensure_dirs() { + section "Prepare install directories" + run mkdir -p "${BLUEOS_BIN_TOOLS_PREFIX}/bin" "${BLUEOS_BIN_TOOLS_PREFIX}/toolchains" "${BLUEOS_BIN_TOOLS_PREFIX}/src" "${BLUEOS_BIN_TOOLS_PREFIX}/qemu" "$CACHE_DIR" + export PATH="${BLUEOS_BIN_TOOLS_PREFIX}/bin:${PATH}" +} + +install_repo_if_needed() { + section "Install repo launcher" + if command -v repo >/dev/null 2>&1; then + log "repo already available: $(command -v repo)" + return + fi + + log "Installing repo into ${BLUEOS_BIN_TOOLS_PREFIX}/bin" + run mkdir -p "${BLUEOS_BIN_TOOLS_PREFIX}/bin" + run curl -fsSL "https://mirrors.tuna.tsinghua.edu.cn/git/git-repo" -o "${BLUEOS_BIN_TOOLS_PREFIX}/bin/repo" + run chmod +x "${BLUEOS_BIN_TOOLS_PREFIX}/bin/repo" +} + +check_gn() { + section "Check GN" + if command -v gn >/dev/null 2>&1; then + log "gn available: $(command -v gn)" + return + fi + + if [[ -x /usr/bin/gn ]]; then + log "gn available: /usr/bin/gn" + return + fi + + if [[ "$DRY_RUN" == 1 ]]; then + log "Would verify gn after apt installs generate-ninja" + return + fi + + die "gn was not found after apt install. On Ubuntu this is usually provided by generate-ninja." +} + +download_and_extract_arm_toolchain() { + local target="$1" + local archive="arm-gnu-toolchain-${ARM_VERSION}-x86_64-${target}.tar.xz" + local url="https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_VERSION}/binrel/${archive}" + local install_dir="${BLUEOS_BIN_TOOLS_PREFIX}/toolchains/arm-gnu-toolchain-${ARM_VERSION}-x86_64-${target}" + + if [[ -x "${install_dir}/bin/${target}-gcc" ]]; then + log "ARM toolchain already installed: ${install_dir}" + return + fi + + log "Installing ARM toolchain: ${target}" + run wget -c "$url" -O "${CACHE_DIR}/${archive}" + run tar -xJf "${CACHE_DIR}/${archive}" -C "${BLUEOS_BIN_TOOLS_PREFIX}/toolchains" +} + +install_arm_toolchains() { + section "Install ARM toolchains" + download_and_extract_arm_toolchain "arm-none-eabi" + download_and_extract_arm_toolchain "aarch64-none-elf" +} + +install_qemu() { + section "Build and install QEMU" + if [[ "$SKIP_QEMU" == 1 ]]; then + log "Skipping QEMU build" + return + fi + + local qemu_prefix="${BLUEOS_BIN_TOOLS_PREFIX}/qemu/${QEMU_VERSION}" + local qemu_bin="${qemu_prefix}/bin/qemu-system-aarch64" + local archive="qemu-${QEMU_VERSION}.tar.xz" + local source_dir="${BLUEOS_BIN_TOOLS_PREFIX}/src/qemu-${QEMU_VERSION}" + local build_dir="${source_dir}/build" + + if [[ -x "$qemu_bin" ]]; then + log "QEMU already installed: ${qemu_bin}" + return + fi + + log "Building QEMU ${QEMU_VERSION}" + run wget -c "https://download.qemu.org/${archive}" -O "${CACHE_DIR}/${archive}" + run tar -xJf "${CACHE_DIR}/${archive}" -C "${BLUEOS_BIN_TOOLS_PREFIX}/src" + run mkdir -p "$build_dir" + run bash -lc "cd \"${build_dir}\" && ../configure --prefix=\"${qemu_prefix}\" --enable-slirp" + run make -C "$build_dir" "-j$(nproc)" install +} + +write_env_file() { + section "Write environment file" + if [[ "$DRY_RUN" == 1 ]]; then + log "Would write environment file: ${ENV_FILE}" + return + fi + + cat >"$ENV_FILE" < Date: Thu, 2 Jul 2026 17:28:05 +0800 Subject: [PATCH 2/2] feat(tools): add error handle logic for boot scripts --- tools/build-blueos-rust-toolchain.sh | 47 +++++++++++++++++++++- tools/setup-blueos-base-env.sh | 59 +++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/tools/build-blueos-rust-toolchain.sh b/tools/build-blueos-rust-toolchain.sh index bde75511e..1ad1d7750 100755 --- a/tools/build-blueos-rust-toolchain.sh +++ b/tools/build-blueos-rust-toolchain.sh @@ -10,6 +10,7 @@ ENV_FILE="${SCRIPT_DIR}/blueos-env.sh" LINK_RUSTUP=0 DRY_RUN=0 USE_SSH=0 +CURRENT_STEP="startup" TARGETS=( aarch64-vivo-blueos-newlib @@ -44,7 +45,8 @@ log() { } section() { - local title="[blueos-rust] $*" + CURRENT_STEP="$*" + local title="[blueos-rust] ${CURRENT_STEP}" local width=78 local inner_width=$((width - 4)) local line @@ -60,13 +62,54 @@ die() { exit 1 } +print_recovery_hint() { + case "$CURRENT_STEP" in + "Load environment") + printf '[blueos-rust] recovery: check that --env-file is readable if you expect to load one, then rerun the script.\n' >&2 + ;; + "Check prerequisites") + printf '[blueos-rust] recovery: run setup-blueos-base-env.sh first, source the generated env file, and verify the BlueOS workspace exists.\n' >&2 + ;; + "Clone or update Rust sources") + printf '[blueos-rust] recovery: check git network/proxy access and permissions for %s, then rerun the script.\n' "$SRC_DIR" >&2 + ;; + "Build BlueOS Rust toolchain") + printf '[blueos-rust] recovery: inspect the Rust build error above. After fixing dependencies or source state, rerun this script; x.py will reuse completed work.\n' >&2 + ;; + "Register rustup toolchain") + printf '[blueos-rust] recovery: install rustup or rerun without --link-rustup, then source the generated env file directly.\n' >&2 + ;; + *) + printf '[blueos-rust] recovery: fix the command error above, then rerun the script.\n' >&2 + ;; + esac +} + +report_command_failure() { + local status="$1" + shift + + printf '[blueos-rust] error: command failed with exit code %s\n' "$status" >&2 + printf '[blueos-rust] failed step: %s\n' "$CURRENT_STEP" >&2 + printf '[blueos-rust] failed command:' >&2 + printf ' %q' "$@" >&2 + printf '\n' >&2 + print_recovery_hint +} + run() { if [[ "$DRY_RUN" == 1 ]]; then printf '[dry-run]' printf ' %q' "$@" printf '\n' else - "$@" + if "$@"; then + return 0 + else + local status="$?" + report_command_failure "$status" "$@" + exit "$status" + fi fi } diff --git a/tools/setup-blueos-base-env.sh b/tools/setup-blueos-base-env.sh index b9a4a6c3a..c0ac4e130 100755 --- a/tools/setup-blueos-base-env.sh +++ b/tools/setup-blueos-base-env.sh @@ -13,6 +13,7 @@ SKIP_QEMU=0 SKIP_REPO_SYNC=0 ARM_VERSION="14.3.rel1" QEMU_VERSION="10.0.2" +CURRENT_STEP="startup" APT_PACKAGES=( build-essential cmake ninja-build pkg-config libssl-dev gdb-multiarch @@ -47,7 +48,8 @@ log() { } section() { - local title="[blueos-base] $*" + CURRENT_STEP="$*" + local title="[blueos-base] ${CURRENT_STEP}" local width=78 local inner_width=$((width - 4)) local line @@ -63,13 +65,66 @@ die() { exit 1 } +print_recovery_hint() { + case "$CURRENT_STEP" in + "Check platform") + printf '[blueos-base] recovery: run this script on Ubuntu 24.04+ or Debian 12+.\n' >&2 + ;; + "Install distribution packages") + printf '[blueos-base] recovery: check apt/network/proxy/sudo, then rerun the script. Use --skip-apt only if packages are already installed.\n' >&2 + ;; + "Prepare install directories") + printf '[blueos-base] recovery: check that --prefix and the cache directory are writable directories, then rerun the script.\n' >&2 + ;; + "Install repo launcher") + printf '[blueos-base] recovery: check network/proxy access to the repo launcher mirror, then rerun the script.\n' >&2 + ;; + "Check GN") + printf '[blueos-base] recovery: install the distro package that provides gn/generate-ninja, then rerun the script.\n' >&2 + ;; + "Install ARM toolchains") + printf '[blueos-base] recovery: check network/proxy access to developer.arm.com. If an archive is partial, remove it from %s and rerun.\n' "$CACHE_DIR" >&2 + ;; + "Build and install QEMU") + printf '[blueos-base] recovery: check QEMU build dependencies and network access. If the source/build tree is partial, remove %s/src/qemu-%s and rerun.\n' "$BLUEOS_BIN_TOOLS_PREFIX" "$QEMU_VERSION" >&2 + ;; + "Write environment file") + printf '[blueos-base] recovery: check that --env-file points to a writable location, then rerun the script.\n' >&2 + ;; + "Initialize and sync BlueOS workspace") + printf '[blueos-base] recovery: check repo/git network access and workspace permissions. Rerun the script; use --skip-repo-sync only to defer syncing.\n' >&2 + ;; + *) + printf '[blueos-base] recovery: fix the command error above, then rerun the script.\n' >&2 + ;; + esac +} + +report_command_failure() { + local status="$1" + shift + + printf '[blueos-base] error: command failed with exit code %s\n' "$status" >&2 + printf '[blueos-base] failed step: %s\n' "$CURRENT_STEP" >&2 + printf '[blueos-base] failed command:' >&2 + printf ' %q' "$@" >&2 + printf '\n' >&2 + print_recovery_hint +} + run() { if [[ "$DRY_RUN" == 1 ]]; then printf '[dry-run]' printf ' %q' "$@" printf '\n' else - "$@" + if "$@"; then + return 0 + else + local status="$?" + report_command_failure "$status" "$@" + exit "$status" + fi fi }