Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -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 编译
71 changes: 71 additions & 0 deletions .github/scripts/prepare_env_layout.sh
Original file line number Diff line number Diff line change
@@ -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-source> [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"
120 changes: 120 additions & 0 deletions .github/scripts/rt_thread_compat.sh
Original file line number Diff line number Diff line change
@@ -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."
104 changes: 104 additions & 0 deletions .github/workflows/rt-thread-compat.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions plugins/tests/test_env_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
27 changes: 27 additions & 0 deletions plugins/tests/test_rt_thread_compat.py
Original file line number Diff line number Diff line change
@@ -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)