diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..f3b59da --- /dev/null +++ b/.github/README.md @@ -0,0 +1,36 @@ +# RT-Thread 关联兼容性 CI + +Env 仓库本身不包含 RT-Thread 源码,但 RT-Thread 会把本仓库安装到 `~/.env/tools/scripts`,再执行: + +- `source ~/.env/env.sh` +- `scons --pyconfig-silent` +- `pkgs --update` / `pkgs --list` +- `scons` + +`.github/workflows/rt-thread-compat.yml` 用当前 Env 提交搭出同一套目录布局,再拉取 `RT-Thread/rt-thread`、`packages` 和 `sdk`,确认这条引用链不会被 Env 侧改动打断。兼容脚本位于 `.github/scripts/`,因为它们只服务于 GitHub Actions 及其本地复现。 + +## 本地运行 + +不要覆盖正在使用的 `~/.env`。可以在一次性 HOME 下运行: + +```sh +export CI_HOME="$(mktemp -d)" +export HOME="$CI_HOME" +export RTT_ROOT=/path/to/rt-thread + +./.github/scripts/prepare_env_layout.sh "$(pwd)" "$HOME/.env" \ + /path/to/packages /path/to/sdk +./.github/scripts/rt_thread_compat.sh +``` + +兼容脚本会从 SDK 索引安装 `arm-none-eabi-gcc`,不需要手动设置 `RTT_EXEC_PATH`。脚本会编译这两个 BSP: + +- `bsp/qemu-vexpress-a9` +- `bsp/stm32/stm32f407-rt-spark` + +## 检查范围 + +- Env 单元测试(Python 3.8 / 3.12) +- 从 SDK 索引安装 `arm-none-eabi-gcc`,并通过 Env 的 SDK 状态发现工具链 +- `qemu-vexpress-a9` 的静默配置、包更新和 ARM GCC 编译 +- `stm32/stm32f407-rt-spark` 的静默配置、包更新和 ARM GCC 编译 diff --git a/.github/scripts/prepare_env_layout.sh b/.github/scripts/prepare_env_layout.sh new file mode 100755 index 0000000..61811fd --- /dev/null +++ b/.github/scripts/prepare_env_layout.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Create the ~/.env layout that RT-Thread CI and local builds expect, using +# the Env source under test instead of cloning env master. + +set -euo pipefail + +usage() { + echo "Usage: $0 [env-root] [packages-src] [sdk-src]" >&2 + exit 2 +} + +if [ "${1:-}" = "" ]; then + usage +fi + +ENV_SOURCE=$(cd -- "$1" && pwd) +ENV_ROOT=${2:-${ENV_ROOT:-$HOME/.env}} +PACKAGES_SRC=${3:-${PACKAGES_SRC:-}} +SDK_SRC=${4:-${SDK_SRC:-}} +PACKAGE_URL=${RTT_PACKAGE_URL:-https://github.com/RT-Thread/packages.git} +SDK_URL=${RTT_SDK_URL:-https://github.com/RT-Thread/sdk.git} + +mkdir -p "$ENV_ROOT/local_pkgs" "$ENV_ROOT/packages" "$ENV_ROOT/tools" + +if [ -e "$ENV_ROOT/tools/scripts" ] || [ -L "$ENV_ROOT/tools/scripts" ]; then + echo "Refusing to replace existing Env scripts directory: $ENV_ROOT/tools/scripts" >&2 + exit 1 +fi +if [ -e "$ENV_ROOT/env.sh" ]; then + echo "Refusing to replace existing Env activation script: $ENV_ROOT/env.sh" >&2 + exit 1 +fi +if [ -e "$ENV_ROOT/packages/Kconfig" ]; then + echo "Refusing to replace existing packages Kconfig: $ENV_ROOT/packages/Kconfig" >&2 + exit 1 +fi + +ln -s "$ENV_SOURCE" "$ENV_ROOT/tools/scripts" +cp "$ENV_SOURCE/env.sh" "$ENV_ROOT/env.sh" + +link_or_clone() { + dest=$1 + src=$2 + url=$3 + name=$4 + + if [ -d "$dest/.git" ] || [ -L "$dest" ]; then + echo "Using existing $name at $dest" + return 0 + fi + if [ -e "$dest" ]; then + echo "Refusing to replace existing $name path: $dest" >&2 + exit 1 + fi + if [ -n "$src" ]; then + src=$(cd -- "$src" && pwd) + ln -s "$src" "$dest" + echo "Linked $name from $src" + return 0 + fi + git clone --depth=1 "$url" "$dest" +} + +link_or_clone "$ENV_ROOT/packages/packages" "$PACKAGES_SRC" "$PACKAGE_URL" "packages index" +link_or_clone "$ENV_ROOT/packages/sdk" "$SDK_SRC" "$SDK_URL" "SDK index" + +printf 'source "$PKGS_DIR/packages/Kconfig"\n' > "$ENV_ROOT/packages/Kconfig" + +echo "Env layout ready:" +echo " ENV_ROOT=$ENV_ROOT" +echo " scripts=$ENV_ROOT/tools/scripts -> $ENV_SOURCE" diff --git a/.github/scripts/rt_thread_compat.sh b/.github/scripts/rt_thread_compat.sh new file mode 100755 index 0000000..6f6329d --- /dev/null +++ b/.github/scripts/rt_thread_compat.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# Exercise the Env layout the same way RT-Thread CI does: +# source env.sh, install the SDK toolchain, then configure, update packages, +# and build RT-Thread BSPs. + +set -euo pipefail + +ENV_ROOT=${ENV_ROOT:-$HOME/.env} +RTT_ROOT=${RTT_ROOT:?RTT_ROOT must point at an RT-Thread checkout} +QEMU_BSP=${QEMU_BSP:-bsp/qemu-vexpress-a9} +SPARK_BSP=${SPARK_BSP:-bsp/stm32/stm32f407-rt-spark} +SDK_TOOLCHAIN_VERSION=${SDK_TOOLCHAIN_VERSION:-v13.2.rel1} + +fail() { + echo "::error::$1" >&2 + exit 1 +} + +require_dir() { + [ -d "$1" ] || fail "missing directory: $1" +} + +require_file() { + [ -f "$1" ] || fail "missing file: $1" +} + +require_dir "$ENV_ROOT" +require_file "$ENV_ROOT/env.sh" +require_dir "$ENV_ROOT/tools/scripts" +require_dir "$RTT_ROOT" +require_file "$RTT_ROOT/$QEMU_BSP/Kconfig" +require_file "$RTT_ROOT/$SPARK_BSP/Kconfig" + +export ENV_ROOT +export ENV_VENV_AUTO_UPGRADE=${ENV_VENV_AUTO_UPGRADE:-1} +export RTT_ROOT + +# shellcheck disable=SC1091 +. "$ENV_ROOT/env.sh" + +command -v python3 >/dev/null || fail "python3 is not on PATH after sourcing env.sh" +command -v scons >/dev/null || fail "scons is not on PATH after sourcing env.sh" +command -v pkgs >/dev/null || fail "pkgs is not on PATH after sourcing env.sh" +command -v menuconfig >/dev/null || fail "menuconfig is not on PATH after sourcing env.sh" + +python3 -c 'import kconfiglib, requests, SCons' || fail "Env Python dependencies are missing after activation" + +if ! pkgs --printenv | grep -F "ENV_ROOT:$ENV_ROOT" >/dev/null; then + fail "pkgs --printenv did not report ENV_ROOT=$ENV_ROOT" +fi +if ! pkgs --printenv | grep -F "PKGS_ROOT:$ENV_ROOT/packages" >/dev/null; then + fail "pkgs --printenv did not report PKGS_ROOT=$ENV_ROOT/packages" +fi + +python3 "$ENV_ROOT/tools/scripts/env.py" -v +pkgs -h >/dev/null +menuconfig -h >/dev/null + +case "$SDK_TOOLCHAIN_VERSION" in + v13.2.rel1) + sdk_version_symbol=V132REL1 + ;; + v10.3) + sdk_version_symbol=V103 + ;; + *) + fail "unsupported SDK ARM GCC version: $SDK_TOOLCHAIN_VERSION" + ;; +esac + +sdk_config="$ENV_ROOT/tools/scripts/.config" +printf '%s\n' \ + 'CONFIG_TARGET_FILE=""' \ + 'CONFIG_PKG_USING_ARM_NONE_EABI_GCC=y' \ + 'CONFIG_PKG_ARM_NONE_EABI_GCC_PATH="sdk/Linux/arm-none-eabi-gcc"' \ + "CONFIG_PKG_USING_ARM_NONE_EABI_GCC_${sdk_version_symbol}=y" \ + "CONFIG_PKG_ARM_NONE_EABI_GCC_VER=\"$SDK_TOOLCHAIN_VERSION\"" \ + > "$sdk_config" + +echo "==> Install ARM GCC $SDK_TOOLCHAIN_VERSION from the SDK index" +( + cd "$ENV_ROOT/tools/scripts" + pkgs --update-force + pkgs --list +) + +sdk_toolchain_root="$ENV_ROOT/tools/scripts/packages/arm-none-eabi-gcc-$SDK_TOOLCHAIN_VERSION" +if [ -z "$sdk_toolchain_root" ] || [ ! -x "$sdk_toolchain_root/bin/arm-none-eabi-gcc" ]; then + fail "SDK did not install arm-none-eabi-gcc" +fi +"$sdk_toolchain_root/bin/arm-none-eabi-gcc" --version + +if [ -f "$RTT_ROOT/tools/requirements.txt" ]; then + python3 -m pip install -r "$RTT_ROOT/tools/requirements.txt" +fi + +run_bsp() { + bsp_rel=$1 + bsp_dir="$RTT_ROOT/$bsp_rel" + + echo "==> RT-Thread Env steps for $bsp_rel" + scons --pyconfig-silent -C "$bsp_dir" + require_file "$bsp_dir/.config" + require_file "$bsp_dir/rtconfig.h" + + ( + cd "$bsp_dir" + pkgs --update-force + pkgs --list + ) + require_file "$bsp_dir/packages/SConscript" + + echo "==> Build $bsp_rel" + scons -j"$(nproc)" -C "$bsp_dir" +} + +run_bsp "$QEMU_BSP" +run_bsp "$SPARK_BSP" + +echo "RT-Thread Env compatibility checks passed." diff --git a/.github/workflows/rt-thread-compat.yml b/.github/workflows/rt-thread-compat.yml new file mode 100644 index 0000000..6464e25 --- /dev/null +++ b/.github/workflows/rt-thread-compat.yml @@ -0,0 +1,104 @@ +name: RT-Thread Env Compatibility + +on: + push: + branches: + - master + paths-ignore: + - '**/README.md' + - '**/README.zh-CN.md' + pull_request: + branches: + - master + paths-ignore: + - '**/README.md' + - '**/README.zh-CN.md' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + ENV_VENV_AUTO_UPGRADE: '1' + +jobs: + unit-tests: + name: Unit tests (${{ matrix.python-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + python-version: ['3.8', '3.12'] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install test dependencies + run: | + python -m pip install --upgrade pip + python -m pip install SCons requests psutil tqdm kconfiglib PyYAML + + - name: Run Env unit tests + run: python -m unittest discover -s plugins/tests -t . -v + + rt-thread-compat: + name: RT-Thread reference compatibility + runs-on: ubuntu-22.04 + steps: + - name: Checkout Env + uses: actions/checkout@v4 + with: + path: env + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + + - name: Install host build packages + run: sudo apt-get update && sudo apt-get -qq install -y git python3-venv + + - name: Checkout RT-Thread + uses: actions/checkout@v4 + with: + repository: RT-Thread/rt-thread + path: rt-thread + fetch-depth: 1 + + - name: Checkout packages index + uses: actions/checkout@v4 + with: + repository: RT-Thread/packages + path: packages-index + fetch-depth: 1 + + - name: Checkout SDK index + uses: actions/checkout@v4 + with: + repository: RT-Thread/sdk + path: sdk-index + fetch-depth: 1 + + - name: Prepare Env layout used by RT-Thread + run: | + chmod +x env/.github/scripts/*.sh env/pkgs env/menuconfig env/sdk + env/.github/scripts/prepare_env_layout.sh \ + "$GITHUB_WORKSPACE/env" \ + "$HOME/.env" \ + "$GITHUB_WORKSPACE/packages-index" \ + "$GITHUB_WORKSPACE/sdk-index" + + - name: Run RT-Thread compatibility checks + env: + RTT_ROOT: ${{ github.workspace }}/rt-thread + run: | + export ENV_ROOT="$HOME/.env" + env/.github/scripts/rt_thread_compat.sh diff --git a/plugins/tests/test_env_venv.py b/plugins/tests/test_env_venv.py index 6e1b993..ddc4dc8 100644 --- a/plugins/tests/test_env_venv.py +++ b/plugins/tests/test_env_venv.py @@ -195,11 +195,11 @@ def test_missing_package_is_repaired_without_upgrade_prompt(self): self.assertTrue(layout['rt_env'].is_file()) def test_country_detection_and_explicit_index_override(self): - with mock.patch.object(env_venv, 'urlopen', return_value=FakeResponse(b'CN\n')) as request: + with mock.patch.object(env_venv, '_open_without_proxy', return_value=FakeResponse(b'CN\n')) as request: self.assertEqual(env_venv.detect_country(), 'CN') self.assertEqual(request.call_args[1]['timeout'], 3) - with mock.patch.object(env_venv, 'urlopen', side_effect=OSError('offline')): + with mock.patch.object(env_venv, '_open_without_proxy', side_effect=OSError('offline')): self.assertIsNone(env_venv.detect_country()) output = io.StringIO() diff --git a/plugins/tests/test_rt_thread_compat.py b/plugins/tests/test_rt_thread_compat.py new file mode 100644 index 0000000..5d22539 --- /dev/null +++ b/plugins/tests/test_rt_thread_compat.py @@ -0,0 +1,27 @@ +import unittest +from pathlib import Path + + +REPOSITORY = Path(__file__).resolve().parents[2] + + +class RtThreadCompatScriptTest(unittest.TestCase): + def test_compatibility_uses_the_sdk_toolchain_flow(self): + text = (REPOSITORY / '.github/scripts/rt_thread_compat.sh').read_text(encoding='utf-8') + self.assertIn('CONFIG_PKG_USING_ARM_NONE_EABI_GCC', text) + self.assertIn('pkgs --update-force', text) + self.assertIn('arm-none-eabi-gcc-$SDK_TOOLCHAIN_VERSION', text) + self.assertNotIn('RTT_EXEC_PATH', text) + self.assertNotIn('/opt/', text) + + def test_workflow_does_not_install_a_toolchain_outside_the_sdk(self): + text = (REPOSITORY / '.github/workflows/rt-thread-compat.yml').read_text(encoding='utf-8') + self.assertIn('Checkout SDK index', text) + self.assertNotIn('RTT_EXEC_PATH', text) + self.assertNotIn('/opt/', text) + self.assertNotIn('toolchains-ci/releases', text) + + def test_compat_script_covers_qemu_and_rt_spark(self): + text = (REPOSITORY / '.github/scripts/rt_thread_compat.sh').read_text(encoding='utf-8') + self.assertIn('bsp/qemu-vexpress-a9', text) + self.assertIn('bsp/stm32/stm32f407-rt-spark', text)