diff --git a/.github/native-dependencies.json b/.github/native-dependencies.json index 477b1d1..a411c6a 100644 --- a/.github/native-dependencies.json +++ b/.github/native-dependencies.json @@ -1,8 +1,8 @@ { "xcomm": { "repository": "XS-MLVP/xcomm", - "ref": "c39db6652cd08d46df05d08f0bb5a033b83e4d2f", - "package_version": "0.3.0.dev3", + "ref": "69e993d4fc0c8786819d2beae50a2197ea4ab124", + "package_version": "1.1.0", "native_abi": 3, "coverage_protocol": 5 } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4af38d8..7c31fda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,14 +61,25 @@ jobs: run: | sudo apt-get update sudo apt-get install -y --no-install-recommends build-essential cmake ninja-build lcov verilator - python -m pip install 'swig>=4.2,<5' '.[test]' - - name: Install pinned native backend - run: python -m pip install ./_deps/xcomm + python -m pip install 'build>=1,<2' + - name: Build pinned native backend + # This wheel is reused by another runner during isolated installation. + run: python -m build --wheel ./_deps/xcomm --outdir native-wheel -Ccmake.define.XSPCOMM_NATIVE=OFF env: BUILD_XSPCOMM_SWIG: python XSPCOMM_BUILD_WHEEL: "1" CMAKE_BUILD_PARALLEL_LEVEL: "2" - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: ${{ steps.native.outputs.package_version }} + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_UC_XCOMM: ${{ steps.native.outputs.package_version }} + - name: Install Toffee with the pinned dependency + run: | + python -m pip install native-wheel/*.whl '.[test]' + python -m pip check + - uses: actions/upload-artifact@v4 + if: matrix.python-version == '3.12' + with: + name: ${{ inputs.artifact-name || 'toffee-dist' }}-native-cp312 + path: native-wheel/*.whl + if-no-files-found: error - name: Require the tested package, native ABI and coverage protocol env: EXPECTED_PACKAGE_VERSION: ${{ steps.native.outputs.package_version }} @@ -78,7 +89,7 @@ jobs: python - <<'PY' from importlib.metadata import version import os, xspcomm - assert version('xspcomm') == os.environ['EXPECTED_PACKAGE_VERSION'] + assert version('uc-xcomm') == os.environ['EXPECTED_PACKAGE_VERSION'] assert xspcomm.version() == os.environ['EXPECTED_PACKAGE_VERSION'] assert xspcomm.abi_version() == int(os.environ['EXPECTED_NATIVE_ABI']) assert xspcomm.XTriggerEngine.CoverageVersion() == int(os.environ['EXPECTED_COVERAGE_PROTOCOL']) @@ -111,7 +122,7 @@ jobs: with: python-version: "3.12" cache: pip - - run: python -m pip install '.[docs]' + - run: python -m pip install 'mkdocs>=1.6' - run: mkdocs build --strict package: @@ -137,18 +148,22 @@ jobs: - name: Rebuild from the source distribution without Git metadata run: | mkdir -p "$RUNNER_TEMP/sdist-source" "$RUNNER_TEMP/sdist-wheel" - tar -xf dist/pytoffee-*.tar.gz -C "$RUNNER_TEMP/sdist-source" - source_dir=("$RUNNER_TEMP"/sdist-source/pytoffee-*) + tar -xf dist/uc_toffee-*.tar.gz -C "$RUNNER_TEMP/sdist-source" + source_dir=("$RUNNER_TEMP"/sdist-source/uc_toffee-*) python -m build --wheel "${source_dir[0]}" --outdir "$RUNNER_TEMP/sdist-wheel" original=(dist/*.whl) rebuilt=("$RUNNER_TEMP"/sdist-wheel/*.whl) test "$(basename "${original[0]}")" = "$(basename "${rebuilt[0]}")" + - uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name || 'toffee-dist' }}-native-cp312 + path: native-wheel - name: Install the wheel in an isolated environment run: | python -m venv "$RUNNER_TEMP/wheel-test" - "$RUNNER_TEMP/wheel-test/bin/python" -m pip install --no-index dist/*.whl + "$RUNNER_TEMP/wheel-test/bin/python" -m pip install --no-index --find-links=native-wheel dist/*.whl "$RUNNER_TEMP/wheel-test/bin/python" -m pip check - "$RUNNER_TEMP/wheel-test/bin/python" -I -c 'from importlib.metadata import version; from importlib.resources import files; import toffee; assert files("toffee").joinpath("templates/unified_coverage.html").is_file(); print(version("pytoffee"), toffee.__file__)' + "$RUNNER_TEMP/wheel-test/bin/python" -I -c 'from importlib.metadata import version; from importlib.resources import files; import toffee; assert files("toffee").joinpath("templates/unified_coverage.html").is_file(); print(version("uc-toffee"), toffee.__file__)' - name: Record exact tested build provenance env: EXPECTED_VERSION: ${{ inputs.expected-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69dd1a6..1699df9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,3 +102,25 @@ jobs: files: release-assets/* fail_on_unmatched_files: true overwrite_files: false + + pypi: + needs: [resolve, publish] + runs-on: ubuntu-24.04 + environment: + name: pypi + url: https://pypi.org/p/uc-toffee + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: release-dist-${{ needs.resolve.outputs.tag }} + path: verified-assets + - name: Select the verified Python distributions + run: | + mkdir dist + cp verified-assets/*.whl verified-assets/*.tar.gz dist/ + - name: Publish with Trusted Publishing + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6d7062..04afd11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,15 @@ Toffee 负责 Python API、仿真调度、验证组件和报告。 ## 提交前检查 -先安装开发依赖: +先准备 `uc-xcomm>=1.1,<2`,再安装开发依赖。开发跨仓库未发布修改时, +按 `.github/native-dependencies.json` 检出 xcomm 的固定提交,再运行: + +```bash +SETUPTOOLS_SCM_PRETEND_VERSION_FOR_UC_XCOMM=1.1.0 \ + python3 -m pip install /path/to/xcomm +``` + +这里的版本覆盖仅用于本地和 CI 验证目标依赖范围,不发布该测试包。随后安装开发依赖: ```bash python3 -m pip install -e '.[test,docs]' diff --git a/README.md b/README.md index ac6cbf8..d080aa3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Toffee +[![PyPI](https://img.shields.io/pypi/v/uc-toffee?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/uc-toffee/) +[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12-blue?style=flat-square&logo=python&logoColor=white)](https://github.com/XS-MLVP/toffee/blob/main/docs/getting-started/installation.md) +[![CI](https://img.shields.io/github/actions/workflow/status/XS-MLVP/toffee/ci.yml?branch=main&style=flat-square&label=CI&logo=github)](https://github.com/XS-MLVP/toffee/actions/workflows/ci.yml?query=branch%3Amain) +[![License](https://img.shields.io/badge/license-MulanPSL--2.0-blue?style=flat-square)](https://github.com/XS-MLVP/toffee/blob/main/LICENSE) +[![Docs](https://img.shields.io/badge/docs-Guide-0A7EA4?style=flat-square)](https://github.com/XS-MLVP/toffee/blob/main/docs/index.md) +[![GitHub Stars](https://img.shields.io/github/stars/XS-MLVP/toffee?style=social)](https://github.com/XS-MLVP/toffee/tree/main) + Toffee 是一个 Python 硬件验证框架。用 `async` / `await` 描述输入时序与条件等待, 用 pytest 运行测试,再通过参考模型、结果检查和覆盖率构建完整的验证环境。 @@ -9,7 +16,9 @@ Toffee 是一个 Python 硬件验证框架。用 `async` / `await` 描述输入 ## 快速开始 -需要 **Python 3.11+**。在仓库根目录创建环境并从源码安装: +需要 **Python 3.11+**。PyPI 分发名为 `uc-toffee`,导入名为 `toffee`, +原生绑定由依赖 `uc-xcomm` 提供。可用 `python3 -m pip install "uc-toffee[runner]"` 安装。 +下面在仓库根目录创建环境并从源码安装,以便运行仓库自带的例子: ```bash python3 -m venv .venv @@ -45,19 +54,19 @@ async def test_one_input_and_idle(): dut.close() ``` -这个用例已包含在[示例文件](examples/getting_started/test_accumulator.py)中,可以直接运行: +这个用例已包含在[示例文件](https://github.com/XS-MLVP/toffee/blob/main/examples/getting_started/test_accumulator.py)中,可以直接运行: ```bash python3 -m pytest -q examples/getting_started/test_accumulator.py::test_one_input_and_idle ``` -预期得到 `1 passed`。继续阅读[累加器教程](docs/getting-started/index.md), +预期得到 `1 passed`。继续阅读[累加器教程](https://github.com/XS-MLVP/toffee/blob/main/docs/getting-started/index.md), 逐步加入边界测试、Driver、Monitor、参考模型与覆盖率。 ## 用于项目验证 接入真实 RTL 时,使用 Picker 生成的 DUT Python 包,通过 `XCommClockBackend` -连接 xspcomm 的时钟与信号。具体准备和依赖要求见[真实 DUT 接入指南](docs/getting-started/native-and-next.md)。 +连接 xspcomm 的时钟与信号。具体准备和依赖要求见[真实 DUT 接入指南](https://github.com/XS-MLVP/toffee/blob/main/docs/getting-started/native-and-next.md)。 测试增多后,用 `toffee run` 并行执行并汇总覆盖率。例如,运行整个入门示例目录: @@ -66,15 +75,15 @@ toffee run examples/getting_started --workers 4 ``` 终端会显示本次运行的产物目录;有覆盖数据时会生成 `coverage.html`。 -将示例路径换成项目测试目录即可运行自己的回归,更多选项见[回归运行指南](docs/guides/runner.md)。 +将示例路径换成项目测试目录即可运行自己的回归,更多选项见[回归运行指南](https://github.com/XS-MLVP/toffee/blob/main/docs/guides/runner.md)。 ## 文档 -- [入门教程](docs/getting-started/index.md):从一条断言到完整的验证环境。 -- [操作指南](docs/guides/index.md):接入 DUT、组织组件、采集覆盖率和运行回归。 -- [接口参考](docs/reference/index.md):API、命令行参数和行为限制。 -- [原理说明](docs/explanation/index.md):仿真调度、资源归属与覆盖率含义。 -- [可运行示例](docs/examples.md) · [参与开发](CONTRIBUTING.md) +- [入门教程](https://github.com/XS-MLVP/toffee/blob/main/docs/getting-started/index.md):从一条断言到完整的验证环境。 +- [操作指南](https://github.com/XS-MLVP/toffee/blob/main/docs/guides/index.md):接入 DUT、组织组件、采集覆盖率和运行回归。 +- [接口参考](https://github.com/XS-MLVP/toffee/blob/main/docs/reference/index.md):API、命令行参数和行为限制。 +- [原理说明](https://github.com/XS-MLVP/toffee/blob/main/docs/explanation/index.md):仿真调度、资源归属与覆盖率含义。 +- [可运行示例](https://github.com/XS-MLVP/toffee/blob/main/docs/examples.md) · [参与开发](https://github.com/XS-MLVP/toffee/blob/main/CONTRIBUTING.md) ## Toffee Legacy diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 96bd925..4114bb5 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -15,8 +15,12 @@ cd toffee 以下所有命令都在这个目录运行。不要进入 `src/` 或 `examples/` 再执行。 -分发包名是 `pytoffee`,Python 导入名是 `toffee`。Toffee 2 当前从源码安装; -PyPI 上的旧包不等同于这条新主线。需要固定版本时可检出 `v2.0.0` 标签。 +分发包名是 `uc-toffee`,Python 导入名是 `toffee`。可以用 +`python3 -m pip install "uc-toffee[runner]"` 安装;它会自动安装 `uc-xcomm>=1.1,<2`, +由后者提供 `xspcomm` 原生绑定。旧 `pytoffee` 对应 Legacy,不是这条新主线。 + +下面从源码安装,以便运行仓库中的教程。 +开发未发布版本时,按[贡献指南](https://github.com/XS-MLVP/toffee/blob/main/CONTRIBUTING.md)准备固定提交的原生依赖。 ## 安装到独立的 Python 环境 @@ -48,4 +52,5 @@ python3 -c 'import toffee; print(toffee.__file__)' | 后续提示 `No module named examples` / `file not found` | 回到仓库根目录,使用文档中的 `python3 -m pytest ...` | 先继续 [1. 第一次 PASS 和 FAIL](first-test.md)。 -需要原生 XClock 时再看 [接入 native](native-and-next.md),不必在这里被 绑定 构建挡住。 +需要使用原生 XClock 时再看 [接入 native](native-and-next.md)。匹配的预编译包无需本地编译; +其他环境安装 `uc-xcomm` 源码包时,需要系统提供 C++20 编译器、CMake 和 Python 开发头文件。 diff --git a/docs/getting-started/native-and-next.md b/docs/getting-started/native-and-next.md index 8e3b9c5..511c7bb 100644 --- a/docs/getting-started/native-and-next.md +++ b/docs/getting-started/native-and-next.md @@ -5,7 +5,7 @@ ## 先确认加载的是哪个 绑定 -Toffee 不附带 xspcomm。原生后端需要 `XClock.StepHalf`、`GetHalfTick`、`GetPhase`、 +Toffee 通过常规依赖 `uc-xcomm` 安装 `xspcomm` 绑定。原生后端需要 `XClock.StepHalf`、`GetHalfTick`、`GetPhase`、 `XData` 和 `XTriggerEngine` 等接口。开发使用的提交、包版本、ABI 和协议版本以 [依赖清单](https://github.com/XS-MLVP/toffee/blob/main/.github/native-dependencies.json)为准, 不要只按包名判断版本是否匹配。安装到当前 Python 环境后,先运行: diff --git a/docs/guides/toffee-v2-migration.md b/docs/guides/toffee-v2-migration.md index e562d91..37168da 100644 --- a/docs/guides/toffee-v2-migration.md +++ b/docs/guides/toffee-v2-migration.md @@ -3,13 +3,15 @@ Toffee 2 基于 XReactor 1.1.0 的实现迁入,提供驱动、监视、参考模型、结果检查和覆盖率。 **Toffee Legacy(旧版)的实现与提交历史保留在 [`master`](https://github.com/XS-MLVP/toffee/tree/master);新实现位于 `main`。** -两代实现的安装包名都是 `pytoffee`,Python 导入名都是 `toffee`,但 API 与运行语义不兼容。 +旧版安装包名为 `pytoffee`,新版为 `uc-toffee`;Python 导入名都为 `toffee`,但 API 与运行语义不兼容。 +两代实现应放在不同的虚拟环境中。 旧验证环境需要按本文调整运行作用域、信号绑定、模型接线和采样语义。 本次迁移基线为 [XReactor 1.1.0 `3017293`](https://github.com/XS-MLVP/xreactor/tree/3017293a7597c39adbae883e15d63d727bcbf6ae) 和 [旧 Toffee `f57dd6f`](https://github.com/XS-MLVP/toffee/tree/f57dd6ffc295b9b8bad3e3b3ce040b8a849f5c91)。 旧版本实际发布在 `0.x` 系列,本文使用“旧版”指代,不将其称为已发布的 1.x。 -Toffee 2 暂未发布到 PyPI,请从 `main` 或 `v2.0.0` 标签安装。 +Toffee 2 从 `2.0.0` 开始以 `uc-toffee` 分发,可用 +`python3 -m pip install "uc-toffee[runner]"` 安装。 ## 旧项目如何选择 @@ -18,7 +20,7 @@ Toffee 2 暂未发布到 PyPI,请从 `main` 或 `v2.0.0` 标签安装。 迁移到 Toffee 2 时: -1. 建立独立的 Python 3.11+ 环境,从 `main` 或 `v2.0.0` 标签安装。 +1. 建立独立的 Python 3.11+ 环境,运行 `python3 -m pip install "uc-toffee[runner]"`。 2. 按下表迁移测试入口、Bundle、Driver、Monitor 与参考模型接线。 3. 根据采样和条件等待章节检查周期边界、事务完成条件与清理行为。 4. 运行项目已有回归,验证结果后再更新项目依赖。 @@ -28,7 +30,7 @@ Toffee 2 暂未发布到 PyPI,请从 `main` 或 `v2.0.0` 标签安装。 ## 从 XReactor 1.1 迁入 Python 导入由 `xreactor` 改为 `toffee`,命令由 `xreactor`、`xreactor-coverage-report` -改为 `toffee`、`toffee-coverage-report`;分发包名为 `pytoffee`。 +改为 `toffee`、`toffee-coverage-report`;分发包名为 `uc-toffee`。 底层公开类 `XReactor` 更名为 `Reactor`。项目中的 `pytest_xreactor_coverage` hook、 `xreactor_register_dut` fixture 和 `xreactor_functional_coverage` marker 分别改用 `toffee` 前缀。 框架自带的 runner 继续使用 pytest、pytest-asyncio 和 pytest-xdist,无需 `toffee-test`。 diff --git a/docs/guides/versioning-and-releases.md b/docs/guides/versioning-and-releases.md index a5a515b..282db75 100644 --- a/docs/guides/versioning-and-releases.md +++ b/docs/guides/versioning-and-releases.md @@ -4,14 +4,13 @@ Toffee 的版本来自 Git tag,正式版本使用 `vX.Y.Z`。维护者不需 版本字符串:例如 `v1.0.0` 构建为 `1.0.0`,后续未打 tag 的提交构建为开发版本。 `setuptools-scm` 只参与构建,使用已安装的包不需要 Git 或该工具。 -## 首次迁入与手动标签 +## 新版与 Legacy 的版本 -Toffee 2 的首次迁入保留 XReactor 的提交历史,以手动标签 `v2.0.0` 标记源码版本。 +Toffee 2 的 `main` 保留 XReactor 的提交历史,首次通过合并 PR 发布为 `v2.0.0`, +对应 PyPI 分发包 `uc-toffee==2.0.0`。 旧 `master` 与 `0.x` 标签保持原样,XReactor 的 `1.x` 标签保留在原仓库。 -首次迁入是直接推送,因此不满足下文发布工作流要求的“本仓库已合并 PR”来源校验; -该标签触发的自动发布任务可能失败,但不会改变标签或源码。此次不发布 PyPI 包, -也不以 GitHub Release 资产上传作为源码迁移的完成条件。 -后续通过 `main` 的正常 PR 继续使用现有版本与发布流程,版本从 `2.0.0` 递增。 +Legacy 的标签不参与新 `main` 的版本计算,旧 PyPI 项目 `pytoffee` 也不由新版流程发布。 +后续版本按合并 PR 的增量标签从 `2.0.0` 递增。 ## 提交与版本选择 @@ -23,8 +22,9 @@ Toffee 2 的首次迁入保留 XReactor 的提交历史,以手动标签 `v2.0. | `release:minor` | 新增公共能力,保留已有调用方式 | 1.1.0 | | `release:major` | 移除接口或改变已有语义 | 2.0.0 | -没有正式 tag 时从 `0.0.0` 计算增量。标签缺失或同时选择多个增量会使版本检查失败。 -版本工作流针对默认分支 `main` 运行。 +`main` 第一父链上没有正式 tag 时,首次版本固定为 `2.0.0`;后续才应用版本增量。 +每个 PR 仍须选择一个增量标签,缺失或同时选择多个增量会使版本检查失败。 +版本工作流针对仓库默认分支 `main` 运行;`master` 保留为 Legacy。 PR 合并后,Version policy 工作流确认目标提交是 `main` 第一父链上的实际 PR 合并 结果,再创建带注释的 tag。分支中的 tag、PR 内部提交和直接 push 不触发自动发布。 @@ -68,7 +68,8 @@ Tagged release 接受 tag push、Version policy 的 dispatch 或维护者手动 发布前再次核对清单、当前 tag、依赖、run ID 和文件集合。已有发布资产不覆盖; 若已有文件与本次验证产物完全一致,可以补齐缺失资产;校验和不同则拒绝混合发布。 发布中断时优先只重跑失败的发布 job,复用原 CI artifact。 -当前流程发布到 GitHub Releases,PyPI 发布尚未配置。 +GitHub Release 成功后,使用同一组 wheel 和源码包发布到 PyPI 的 `uc-toffee`。 +校验清单与原生依赖测试 wheel 保留为 CI/GitHub 资产,不上传到 `uc-toffee` 的 PyPI 项目。 普通 CI 也保留分发包 artifact,便于提交评审;覆盖数据库、日志和源码覆盖 HTML 继续作为本地产物,不上传。 @@ -76,6 +77,26 @@ Version policy 使用 `repository_dispatch` 启动后续验证,因为由 `GITH 创建的 tag push 不会自动启动其他工作流,而 dispatch 支持这种调用。 见 [GitHub 触发规则](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow)。 +## PyPI 配置 + +先发布满足依赖范围的 `uc-xcomm`,再发布 `uc-toffee`。首次在 +[PyPI Publishing](https://pypi.org/manage/account/publishing/) 添加 pending GitHub publisher: + +| 字段 | 值 | +| --- | --- | +| Project name | `uc-toffee` | +| Owner | `XS-MLVP` | +| Repository | `toffee` | +| Workflow filename | `release.yml` | +| Environment | `pypi` | + +在 GitHub 创建同名 `pypi` 环境,并允许该环境从 `main` 或经过验证的发行标签部署。 +不需要 PyPI API token。首次成功上传时创建项目;配置完成前,PyPI job 可以失败, +补齐后重跑该 job 即可。已有版本不能用不同内容覆盖。 + +`repository_dispatch` 使用仓库默认分支上的工作流,因此默认分支须保持为 `main`。 +Legacy `master` 的源码和旧 `pytoffee` 发布配置独立保留。 + ## 首次启用与失败恢复 仓库管理员先创建三个 `release:*` 标签,并将 `CI / test` 的两个 Python job、 @@ -101,7 +122,7 @@ python3 scripts/release/verify_dist.py --manifest --verify-manifest git diff --check ``` -读取安装版本使用 `importlib.metadata.version("pytoffee")`。发布时 CI 必须具有完整 +读取安装版本使用 `importlib.metadata.version("uc-toffee")`。发布时 CI 必须具有完整 Git 历史;源码包已经包含构建时确定的版本,在解包后重建不依赖原仓库。 没有 Git 和源码包元数据的普通源码副本才使用 `2.0.0.dev0` fallback。 配置依据见 [setuptools-scm 文档](https://setuptools-scm.readthedocs.io/en/v9.2.0/usage/)。 diff --git a/pyproject.toml b/pyproject.toml index 096190b..c60227d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=77", "setuptools-scm>=8,<10"] build-backend = "setuptools.build_meta" [project] -name = "pytoffee" +name = "uc-toffee" dynamic = ["version"] description = "Asyncio-native simulation triggers backed by xcomm" readme = "README.md" @@ -11,7 +11,7 @@ requires-python = ">=3.11" license = "MulanPSL-2.0" license-files = ["LICENSE"] authors = [{name = "XS-MLVP"}] -dependencies = [] +dependencies = ["uc-xcomm>=1.1,<2"] [project.optional-dependencies] runner = [ @@ -19,9 +19,6 @@ runner = [ "pytest-asyncio>=0.23", "pytest-xdist>=3.6,<4", ] -native = [ - "xspcomm>=0.3.0.dev3,<0.4", -] test = [ "pytest>=8", "pytest-asyncio>=0.23", diff --git a/scripts/release/verify_dist.py b/scripts/release/verify_dist.py index 527e2a8..e21f2e6 100644 --- a/scripts/release/verify_dist.py +++ b/scripts/release/verify_dist.py @@ -40,8 +40,8 @@ def distributions(directory: Path, expected: str | None = None) -> tuple[str, li for name in archive.getnames()): raise ValueError("Source distribution is missing the coverage report template") version = wheel_meta["Version"] - if wheel_meta["Name"] != "pytoffee" or source_meta["Name"] != "pytoffee": - raise ValueError("Distribution package name must be pytoffee") + if wheel_meta["Name"] != "uc-toffee" or source_meta["Name"] != "uc-toffee": + raise ValueError("Distribution package name must be uc-toffee") if not version or version != source_meta["Version"] or (expected and version != expected): raise ValueError(f"Package versions disagree: wheel={version}, " f"sdist={source_meta['Version']}, expected={expected}") diff --git a/scripts/release/version_policy.py b/scripts/release/version_policy.py index facad35..d7bd0ef 100644 --- a/scripts/release/version_policy.py +++ b/scripts/release/version_policy.py @@ -64,14 +64,15 @@ def next_tag(bump: str, commit: str = "HEAD", base: str = "origin/main") -> str: raise ValueError(f"Multiple release tags already point to {sha}: {existing}") if existing: return existing[0] # Retry dispatch without creating another version. - current = (0, 0, 0) + # Legacy tags belong to a separate history; the new Toffee line starts at 2.0.0. + following = (2, 0, 0) if releases: current, previous, _ = max(releases) if subprocess.run(["git", "merge-base", "--is-ancestor", previous, sha]).returncode: raise ValueError(f"{sha} does not descend from latest release {previous}") - major, minor, patch = current - following = {"patch": (major, minor, patch + 1), - "minor": (major, minor + 1, 0), "major": (major + 1, 0, 0)}[bump] + major, minor, patch = current + following = {"patch": (major, minor, patch + 1), + "minor": (major, minor + 1, 0), "major": (major + 1, 0, 0)}[bump] tag = "v" + ".".join(map(str, following)) if tag in git("tag", "--list", tag).splitlines(): raise ValueError(f"{tag} already exists outside the release history") diff --git a/src/toffee/cli.py b/src/toffee/cli.py index 41f87ac..0469de0 100644 --- a/src/toffee/cli.py +++ b/src/toffee/cli.py @@ -24,7 +24,7 @@ def main(argv: list[str] | None = None) -> int: if args.workers < 1: parser.error("--workers must be positive") if find_spec("pytest") is None or args.workers > 1 and find_spec("xdist") is None: - parser.error('install the runner dependencies with pip install "pytoffee[runner]"') + parser.error('install the runner dependencies with pip install "uc-toffee[runner]"') if any(value.startswith(("-n", "--numprocesses", "--dist", "--tx", "--max-worker-restart")) for value in forwarded): parser.error("use --workers to select parallelism; the runner owns worker scheduling and restart policy") diff --git a/tests/release/test_distributions.py b/tests/release/test_distributions.py index 5038a5e..01b8512 100644 --- a/tests/release/test_distributions.py +++ b/tests/release/test_distributions.py @@ -14,14 +14,14 @@ @pytest.fixture def packages(tmp_path: Path): def create(*, wheel_version="0.1.0", source_version="0.1.0", template=True): - with zipfile.ZipFile(tmp_path / "pytoffee.whl", "w") as wheel: - wheel.writestr("pytoffee.dist-info/METADATA", - f"Name: pytoffee\nVersion: {wheel_version}\n") + with zipfile.ZipFile(tmp_path / "uc_toffee.whl", "w") as wheel: + wheel.writestr("uc_toffee.dist-info/METADATA", + f"Name: uc-toffee\nVersion: {wheel_version}\n") if template: wheel.writestr("toffee/templates/unified_coverage.html", "template") - with tarfile.open(tmp_path / "pytoffee.tar.gz", "w:gz") as archive: + with tarfile.open(tmp_path / "uc_toffee.tar.gz", "w:gz") as archive: for name, contents in { - "toffee/PKG-INFO": f"Name: pytoffee\nVersion: {source_version}\n", + "toffee/PKG-INFO": f"Name: uc-toffee\nVersion: {source_version}\n", "toffee/src/toffee/templates/unified_coverage.html": "template", }.items(): data = contents.encode() @@ -56,7 +56,7 @@ def test_existing_release_can_only_be_completed_with_identical_assets(packages, directory = packages() existing = tmp_path / "published" existing.mkdir() - original = directory / "pytoffee.whl" + original = directory / "uc_toffee.whl" (existing / original.name).write_bytes(original.read_bytes()) distribution.verify_existing_assets(directory, existing) (existing / original.name).write_bytes(b"different build") diff --git a/tests/release/test_version_policy.py b/tests/release/test_version_policy.py index 3801119..b617c8b 100644 --- a/tests/release/test_version_policy.py +++ b/tests/release/test_version_policy.py @@ -56,10 +56,27 @@ def test_bumps_from_the_latest_release(repository, bump, expected): assert policy.next_tag(bump, base="main") == expected -def test_first_release_and_retry_do_not_create_extra_versions(repository): - assert policy.next_tag("minor", base="main") == "v0.1.0" - policy.git("tag", "-a", "v0.1.0", "-m", "first release") - assert policy.next_tag("minor", base="main") == "v0.1.0" +@pytest.mark.parametrize("bump", policy.BUMPS) +def test_first_release_and_retry_do_not_create_extra_versions(repository, bump): + assert policy.next_tag(bump, base="main") == "v2.0.0" + policy.git("tag", "-a", "v2.0.0", "-m", "first release") + assert policy.next_tag(bump, base="main") == "v2.0.0" + + +@pytest.mark.parametrize("bump,expected", [("patch", "v2.0.1"), ("minor", "v2.1.0"), + ("major", "v3.0.0")]) +def test_legacy_history_does_not_determine_new_line_versions(repository, bump, expected): + policy.git("checkout", "-q", "--orphan", "master") + repository("legacy") + policy.git("tag", "v0.1.0") + policy.git("tag", "v0.3.1") + legacy = policy.git("rev-parse", "HEAD") + policy.git("checkout", "-q", "main") + assert policy.next_tag(bump, base="main") == "v2.0.0" + policy.git("tag", "-a", "v2.0.0", "-m", "first release") + repository("next release") + assert policy.next_tag(bump, base="main") == expected + assert policy.git("rev-parse", "master", "v0.1.0", "v0.3.1").splitlines() == [legacy] * 3 def test_side_branch_tags_are_ignored_and_cannot_be_released(repository): @@ -68,7 +85,7 @@ def test_side_branch_tags_are_ignored_and_cannot_be_released(repository): side = repository("side") policy.git("tag", "v99.0.0") policy.git("checkout", "-q", "main") - assert policy.next_tag("minor", base="main") == "v0.1.0" + assert policy.next_tag("minor", base="main") == "v2.0.0" with pytest.raises(ValueError, match="first-parent"): policy.next_tag("minor", side, "main") assert policy.git("rev-parse", "HEAD") == main_sha @@ -78,7 +95,7 @@ def test_rejects_a_conflicting_tag_and_an_out_of_order_commit(repository): original = policy.git("rev-parse", "HEAD") policy.git("checkout", "-qb", "topic") repository("side") - policy.git("tag", "v0.1.0") + policy.git("tag", "v2.0.0") policy.git("checkout", "-q", "main") with pytest.raises(ValueError, match="already exists"): policy.next_tag("minor", base="main")