Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .agents/skills/wavebench-plugin-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ metadata:

本流程面向 Codex 或兼容 Agent Skills 的宿主,仓库要求 Python 3.11+。验证使用已同步的 WaveBench 开发环境;实时仪器工作还需要明确授权、已确认接线和 Core 安全流程。

命令中的 `python` 指 Python 3.11+;仅提供 `python3` 的系统可替换命令名。虚拟环境示例使用 POSIX 的 `.venv/bin/python`,原生 Windows 替换为 `.venv\Scripts\python.exe`。

## Invocation boundary

仅在插件仓的生产交付物是主要任务时使用本 Skill,例如修改 `packages/wavebench-*` 下的 driver、descriptor、parser、profile、测试或打包配置,或者修改仓库级开发、目录生成、conformance 与实机证据工具。
Expand Down Expand Up @@ -92,7 +94,7 @@ metadata:
按改动风险逐层增加验证,不把全仓测试当作每次修改的起点:

```bash
python3 scripts/dev_env.py check
python scripts/dev_env.py check
.venv/bin/python -m pytest -q packages/<package>/tests/<focused-test>.py
.venv/bin/python -m ruff check <changed-paths>
python scripts/generate_plugin_catalog.py --check
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
根据变更逐级运行,前一级失败时先停止并处理根因:

```bash
python3 scripts/dev_env.py check
python scripts/dev_env.py check
.venv/bin/python -m pytest -q packages/<package>/tests/<focused-test>.py
.venv/bin/python -m pytest -q packages/<package>/tests
.venv/bin/python -m ruff check <changed-paths>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
先检查现有标准环境:

```bash
python3 scripts/dev_env.py check
python scripts/dev_env.py check
```

`check` 验证 Core 与所有正式插件的 metadata 快照、editable 安装和 entry point,不访问软件源,也不连接仪器。失败信息如果说明包集合或 metadata 漂移,再考虑:

```bash
python3 scripts/dev_env.py sync
python scripts/dev_env.py sync
```

`sync` 会创建或修改 `.venv`,以 editable 方式安装相邻 Core 与所有正式包,且 pip 可能访问已配置的软件源。运行前必须说明这些影响。已有但未标记的环境只能在审阅后显式接管;不要绕过脚本对 WaveBench 受管插件账本或未完成事务的拒绝。
`sync` 会创建或修改 `.venv`,以 editable 方式安装相邻 Core 与所有正式包,通过 PATH 中的 uv 创建环境并安装,可能访问已配置的软件源。运行前必须说明这些影响。已有但未标记的环境只能在审阅后显式接管;不要绕过脚本对 WaveBench 受管插件账本或未完成事务的拒绝。

开发环境只提供快速反馈,不是发行验收环境,也不能与受管 install/remove 流程混用。

Expand Down
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Plugin lifecycle

on:
pull_request:
push:
branches: [main]
workflow_dispatch:
inputs:
core_ref:
description: WaveBench Core branch or commit with uv lifecycle support
default: Dev
type: string

permissions:
contents: read

jobs:
lifecycle:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python: ["3.11", "3.12"]
env:
PYTHONUTF8: "1"
steps:
- uses: actions/checkout@v4
with:
path: wavebench-instrument-plugins
- uses: actions/checkout@v4
with:
repository: Scaxlibur/wavebench
ref: ${{ inputs.core_ref || 'Dev' }}
path: wavebench
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- uses: astral-sh/setup-uv@v9.0.0
with:
version: "0.12.15"
- name: Install editable development environment
working-directory: wavebench-instrument-plugins
run: python scripts/dev_env.py sync
- name: Verify development environment
working-directory: wavebench-instrument-plugins
run: python scripts/dev_env.py check
- name: Check code
working-directory: wavebench-instrument-plugins
run: uv run --no-project --python .venv python -m ruff check .
- name: Test development tools
working-directory: wavebench-instrument-plugins
run: uv run --no-project --python .venv python -m pytest -q tests/test_dev_env.py
- name: Test wheel compatibility and pip-free plugin lifecycle
working-directory: wavebench-instrument-plugins
run: uv run --no-project --python .venv python -m pytest -q packages -k "wheel or lifecycle"
12 changes: 8 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: python -m pip install "pytest>=8"
- run: python scripts/generate_plugin_catalog.py --check
- uses: astral-sh/setup-uv@v9.0.0
with:
version: "0.12.15"
- run: uv venv
- run: uv pip install --python .venv "pytest>=8"
- run: uv run --no-project --python .venv python scripts/generate_plugin_catalog.py --check
# Errors fail the pilot gate; known migration warnings remain visible.
- run: python scripts/audit_docs.py
- run: python -m pytest -q tests/test_docs_audit.py tests/test_plugin_catalog.py
- run: uv run --no-project --python .venv python scripts/audit_docs.py
- run: uv run --no-project --python .venv python -m pytest -q tests/test_docs_audit.py tests/test_plugin_catalog.py
33 changes: 27 additions & 6 deletions doc/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@

## 首次同步

需要 Python 3.11+ 和 PATH 中可用的 [uv](https://docs.astral.sh/uv/getting-started/installation/)。

下文的 `python` 指 Python 3.11+,Bash 与 PowerShell 使用相同命令;若系统仅提供 `python3`,替换该命令名即可。uv 不读取 `pip.conf` 或 `PIP_INDEX_URL`;使用镜像或私有软件源时,先配置 [uv 软件源](https://docs.astral.sh/uv/concepts/indexes/)。

默认目录布局为相邻的 `wavebench/` 和 `wavebench-instrument-plugins/`:

```bash
cd wavebench-instrument-plugins
python3 scripts/dev_env.py sync
python scripts/dev_env.py sync
```

脚本会创建仓库内被忽略的 `.venv/`,汇总并安装各项目声明的 build-system 依赖,安装 `wavebench[dev]` 和所有正式插件的 editable metadata,并通过真实 `wavebench.instruments` entry point 与 registry 解析验证环境。`sync` 会调用 pip,首次运行可能访问配置的软件源并安装依赖;它不是离线发布门禁。
脚本会创建仓库内被忽略的 `.venv/`,汇总并安装各项目声明的 build-system 依赖,以 editable 方式安装 Core 和所有正式插件及各自的 `[dev]` extras,并通过真实 `wavebench.instruments` entry point 与 registry 解析验证环境。`sync` 使用 `uv venv` 和 `uv pip install`,首次运行可能访问配置的软件源并安装依赖;它不是离线发布门禁。

若核心仓库不在默认相邻位置,应显式指定:

```bash
python3 scripts/dev_env.py --wavebench-root <wavebench-source> sync
python scripts/dev_env.py --wavebench-root <wavebench-source> sync
```

## 日常开发

```bash
.venv/bin/python -m pytest
.venv/bin/python -m ruff check .
python3 scripts/dev_env.py check
uv run --no-project --python .venv python -m pytest
uv run --no-project --python .venv python -m ruff check .
python scripts/dev_env.py check
```

源码修改不需要重新运行 `sync`。只有下列内容变化时才需再次同步:
Expand All @@ -38,8 +42,25 @@ python3 scripts/dev_env.py check

`check` 不安装软件、不访问仪器,也不联网;它会比较已记录的项目元数据,确认核心和插件仍为 editable 安装,要求仪器 entry point 集合与当前正式插件集合精确一致,并通过 WaveBench registry 解析每个 canonical driver ID。再次运行 `sync` 时,脚本会依据这个专用环境自己的旧状态记录卸载已经从仓库移除的正式插件。只有文档、没有 `pyproject.toml` 的孵化目录会被明确跳过。

根目录不维护独立的 uv project 或 lock;脚本按 Core 与各插件的声明解析 editable 开发依赖,
不等同于 Core 的锁定 CI 环境。不要在此环境执行 Core 的 `uv sync`,否则可能移除插件。
脚本仅卸载自身旧状态中已从仓库移除的插件,并保留对受管账本的拒绝检查。

已有的标记开发环境可继续同步;未标记环境须审阅后显式传入 `sync --adopt-existing`。接管不会清理任意旧包;需要干净环境时,通过 `--venv <新目录>` 创建独立环境,不覆盖原环境。

## 与正式验收的边界

editable 环境仅用于开发反馈,不代表可发布验收。提交或发布前仍需使用 WaveBench 的 `plugin package check`、真实 wheel、一次性虚拟环境和受管 install/remove 生命周期门禁。不要在同一虚拟环境中混用 editable 安装与 WaveBench 受管插件账本;脚本检测到账本或未完成事务时会拒绝操作。

uv 插件管理后端要求包含该变更的 Core `Dev`,操作与运行环境边界见 [Core 插件管理说明](https://github.com/Scaxlibur/wavebench/blob/Dev/docs/how-to/manage-plugins.md)。

`.gitattributes` 将文本文件的检出换行固定为 LF,保证 Windows 与 Linux 构建的源码和许可证字节一致。wheel 证据摘要绑定这些字节,构建前不应另外转换换行。

插件 descriptor 导入仍不得打开 transport。默认测试不得扫描端口、连接仪器或发送 SCPI;实机测试必须另行获得明确授权并使用脱敏配置。

## 跨仓 CI

`Plugin lifecycle` workflow 在 Linux/Windows 和 Python 3.11/3.12 上检查开发环境与
四类插件的无 pip 生命周期。默认使用相邻 Core 的 `Dev` 分支,该分支须已包含 uv 安装后端;
迁移阶段可通过手动运行的 `core_ref` 输入指定 Core 分支或提交。先合入 Core 后端,再启用插件仓的对应变更。
wheel 安装兼容测试继续使用 pip;构建步骤使用 uv,并保留既有 wheel 内容和证据摘要检查。
28 changes: 22 additions & 6 deletions doc/DEVELOPMENT_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@ This repository uses one long-lived, dedicated development virtual environment.

## First synchronization

Python 3.11+ and [uv](https://docs.astral.sh/uv/getting-started/installation/) on PATH are required.

Here, `python` means Python 3.11+; the commands work in Bash and PowerShell. Use `python3` instead if that is the only command available on the system. uv does not read `pip.conf` or `PIP_INDEX_URL`; configure [uv indexes](https://docs.astral.sh/uv/concepts/indexes/) first when a mirror or private index is required.

The default layout expects sibling `wavebench/` and `wavebench-instrument-plugins/` directories:

```bash
cd wavebench-instrument-plugins
python3 scripts/dev_env.py sync
python scripts/dev_env.py sync
```

The script creates the ignored repository-local `.venv/`, collects and installs each project's declared build-system requirements, installs `wavebench[dev]` and every maintained plugin as editable distributions, then verifies their real `wavebench.instruments` entry points and registry resolution. `sync` invokes pip and may access configured package indexes to install dependencies on its first run; it is not an offline release gate.
The script creates the ignored repository-local `.venv/`, collects and installs each project's declared build-system requirements, installs Core and every maintained plugin as editable distributions with their respective `[dev]` extras, then verifies their real `wavebench.instruments` entry points and registry resolution. `sync` uses `uv venv` and `uv pip install` and may access configured package indexes to install dependencies on its first run; it is not an offline release gate.

Specify a non-sibling core checkout explicitly:

```bash
python3 scripts/dev_env.py --wavebench-root <wavebench-source> sync
python scripts/dev_env.py --wavebench-root <wavebench-source> sync
```

## Daily development

```bash
.venv/bin/python -m pytest
.venv/bin/python -m ruff check .
python3 scripts/dev_env.py check
uv run --no-project --python .venv python -m pytest
uv run --no-project --python .venv python -m ruff check .
python scripts/dev_env.py check
```

Source edits do not require another `sync`. Synchronize again only when:
Expand All @@ -38,8 +42,20 @@ Source edits do not require another `sync`. Synchronize again only when:

`check` does not install software, access instruments, or use the network. It compares recorded project metadata, verifies editable core and plugin distributions, requires the instrument entry-point set to match the current maintained-plugin set exactly, and resolves every canonical driver ID through the WaveBench registry. On a later `sync`, the script uses this dedicated environment's own previous state record to uninstall maintained plugins that were removed from the repository. Documentation-only incubation directories without `pyproject.toml` are deliberately skipped.

The repository root is not a uv project and has no separate lockfile. The script resolves the declared Core and plugin requirements for editable development; this is not the Core locked CI environment. Do not run Core `uv sync` against this environment: it may remove plugins. The script only removes plugins listed in its previous state that no longer exist in the repository, and still rejects managed runtime ledgers.

An existing marked development environment can be synchronized again. An unmarked environment requires review and explicit `sync --adopt-existing`. Adoption does not remove arbitrary old packages. Use `--venv <new-directory>` for a clean, separate environment without replacing the original.

## Boundary with release acceptance

The editable environment is a development feedback loop, not release acceptance. Before committing or publishing a plugin, continue to use WaveBench `plugin package check`, a real wheel, a disposable virtual environment, and the managed install/remove lifecycle gate. Do not mix editable installs and the WaveBench managed-plugin ledger in one environment; the script refuses to operate when it detects a ledger or unfinished transaction.

The uv plugin backend requires Core `Dev` containing that change; see the [Core plugin management guide](https://github.com/Scaxlibur/wavebench/blob/Dev/docs/how-to/manage-plugins.md) for runtime setup and operation boundaries.

`.gitattributes` fixes text checkout line endings to LF so Windows and Linux builds preserve identical source and license bytes. Wheel evidence digests bind those bytes; do not convert line endings separately before building.

Importing a plugin descriptor must still perform no transport I/O. Default tests must not scan ports, connect to instruments, or send SCPI. Hardware tests require separate explicit authorization and sanitized configuration.

## Cross-repository CI

The `Plugin lifecycle` workflow checks development setup and four plugin types without pip on Linux/Windows with Python 3.11/3.12. It uses Core `Dev` by default, which must contain the uv lifecycle backend. During migration, use the manual `core_ref` input to select a Core branch or commit. Land the Core backend before the corresponding plugin repository changes. Wheel installation compatibility tests keep pip; build steps use uv and retain wheel-content and evidence-digest checks.
61 changes: 38 additions & 23 deletions packages/wavebench-rigol-dg4000/tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
import subprocess
import sys
import shutil
import sysconfig
import tarfile
from zipfile import ZipFile
Expand All @@ -28,15 +29,17 @@ def test_wheel_contains_license_and_expected_entry_points(tmp_path: Path) -> Non
wheelhouse.mkdir()
_run(
[
shutil.which("uv"),
"--no-config",
"--offline",
"--no-python-downloads",
"build",
"--wheel",
"--python",
sys.executable,
"-m",
"pip",
"wheel",
"--no-build-isolation",
"--no-deps",
"--no-index",
"--disable-pip-version-check",
"--wheel-dir",
"--out-dir",
str(wheelhouse),
str(PACKAGE_ROOT),
],
Expand Down Expand Up @@ -77,15 +80,17 @@ def test_wheel_contains_verified_source_conformance_manifests(tmp_path: Path) ->
wheelhouse.mkdir()
_run(
[
shutil.which("uv"),
"--no-config",
"--offline",
"--no-python-downloads",
"build",
"--wheel",
"--python",
sys.executable,
"-m",
"pip",
"wheel",
"--no-build-isolation",
"--no-deps",
"--no-index",
"--disable-pip-version-check",
"--wheel-dir",
"--out-dir",
str(wheelhouse),
str(PACKAGE_ROOT),
],
Expand Down Expand Up @@ -151,13 +156,15 @@ def test_sdist_excludes_vendor_manuals_and_contains_public_docs(tmp_path: Path)


def _run(command: list[str], *, cwd: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
completed = subprocess.run(
command,
cwd=cwd,
text=True,
capture_output=True,
check=True,
check=False,
)
assert completed.returncode == 0, completed.stdout + completed.stderr
return completed


def _write_isolated_runtime_bridge(*, purelib: Path, workspace: Path) -> None:
Expand All @@ -170,7 +177,13 @@ def _write_isolated_runtime_bridge(*, purelib: Path, workspace: Path) -> None:
or name.endswith((".dist-info", ".egg-info", ".pth", ".egg"))
):
continue
os.symlink(source, bridge / name, target_is_directory=source.is_dir())
try:
os.symlink(source, bridge / name, target_is_directory=source.is_dir())
except OSError:
if source.is_dir():
shutil.copytree(source, bridge / name)
else:
shutil.copy2(source, bridge / name)
Path(purelib, "wavebench-test-runtime.pth").write_text(
str(Path(wavebench.__file__).resolve().parents[1]) + "\n" + str(bridge) + "\n",
encoding="utf-8",
Expand All @@ -182,15 +195,17 @@ def test_wheel_install_migration_doctor_routing_and_uninstall_fallback(tmp_path:
wheelhouse.mkdir()
_run(
[
shutil.which("uv"),
"--no-config",
"--offline",
"--no-python-downloads",
"build",
"--wheel",
"--python",
sys.executable,
"-m",
"pip",
"wheel",
"--no-build-isolation",
"--no-deps",
"--no-index",
"--disable-pip-version-check",
"--wheel-dir",
"--out-dir",
str(wheelhouse),
str(PACKAGE_ROOT),
],
Expand All @@ -201,7 +216,7 @@ def test_wheel_install_migration_doctor_routing_and_uninstall_fallback(tmp_path:
)
venv_dir = tmp_path / "venv"
_run([sys.executable, "-m", "venv", str(venv_dir)], cwd=tmp_path)
python = venv_dir / "bin" / "python"
python = venv_dir / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python")
purelib = _run(
[str(python), "-c", "import sysconfig; print(sysconfig.get_paths()['purelib'])"],
cwd=tmp_path,
Expand Down Expand Up @@ -251,7 +266,7 @@ def forbidden(*args, **kwargs):
"""
_run([str(python), "-I", "-c", discovery_script], cwd=tmp_path)
_run(
[str(python), "-I", "-m", "wavebench", "plugin", "doctor", "--load"],
[str(python), "-I", "-X", "utf8", "-m", "wavebench", "plugin", "doctor", "--load"],
cwd=tmp_path,
)
assert lifecycle.remove("rigol.dg4202-v2").status == "removed"
Expand Down
Loading
Loading