ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spack 贡献指南:从 Pull Request 到 CI 全流程实战

Spack 贡献指南:从 Pull Request 到 CI 全流程实战 Spack 贡献指南从 Pull Request 到 CI 全流程实战【免费下载链接】spackA flexible package manager that supports multiple versions, configurations, platforms, and compilers.项目地址: https://gitcode.com/GitHub_Trending/sp/spack面向希望向 Spack 提交新包、新特性或 bug 修复的开发者与管理员。本文以官方 contribution_guide.rst 为主体系统讲解 PR 粒度规范、分支策略、本地可运行的单元测试 / 风格检查 / 文档测试以及 GitLab 云端流水线与构建缓存堆栈的接入方式帮助你在提交前自测达标、提交后顺利通过验收。Spack当前仓库 spack 根目录即完整源码树是一个支持多版本、多配置、多平台、多编译器的包管理器它的develop分支承载着最新贡献。想要让一个package.py或核心库改动被合并进主干除了代码本身正确还必须通过一整套自动化验收流程。本文给出可直接照做的贡献工作流与本地验证命令并附上仓库内的源码证据方便你核对每一步的真实行为。一次 PR 应该包含什么one-PR-one-package/feature 规则贡献指南引用 Bitbucket 教程对 Pull Request 的定义PR 是开发者向团队成员发出已完成一个功能通知的机制同时也是一个围绕该提案展开讨论的专属论坛。这里的关键词是completed feature已完成的功能。指南明确建议一个 PR 中的改动应对应一个功能、bug 修复或扩展虽然技术上可以创建包含多个不同想法的 PR但这样会让评审变得繁琐且易错尽量遵守one-PR-one-package/feature一个 PR 只提交一个包/功能规则。从仓库结构看每个包的 recipe 都独立存放在var/spack/repos/builtin/packages/包名/package.py中提交彼此解耦这也是一包一 PR能在工程上落地的原因。分支策略develop 是唯一起点Spack 的develop分支包含最新贡献几乎所有的 PR 都应从develop拉出并指向develop。每个大版本系列有独立的分支版本分支从develop起源并在其上打每个小版本发布的 tag。例如releases/v0.14分支带有v0.14.0、v0.14.1、v0.14.2等 tag。维护者只向这些分支反向移植backport重要 bug 修复但不会推进包版本或做任何会改变 Spack 依赖具体化concretization结果的改动当前这些分支由维护者通过从develop上cherry-pick来管理详见 pipelines.rst 等文档体系中的发布说明。持续集成CI提交后必须通过的关卡Spack 使用 GitHub Actions 做 CI 测试。每次提交 PR都会自动运行一系列测试以确认你没有意外引入 bug。PR 只有在全部通过后才会被接受。指南建议在本地先行运行这些测试以加速评审而不必干等 CI 结果。处理与 PR 无关的 CI 失败CI 常常会因与 PR 无关的原因失败例如apt-get、pip或brew下载测试套件依赖失败瞬时 bug 导致单元测试超时。处理方式依据文档原文点击失败的 job 的 Details 链接查看具体失败的测试若失败原因与你的 PR 无关有仓库写权限的人点击右侧 Restart workflow 按钮重启无权限的人关闭再重新打开 PR 以重跑全部测试若同一测试反复失败可能问题出在你的 PR 上若发现近期每个 PR 都以相同错误失败则可能是 CI 基础设施故障或 Spack 某个依赖发布了新版本引发问题——此时请提交 issue。指南声明当前在 macOS 和 Linux 上对 Python 3.6 及以上版本进行测试并执行三类测试单元测试Unit Tests、风格测试Style Tests和文档测试Documentation Tests。单元测试spack unit-test单元测试确保 Spack 核心功能如源码获取 fetch、spec 解析按预期工作。如果你的 PR 只新增或修改包 recipe几乎不可能导致单元测试失败但如果修改了核心库就必须运行单元测试确认没有破坏任何东西。由于测试涉及从 VCS 仓库拉取源码运行单元测试需要git、mercurial和subversion可执行文件位于PATH中三者都可用 Spack 或系统包管理器安装。运行全部测试$ spack unit-test整套测试可能需要数分钟。如果你只改动单个功能可以分批运行子集$ spack unit-test lib/spack/spack/test/architecture.py只运行该文件中的test_platform测试$ spack unit-test lib/spack/spack/test/architecture.py::test_platform这个能力来自spack unit-test是 pytest 的包装器从 unit_test.py 源码 可以看到命令解析器将剩余参数原样转交给 pytestnargsargparse.REMAINDER的pytest_args最终在仓库根目录调用pytest.main(pytest_args)见 unit_test.py。因此 pytest 的测试选择语法::定位、-k关键词过滤等全部可用。列出可用测试spack unit-test提供了几个辅助选项用于在运行前了解可用的测试选项作用spack unit-test --list列出所有可用的测试文件spack unit-test --list-long列出更详细的可用单元测试spack unit-test --list-names列出所有测试的全限定名称以上三个选项的实现在 unit_test.py 的do_list函数--list仅扫描lib/spack/spack/test/目录下的*.py排除conftest.py与__init__.py后两者则通过调用pytest --collect-only收集测试节点并按作用域归组且会对参数化测试去除[参数]后缀。还可以把它们与 pytest 参数组合使用以缩小范围例如$ spack unit-test --list-long lib/spack/spack/test/architecture.py $ spack unit-test --list-names -k spec and concretize其中-k spec and concretize会匹配名称中同时含有 spec 和 concretize 的测试。对应的仓库测试文件真实存在lib/spack/spack/test/architecture.py。其他常用参数-s关闭 pytest 的输出捕获实时查看测试中的print输出。示例$ spack unit-test -s --list-long lib/spack/spack/test/architecture.py::test_platform-n N/--numprocesses N并行运行测试默认 1 为顺序执行。源码显示该选项在 N1 时要求安装pytest-xdist否则报错退出见 unit_test.py。--pytest-help-H显示完整的 pytest 帮助包含高级选项。--extension 名称改为测试指定的 Spack 扩展spack.extensions.load_extension见 unit_test.py。指南特别强调单元测试是防止 bug 混入 Spack 的关键。如果修改了核心库或新增功能请为你的特性补充新单元测试并考虑强化既有测试——向 Spack 提交 PR 时你很可能会被要求这样做。风格测试spack styleSpack 使用Ruff做代码格式化与 lint使用mypy做类型检查。为了限制纯风格改动的 PR 数量Spack 强制要求 PEP 8 合规若改动涉及 Spack 库还必须通过 mypy 类型检查。运行风格检查$ spack style自动修复格式化与 lint 问题$ spack style --fix从 style.py 源码 可见实际依次执行的工具链为import、ruff-format、ruff-check、mypy四步。spack style相比手工运行工具的优势指南原文要点只检查你自develop分叉以来修改过的文件——实现上通过git diff --name-only --diff-filterACMR base...、--cached、未暂存 diff 以及ls-files --exclude-standard --other收集改动文件只处理*.py与bin/spack并排除lib/spack/spack/vendor/目录见 style.py 的 changed_files在任何目录下都能工作自动添加经过批准的豁免项例如 URL 往往超过 99 字符因此豁免其行长度检查package.py中的某些 import 相关检查如from spack.package import *也被豁免。通过时的输出示例$ spack style Running style checks on spack selected: import, ruff-format, ruff-check, mypy Checking Files: var/spack/repos/builtin/packages/hdf5/package.py var/spack/repos/builtin/packages/hdf/package.py var/spack/repos/builtin/packages/netcdf/package.py Running import checks import checks were clean Running ruff-format checks ruff-format checks were clean Running ruff-check checks ruff-check checks were clean Running mypy checks mypy checks were clean spack style checks were clean不合规时的错误示例$ spack style Running style checks on spack var/spack/repos/builtin/packages/netcdf/package.py:26:1: F401 os imported but unused var/spack/repos/builtin/packages/netcdf/package.py:61:1: E303 too many blank lines (2) var/spack/repos/builtin/packages/netcdf/package.py:106:100: E501 line too long (105 99 characters)大部分错误信息很直白若删除或新增行行号会变化重新运行spack style即可更新。许多错误可被spack style --fix自动修复。提示按倒序修复风格错误。这样能避免为了重新计算行号而反复运行spack style也更便于直接对照 CI 输出逐条修正。spack style还支持-b/--base对比分支默认develop、-a/--all检查全部文件、-U/--no-untracked排除未跟踪文件、-t/--tool与-s/--skip选择/跳过工具等参数见 style.py 的 setup_parser。文档测试构建 Sphinx 文档Spack 使用 Sphinx 构建文档。为了阻止坏链接、缺失 import 等问题进入文档Spack 增加了文档测试构建文档时只要出现任何 warning 或 error测试即失败。构建文档需要的依赖sphinxsphinxcontrib-programoutputsphinx-rtd-themegraphvizgitmercurialsubversion全部可用 Spack 安装$ spack install py-sphinx py-sphinxcontrib-programoutput py-sphinx-rtd-theme graphviz git mercurial subversion当前仓库 docs/requirements.txt 还给出了精确版本约束如sphinx9.1.0、sphinxcontrib-programoutput0.20、furo2025.12.19、docutils0.22.4、pygments2.20.0、pytest9.1.1等可作为依赖锁定的补充依据。警告Sphinx 有多个必需依赖。如果你使用 Spack 安装的 Python 并安装了py-sphinx及其相关包需要让它们对你的解释器可见。最简单的方式是$ spack load py-sphinx py-sphinx-rtd-theme py-sphinxcontrib-programoutput使所有依赖被加入PYTHONPATH。若看到类似错误Extension error: Could not import extension sphinxcontrib.programoutput (exception: No module named sphinxcontrib.programoutput) make: *** [html] Error 1说明 Sphinx 在PYTHONPATH中找不到py-sphinxcontrib-programoutput。依赖就绪后构建文档$ cd path/to/spack/lib/spack/docs/ $ make clean $ make出现任何 warning 或 error 都必须先修正PR 才会被接受编辑文档时尤其要运行文档测试。文档改动可能产生晦涩的警告信息不理解时可在提交 PR 时提问。术语表与索引规范文档维护一份 Spack 术语表glossary.rst和通用索引。每个术语条目都会自动被索引因此文档页面中的.. index::指令不得原样重复术语表中的条目而应携带描述性子条目如single: environment; activating并放在最具体的章节位置。完整的索引约定写在lib/spack/docs/glossary.rst文件顶部的注释中——添加索引条目或术语时请遵循它。GitLab CI贡献构建缓存堆栈除了 GitHub ActionsSpack 还通过 GitLab CI 管理构建作业的编排并欢迎社区贡献软件栈stacks这些栈用于测试包 recipe 并生成公开可用的构建缓存build cache。GitLab 入口点.gitlab-ci.yml为每个新栈在share/spack/gitlab/cloud_pipelines/.gitlab-ci.yml中添加一个入口点。每个栈需要两个阶段生成阶段generate与构建阶段build。生成阶段使用作业模板.generate并通过环境变量定义栈名SPACK_CI_STACK_NAME、平台SPACK_TARGET_PLATFORM与架构SPACK_TARGET_ARCH配置以及构建所用 runner 类别的tags。注意SPACK_CI_STACK_NAME必须与存放该栈spack.yaml文件的目录名完全一致。注意平台与架构变量用于从 Spack CI 的通用配置中选出正确的配置。当前可用的配置包括.cray_rhel_zen4.cray_sles_zen4.darwin_aarch64.darwin_x86_64.linux_aarch64.linux_icelake.linux_neoverse_n1.linux_neoverse_v1.linux_neoverse_v2.linux_skylake.linux_x86_64.linux_x86_64_v4可以新增配置以支持新的平台与架构。构建阶段定义为触发器作业trigger job消费生成阶段为该栈生成的 GitLab CI 流水线。构建阶段作业使用处理基本配置的.build作业模板。新栈my-super-cool-stack的入口点示例.my-super-cool-stack: extends: [.linux_x86_64_v3] variables: SPACK_CI_STACK_NAME: my-super-cool-stack tags: [all, tags, your, job, needs] my-super-cool-stack-generate: extends: [.generate, .my-super-cool-stack] image: my-super-cool-stack-image:0.0.1 my-super-cool-stack-build: extends: [.build, .my-super-cool-stack] trigger: include: - artifact: jobs_scratch_dir/cloud-ci-pipeline.yml job: my-super-cool-stack-generate strategy: depend needs: - artifacts: true job: my-super-cool-stack-generate栈配置spack.yaml栈配置是一个 Spack 环境文件额外增加了两个节。栈配置应放在share/spack/gitlab/cloud_pipelines/stacks/stack_name/spack.yaml。ci节通常用于定义栈特定的映射如镜像或 tags。更多可放入ci节的内容请参考 pipelines.rst。cdash节用于定义构建结果上传到哪里。Spack 会配置将流水线结果发布到 cdash.spack.io 的大部分细节栈配置中唯一的要求是定义一个唯一的build-group通常取栈的长名称。构建zlib的栈示例spack: view: false packages: all: require: [%gcc, targetx86_64_v3] specs: - zlib ci: pipeline-gen: - build-job: image: my-super-cool-stack-image:0.0.1 cdash: build-group: My Super Cool Stack注意*-generate作业中使用的image必须与build-job中使用的image完全一致。两者不一致时构建作业可能失败。注册 Runner为 Spack CI 贡献算力为 Spack 的 CI 构建农场贡献计算资源是扩展公共 Spack 构建缓存能力的方式之一。目前 Spack 使用来自 AWS、Google 和俄勒冈大学UO的 Linux runner。runner 需要四样关键配置Runner 注册令牌Registration Token准确的 tagsOIDC 认证脚本GPG 密钥最低 GitLab Runner 版本16.1.0。注册令牌第一步是在 Spack infrastructure 项目中开一个 issue分配runner-registration标签、使用runner_registration.yml模板Spack 基础设施团队会引导你完成注册流程。需要提供的信息包括新增资源的动机、runner 的半详细描述、以及维护 runner 上软件的联系人。联系人随后与基础设施团队协作获取与 Spack GitLab 实例交互的注册令牌runner 上线后该联系人还要负责持续更新 GitLab runner 软件以跟上 Spack GitLab 的版本。打标签注册初期务必排除特殊标签spack防止新 runner 在配置与评估期间被生产 CI 作业选中确认可投入生产后再添加spack标签。由于 GitLab 没有排除标签的概念提供专用资源的 runner 需要专用标签。例如纯 CPU 的 x86_64 runner 可带标签x86_64而带 CUDA 显卡的 runner 可用x86_64-cuda表明它只应被用于能从 CUDA 资源获益的包。OIDCSpack runner 使用 OIDC 认证连接相应的 AWS bucket该 bucket 用于在构建作业之间协调二进制的传递。配置 OIDC 认证时Spack CI runner 使用一个依赖极少的 Python 脚本通过pre_build_script配置示例runner 的 config.toml[[runners]] pre_build_script echo Executing Spack pre-build setup script for cmd in ${PY3:-} python3 python; do if command -v /dev/null $cmd; then export PY3$(command -v $cmd) break fi done if [ -z ${PY3:-} ]; then echo Unable to find python3 executable exit 1 fi $PY3 -c import urllib.request; urllib.request.urlretrieve(https://raw.githubusercontent.com/spack/spack-infrastructure/main/scripts/gitlab_runner_pre_build/pre_build.py, pre_build.py) $PY3 pre_build.py envvars . ./envvars rm -f envvars unset GITLAB_OIDC_TOKEN GPG 密钥可能用于protectedCI 的 runner 需要注册一个可用来签名包的中间签名密钥。包签名机制的更多内容见 signing.rst。代码覆盖率CodecovSpack 使用 Codecov 生成并报告单元测试覆盖率帮助判断 Spack 中有多大比例的行被单元测试覆盖。被单元测试覆盖的代码仍可能有 bug但远比未覆盖的代码不易出错。Codecov 提供 Chrome/Firefox 浏览器扩展与 GitHub 集成可在查看 Spack 仓库时逐行看到覆盖情况对新贡献者来说编写单元测试提升覆盖率是一个很好的入门方式与 GitHub Actions CI 不同Codecov 测试不要求通过才能合入 PR但如果修改了核心库强烈建议补充覆盖这些改动的单元测试否则无从得知改动是否引入 bug对核心做了实质性改动时维护者可能要求你补测试。注意如果只修改了 package 文件Spack 不关心你 PR 的覆盖率。你可能会看到 Codecov 测试失败但自己没改任何核心文件——这说明自你分叉develop以来 Spack 的整体覆盖率上升了这是好事。若想让 Codecov 测试通过可以基于最新develop变基但这并非必需。Git 工作流日常贡献的六种姿势Spack 仍处于 beta 阶段多数用户直接跑develop分支修复与新特性不断合入。如何在跟上上游的同时维护本地差异、并向 Spack 提交 PR指南给出了完整的操作序列。1. 建分支Branching最简单的贡献方式是让所有改动都发生在新分支上。先确保develop最新再从它创建新分支$ git checkout develop $ git pull upstream develop $ git branch descriptive_branch_name $ git checkout descriptive_branch_name这里假设本地develop跟踪上游develop用远程分支亦可但对某些人来说本地跟踪更便利。提交信息约定涉及包package-name的提交消息格式为package-name: descriptive message。描述性消息很重要——几个月甚至几年后别人查看你的改动时需要据此理解改动背后的动机。改动并提交$ git add files_to_be_part_of_the_commit $ git commit --message descriptive_message_of_this_particular_commit推送到你的 fork 并创建 PR目标分支选develop$ git push origin descriptive_branch_name --set-upstream如果急需这个改动、等不及 PR 合入可以继续在这个分支上工作若有多个 PR也可以维护一个合并了所有分支的 Frankenstein 分支$ git co develop $ git branch your_modified_develop_branch $ git checkout your_modified_develop_branch $ git merge descriptive_branch_name每个新 PR 都可重复此操作但要记得让该本地分支也跟上上游develop。2. 摘樱桃Cherry-Picking如果你在本地修改过的develop分支上已提交了一些改动之后才决定贡献给 Spack可以用 cherry-pick 创建只含这些提交的新分支$ git checkout your_modified_develop_branch $ git log $ git checkout develop $ git pull upstream develop $ git branch descriptive_branch_name $ git checkout descriptive_branch_name $ git cherry-pick hash $ git push origin descriptive_branch_name --set-upstream随后即可从 GitHub 网页界面创建 PR。净效果是本地版本已打上补丁可继续使用同时这些改动被摘入独立分支提交为上游 PR。多个提交可同理逐个 cherry-pick。注意每当改动可能对上游有意义时应尽快创建 PR不要拖上数周或数月原因有二你可能忘记当初为什么改这些文件改动也可能难以被隔离成一个干净、独立的 PR。3. 变基Rebasing其他开发者不断向 Spack 贡献可能恰好改了你 PR 涉及的文件。若他们的 PR 先被合入就会产生合并冲突你的 PR 无法在不破坏改动的前提下自动合并。此时你将被要求基于最新上游develop变基$ git checkout develop $ git pull upstream develop $ git checkout descriptive_branch_name $ git rebase developGit 会提示你解决冲突编辑无法自动合并的文件解决冲突后$ git add file_that_could_not_be_merged $ git rebase --continue可能需重复多次直至全部冲突解决然后强制推送$ git push --force origin descriptive_branch_name4. 用 cherry-pick 变基也可以用 cherry-pick 完成变基。先创建临时备份分支$ git checkout descriptive_branch_name $ git branch tmp出问题时可随时退回tmp分支。查看日志并记下想保留的提交哈希$ git log回到原分支并硬重置到develop先确保本地develop已与上游同步$ git checkout develop $ git pull upstream develop $ git checkout descriptive_branch_name $ git reset --hard develop再逐个摘取相关提交$ git cherry-pick hash1 $ git cherry-pick hash2推送修改后的分支到 fork$ git push --force origin descriptive_branch_name一切正常后删除备份分支$ git branch --delete --force tmp5. 重写历史Re-writing History有时分支与develop分歧过大难以直接变基。如果当前提交历史更偏实验性质、只有净结果重要可以重写历史。先合并上游develop并把分支重置到它$ git merge develop $ git reset develop此时分支与develop指向同一提交、两者不可区分但所有先前修改的文件保持不变——改动不会丢失。可通过 diff 审查改动$ git status $ git diff然后通过 add 与 commit 重写历史$ git add files_to_be_part_of_commit $ git commit --message descriptive_message全部改完提交后推送分支到 fork 并创建 PR$ git push origin --set-upstream小结提交前的自检清单综合指南全文向 Spack 提交 PR 前建议按顺序自查粒度一个 PR 只对应一个包/功能/bug 修复分支从最新develop拉出新分支目标分支选develop单元测试修改核心库时运行spack unit-test必要时补充针对新功能的测试只改包 recipe 也建议至少跑相关子集风格spack style必须全绿可用spack style --fix自动修复参考输出中的四道检查import、ruff-format、ruff-check、mypy文档改动文档时在lib/spack/docs/下make clean make确保无 warning/errorCI 失败排查区分与 PR 相关的失败和基础设施/依赖导致的偶发失败必要时重启工作流或提交 issue覆盖与发布按需关注 Codecov 覆盖率涉及构建缓存堆栈时按 GitLab CI 模板.generate/.build、SPACK_CI_STACK_NAME等配置并注意镜像一致性。以上每一步都可在当前仓库中验证命令实现见 unit_test.py 与 style.py测试样例见 lib/spack/spack/test/CI 与流水线配置约定可对照 pipelines.rst 与 ci 模块源码 继续深入。【免费下载链接】spackA flexible package manager that supports multiple versions, configurations, platforms, and compilers.项目地址: https://gitcode.com/GitHub_Trending/sp/spack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进