贡献指南#
首先阅读Qiskit项目的整体贡献指南。这些全部包含在Qiskit文档中:
https://github.com/Qiskit/qiskit/blob/main/CONTRIBUTING.md
虽然并非所有内容都直接适用,因为大多数内容是关于Qiskit项目本身,而rustworkx是一个与Qiskit同步开发的独立库;但通用指南和建议仍然适用。
贡献 rustworkx#
除了通用指南外,关于参与rustworkx还有一些具体细节,这些内容记录如下。
更改代码#
rustworkx 主要使用Rust实现,并带有薄层的Python封装。
因此,您的代码改动大多会涉及修改位于src中的Rust文件。为了了解您需要修改哪些文件,我们邀请您一瞥我们简化的源码树概览:
├── src/
│ ├── lib.rs
│ ├── tiny.rs
│ ├── large/
│ │ ├── mod.rs
│ │ ├── pure_rust_code.rs
│ │ └── more_pure_rust_code.rs
lib.rs 中的模块导出项#
要添加新功能,你需要将它们导出至lib.rs文件。lib.rs会导入Rust模块中定义的函数(详见下一节内容),并通过m.add_wrapped(wrap_pyfunction!(your_new_function))?;将它们导出到Python环境。
模块中添加与更改函数#
要添加和更改功能,您需要修改模块文件。模块包含将要导出的pyfunctions,并且可以定义为单个文件例如tiny.rs或作为包含mod.rs的目录例如large/。
导出到 Python 的 Rust 函数需要标注 #[pyfunction]。该注解使其能够同时与 Python 解释器及纯 Rust 代码进行交互。若要修改现有函数,请搜索其名称并编辑已有代码。
如果你想添加一个新函数,找到你想插入其中的模块
或创建一个新模块,例如 your_module.rs。然后,从以下样板代码开始:
/// Docstring containing description of the function
#[pyfunction]
#[pyo3(text_signature = "(graph, /)")]
pub fn your_new_function(
py: Python,
graph: &graph::PyGraph,
) -> PyResult<()> {
/* Your code goes here */
}
注意: 如果你创建了一个新的
your_module.rs,请记住在lib.rs中声明并导入它:mod your_module; use your_module::*;
模块目录: 当单个文件不足以满足需求时#
有时你会发现很难在一个像tiny.rs这样的小文件中组织模块。在这种情况下,我们建议将文件移动到一个目录中,并按照large/的结构进行拆分。
模块目录有一个包含pyfunctions的mod.rs文件。该文件中的pyfunctions随后通过从pure_rust_code.rs和more_pure_rust_code.rs导入并调用纯Rust代码来委托大部分逻辑。
注意:对做出贡献仍有疑问吗? 请在 Qiskit Slack 的 #rustworkx 频道联系我们
rustworkx-core#
如果你正在编写一个纯rust函数,并且可以将其设计为泛型,使其适用于任何petgraph图(如果适用),并且不依赖Python或pyo3,那么它可能更适合放在rustworkx-core中。
rustworkx-core是一个独立的rust库,用于为rustworkx和其他rust应用或库提供Rust API。
与rustworkx不同,它是一个Rust库而非Python库,旨在作为petgraph之上的附加库,提供额外的图算法和功能。
当对rustworkx-core做贡献时,需要牢记的关键区别是:公共rust接口需要被视为稳定接口,这与rustworkx不同——在rustworkx中稳定的rust接口兼容性无关紧要,只有导出的Python API才重要。此外,文档和测试应当通过cargo doc和cargo test完成。期望任何rustworkx-core的新功能或更改也会被rustworkx使用,因此需要通过rustworkx测试中的python以及rustworkx-core的rust接口进行测试覆盖。
测试#
在您完成代码更改后,务必验证您的更改不会破坏任何现有测试,同时您添加的新测试也能成功运行。在您为更改开启新拉取请求之前,需要在本地运行测试套件。
运行测试套件的最简单方法是使用
**Nox**。你可以通过 pip 安装 Nox:pip install -U "nox[uv]"。Nox 提供若干优势,但最突出的优势是它会构建一个隔离的虚拟环境来运行测试。这意味着它不会污染你的系统 Python 环境。然而,默认情况下,Nox 每次运行时都会重新从源码编译 rustworkx,即使没有对 Rust 代码进行任何更改。为了避免这种情况,如果你想在不重新编译的情况下重新运行测试,可以使用选项 --no-install。请注意,你仅应在近期运行过 Nox 且自那以后版本库中无 Rust 代码(或打包的 Python 代码)更改的情况下使用此选项。否则,Nox 在其虚拟环境中安装的 rustworkx 包将会过时(或缺失)。
注意,如果你在Nox之外运行测试,你不能从repo的根目录运行测试,这是因为rustworkx的包装垫片会与已安装版本的rustworkx(包含编译后的扩展)的导入发生冲突。
在特定Python版本中运行测试#
如果希望使用特定版本的Python运行测试,请使用test_with_version目标。例如,启动3.11版本的测试,命令为:
nox --python 3.11 -e test_with_version
运行测试子集#
如果你想只运行一部分测试,可以向测试运行器传递一个选择正则表达式。例如,如果你想运行所有测试ID中包含“dag”的测试,你可以执行:nox -e test -- dag。你可以在裸--之后直接向测试运行器传递参数。要查看所有测试选择选项,可以参考stestr手册:
https://stestr.readthedocs.io/en/stable/MANUAL.html#test-selection
如果你想运行单个测试模块、测试类或独立测试方法,可以使用-n/--no-discover选项来加速这个过程。例如:
要运行一个模块:
nox -e test -- -n test_max_weight_matching
或者通过路径运行相同的模块:
nox -e test -- -n graph/test_nodes.py
要运行一个类:
nox -e test -- -n graph.test_nodes.TestNodes
运行一个方法:
nox -e test -- -n graph.test_nodes.TestNodes.test_no_nodes
需注意的是,Nox 将在仓库的 tests/ 目录下运行,因此通过路径传递给测试运行器的所有路径都必须相对于该目录。
可视化测试#
在运行可视化测试时,每个测试会生成一幅可视化图像,只有在调用过程中出现异常时才会失败。每个测试将输出图像保存到当前工作目录(如果使用nox运行测试,则目录为tests/)以确保生成的图像可用。但为了避免系统混乱,每个测试会清理生成后的图像,并且默认情况下测试运行不包含查看可视化测试中图像的任何方式。
如果你想检查可视化测试的输出(这在处理可视化时很常见),你可以设置RUSTWORKX_TEST_PRESERVE_IMAGES环境变量为任意值,这样就会跳过清理步骤。这将使你能够查看输出图像并确保可视化是正确的。例如,运行:
RUSTWORKX_TEST_PRESERVE_IMAGES=1 nox -e test
将运行可视化测试,并在运行完成后保留生成的图像文件,以便您检查输出。
rustworkx-core 测试#
由于rustworkx-core是一个独立的rust包,拥有自己的公共接口,它需要自己的测试。这些测试可以是文档测试(嵌入在rust代码文档字符串中的代码示例)或独立测试的组合。您可以参考rust书籍了解如何添加测试:
https://doc.rust-lang.org/book/ch11-01-writing-tests.html
rustworkx核心测试可以使用以下命令运行:
cargo test --workspace
rustworkx中的模糊测试#
我们使用 cargo-fuzz 来测试 rustworkx,以确保不会出现意外崩溃或未定义行为。请按照以下步骤在本地运行模糊测试。
构建模糊目标#
要构建模糊测试目标,首先安装 cargo-fuzz:
cargo install cargo-fuzz
然后运行以下命令:
cargo fuzz build
为了运行模糊测试 (例如,test_traversal_node_coverage):#
列出所有目标:
cargo fuzz list
从列表中运行测试
cargo fuzz run test_traversal_node_coverage
对于夜间工具链:
cargo +nightly fuzz run test_traversal_node_coverage
将模糊测试限制在特定时间内(例如,60秒):
cargo +nightly fuzz run test_traversal_node_coverage -- -max_total_time=60
解读失败案例#
失败被存储在 fuzz/artifacts/ 目录中。
为模糊测试贡献力量 #
模糊测试目标:在模糊测试目录中创建新目标。 修复故障:调查并修复模糊测试发现的错误。
模糊测试可能会占用大量资源。在本地运行它们以节省资源。 提交时附带详细文档和提交信息的模糊测试。
样式#
锈语言#
Rust 是 rustworkx 的主要语言,库中的所有功能代码均使用 Rust 编写。rustworkx 中的 Rust 代码采用 rustfmt 来确保风格一致。 CI 任务已配置用于检查这一点。幸运的是,调整您的代码非常简单,只需运行:cargo fmt
本地运行。这将自动重新格式化 rustworkx 中的 rust 代码以匹配 CI 所检查的样式。
智能体#
另一个步骤是在您的更改上运行 c 。您可以通过运行以下命令来执行:
cargo clippy
如果您想获得与CI完全相同的详细反馈,请改为运行:
cargo clippy --workspace --all-targets -- -D warnings
Python#
Python主要用于测试和一些小的打包和命名空间配置代码,这些代码实际上在库中使用。 black和flake8用于强制执行代码仓库中Python代码的一致风格。你可以通过Nox运行它们:
nox -e lint
这也会在检查模式下运行 cargo fmt 以确保你运行了 cargo fmt,
如果 Rust 代码不符合样式规则,将会失败。
如果black返回代码格式错误,你可以运行nox -e black来自动更新代码格式以符合样式规范。
构建文档#
就像编写测试一样,构建文档也可以通过Nox完成。这将负责编译rustworkx、安装Python依赖项,然后在隔离的虚拟环境中构建文档。
我们的文档配置要求安装 uv 后端用于 Nox。可以通过 pip install -U "nox[uv]" 完成。
您可以仅运行文档构建操作:
nox -e docs
这将输出在 docs/build/html 中渲染的 HTML 文档,你可以通过本地网页浏览器查看。
[!提示] 如果运行
nox -e docs -- -j auto, 文档会使用所有CPU并构建得更快。
rustworkx-core 文档#
要构建rustworkx-core文档,您将使用rust-doc。您可以通过运行以下命令来完成:
cargo doc -p rustworkx-core
编译完成后,文档将位于target/doc/rustworkx_core(相对于仓库根目录,而非rustworkx-core目录)
你可以通过运行以下命令,直接在配置的默认网页浏览器中构建并打开文档:
cargo doc -p rustworkx-core --open
更新文档依赖项#
文档工作流目前是我们依赖最多的步骤。尽管rustworkx目前仅有极少的Python依赖,我们的文档却依赖于sphinx以及许多其他包。因此,uv.lock文件包含了一个冻结的依赖列表,可供该工作流使用。
如果您需要添加或移除依赖项,请更新pyproject.toml(特别是dependency-groups中的docs部分),然后运行uv sync --all-groups来更新uv.lock。
类型注解#
如果你新增了方法、函数或类,和/或更改了任何签名,那么在拉取请求中必须包含Python的类型标注。类型标注通过使用类型存根文件添加,这些文件为使用类型标注的 Python 工具提供类型标注。存根文件位于rustworkx/目录下,文件扩展名为.pyi。它们包含Python函数的带标注签名,去除了实现部分。你可以在以下链接找到更多关于Python类型标注的详细信息:
类型注释对于Python最终用户非常有帮助。添加注释可使用户通过mypy进行代码类型检查,这在使用rustworkx过程中有助于发现错误。
就像代码测试一样,注释也是通过Nox测试的。
nox -e stubs
需要注意的一点是,如果你要在Rust模块中添加一个新函数,你需要确保带有注解的签名被添加到
rustworkx/rustworkx.pyi中。然后,还需要通过在rustworkx/__init__.pyi中以以下形式添加导入行来重新导出注解:
from .rustworkx import foo as foo
这确保用户在从根rustworkx包导入时,mypy能够找到类型注解(这是最常见的访问方式)。
版本发布说明#
当我们发布新版本的rustworkx时,记录所有面向最终用户的变更非常重要。预期是如果您的代码贡献有面向用户的变更,您需要为这些变更编写发布文档。此文档必须说明更改了什么、为什么更改以及用户如何使用或适应变更。发布文档的理念在于,当对项目内部知识有限的普通用户从上一版本升级到新版本时,他们应该能够阅读发布说明,了解是否需要更新使用rustworkx的程序,以及如何进行更新。理想情况下还应解释为什么需要做出此更改,以提供必要的背景信息。
为确保我们不会遗漏发布说明或在发布周期内用户相关变更的细节,我们要求所有面向用户的变更都要在提交代码的同时提供文档。为完成这一任务,我们使用 reno 工具,这使得我们可以在基于 git 的工作流中撰写和编译发布说明。
添加新的发布说明#
制作新版发行说明相当直接明了。请确保已安装reno工具:
pip install -U reno
安装完reno后,你可以在本地仓库检出版本的根目录运行以下命令来创建新的发布说明:
reno new short-description-string
其中 short-description-string 是一个简短的字符串(不含空格),用于描述 发布说明中包含的内容。这将作为发布说明文件的前缀。运行此命令后,将在 releasenotes/notes 中创建一个新的 yaml 文件。然后用文本编辑器打开该 yaml 文件并编写发布说明。发布说明的 基本结构是在类别键下的 yaml 列表中使用重组文本。您可以在每个类别下添加单个项目, 这些项目将在编译发布说明时按发布版本自动分组。单个文件中可以根据需要包含任意数量的条目, 但为了避免潜在的冲突,您需要为每个具有面向用户变更的拉取请求创建一个新文件。 当您打开新创建的文件时,它将是一个包含不同类别的完整模板,每个类别中都有一个类别的描述作为单个条目。 您需要删除所有未使用的部分,并更新您正在使用部分的内容。例如,最终结果应如下所示:
features:
- |
Added a new function, :func:`~rustworkx.foo` that adds support for doing
something to :class:`~rustworkx.PyDiGraph` objects.
- |
The :class:`~rustworkx.PyDiGraph` class has a new method
:meth:`~rustworkx.PyDiGraph.foo``. This is the equivalent of calling the
:func:`~rustworkx.foo` function to do something to your
:class:`~rustworkx.PyDiGraph` object, but provides the convenience of running
it natively on an object. For example::
from rustworkx import PyDiGraph
g = PyDiGraph.
g.foo()
deprecations:
- |
The ``rustworkx.bar`` function has been deprecated and will be removed in a
future release. It has been superseded by the
:meth:`~rustworkx.PyDiGraph.foo` method and :func:`~rustworkx.foo` function
which provides similar functionality but with more accurate results and
better performance. You should update your calls
``rustworkx.bar()`` calls to use ``rustworkx.foo()`` instead.
你也可以查看其他发布说明来了解更多示例。
你可以使用任何 sphinx 功能 来根据需要表达正在变更的内容(代码区块、表格、编号列表、项目符号列表等)。一般而言,你应该让发布说明尽可能包含足够的细节,以便用户了解变更的内容、原因以及他们需要如何更新代码。
编写完发布说明后,您需要使用git add将说明文件添加到提交中,并将其提交到您的PR分支,以确保它们与PR中的代码一同包含。
与问题关联#
如果你需要将问题或其他Github工件作为发布说明的一部分进行链接,这应该通过行内链接来实现,链接文本应为问题编号。例如,你可以编写一个包含问题12345链接的发布说明,如下所示:
fixes:
- |
Fixes a race condition in the function ``foo()``. Refer to
`#12345 <https://github.com/Qiskit/rustworkx/issues/12345>`__ for more
details.
生成发布说明#
添加发布说明后,如果你想查看完整的发布说明输出。Reno 用于将发布说明 yaml 文件合并成单个 rst(ReStructuredText)文档,sphinx 随后会将其编译作为文档构建的一部分。如果你想生成 rst 文件,可以使用 reno report 命令。如果你想生成所有版本的完整 rustworkx 发布说明(因为我们从 0.8 版本开始使用 reno),只需运行:
reno report
但你也可以使用 --version 参数来查看单个发布版本(在它被标记之后:
reno report --version 0.8.0
本地构建发布说明#
构建发布说明是标准 rustworkx 文档构建的一部分。要查看当前代码库状态下渲染后的 html 输出效果,可以运行:nox -e docs,这会将所有文档构建到 docs/_build/html 中,特别是发布说明将位于 docs/_build/html/release_notes.html
拉取请求审查、CI与合并队列#
当你向rustworkx提交拉取请求后,它需要通过CI测试并获得核心团队审核人员的批准。CI测试会在你的拉取请求开启时自动触发,并对后续提交到该分支的每个 commit 都进行测试。然而代码审查可能需要一些时间,有时甚至需要数周或数月,因为每天都有很多新的拉取请求开启,而审核人员数量有限,尽管每一个提议的变更对项目都是宝贵的补充,但并不是所有内容都是最高优先级。你可以通过主动审核其他开放的PR来帮助加速这一过程。虽然只有rustworkx核心团队成员有权限提供最终批准并将PR标记为可合并,但代码审查对所有人开放,所有审查都是受欢迎且极其宝贵的贡献。帮助进行代码审查也有助于减轻核心团队的负担,使他们能够更快地审查代码。
代码审查过程是一个来回沟通的过程,你会收到关于你对项目提出修改的反馈和问题。在获得批准之前,你很可能会有多轮的反馈,包括建议或要求的更改。请不要灰心,因为这是正常的,也是确保 rustworkx 项目质量的一部分,即使是那些一开始看起来直接或简单的更改,也可能有起初并不明显的更大影响。如果你收到反馈,可以在根据收到的意见调整你的PR之后,随时请求审阅者重新审阅。
此外,需要记住的一件事是CI时间是受限制的资源,而非无限。在等待评审和批准期间,并非每次main分支变更都需要保持你的PR分支最新。定期更新以确保代码库变化时不出现回归问题是可行的,但过于频繁只会不必要地浪费CI资源。这会导致CI资源紧缺,减缓项目的总吞吐量。如果可能,尝试将你的分支更新到main分支的当前HEAD的操作与其他对PR分支的更改(如根据代码评审进行调整)捆绑在一起。这样将导致单次CI运行,而不是在没有代码更改的情况下进行独立更新。
一旦你的PR获得必要的批准,它将被标记为automerge标签。这是给mergify机器人的一个信号,表示该PR已获批准并准备合并。随后,mergify机器人将把PR排入其合并队列。此时,将PR更新到main分支最新HEAD的过程是完全自动化的,一旦CI通过,mergify将自动合并PR。为了节省CI资源并最大化处理能力,mergify机器人只会在PR位于合并队列最前面时进行更新。此时你的PR可能看似处于闲置状态,但这很可能只是意味着mergify合并队列较深和/或CI有积压任务。在PR获得必要批准并被标记为automerge后,切勿手动将PR分支更新到main分支的HEAD,除非出现合并冲突或CI运行失败。这样做只会浪费CI资源并延迟包括你的PR在内的所有内容的合并。
稳定分支策略与后向移植#
稳定分支旨在为主版本发布后已修复的高影响性错误、文档修正以及安全相关问题提供一个安全的修复来源。在审查稳定分支的PR时,我们需要平衡每个补丁的风险以及对稳定分支用户可能带来的价值。只有特定类型的变更适合包含在稳定分支中。一个针对重大问题的庞大、有风险的补丁可能是合理的,同样地,对于相对模糊的错误处理案例进行一次微小的修复也可能是恰当的。在考虑变更时需要权衡多个因素:
回归风险:即使是最微小的变更也带有破坏某些事物的风险,我们确实想要避免稳定分支上的回归。
用户可见性益处:我们是否在修复用户可能实际注意到的某个问题,如果是,它的重要性如何?
修复工作的独立程度:如果修复了一个重要问题,但同时进行了大量代码重构,或许值得思考一个更小风险的修复方案可能是什么样子。
修复是否已在主干上:变更必须是已合并到主干上的变更的回退,除非该变更在主干上完全没有意义。
通常只允许在稳定分支上进行错误修复或非代码更改,其主要例外是增加对新 Python 版本的支持。如果发布了新的 Python 版本,将包含该新支持的功能更改向后移植是可接受的后向移植操作。
在rustworkx中,至少直到1.0版本发布,我们每次仅维护一个稳定分支对应最新的次版本发行。
回迁过程#
在正常情况下,要向后移植一个拉取请求,只需将其标记为 stable-backport-potential,这会向 mergify bot 发出信号,表示该 PR 在合并后应该被向后移植。一旦标记为 stable-backport-potential 的 PR 被合并,mergify 将自动开启一个新的 PR,将其向后移植到稳定分支。
手动回迁步骤#
如果由于某种原因,mergify方法不起作用,且需要手动反向移植PR,可按以下流程操作。当我们从主干(main)向稳定(stable)分支反向移植补丁时,希望保留对主干更改的引用。创建稳定版PR的分支时,请使用:
$ git cherry-pick -x $main_commit_id
然而,这仅适用于来自主分支的小型自包含补丁。如果您 需要从主分支反向移植更大提交的一个子集(例如来自压缩的 PR) 请手动执行此操作。在这些情况下,请添加:
Backported from: #main pr number
这样我们就能追踪变更子集的来源,即使进行严格的挑选(cherry-picking)并不合理。
如果您提交的补丁无法干净地合并到稳定分支,您可以通过自行解决冲突并提交最终补丁来提供帮助。请保留提交信息中的冲突行,以协助稳定补丁的审查。