Spack 高级打包指南:多构建系统、外部包检测、ABI 拼接与自定义视图

发布时间:2026/9/18 3:09:17
Spack 高级打包指南:多构建系统、外部包检测、ABI 拼接与自定义视图
Spack 高级打包指南多构建系统、外部包检测、ABI 拼接与自定义视图【免费下载链接】spackA flexible package manager that supports multiple versions, configurations, platforms, and compilers.项目地址: https://gitcode.com/GitHub_Trending/sp/spack本文是 Spack 打包指南系列的第四部分前三个部分为 创建包、构建阶段、测试聚焦于单个package.py文件难以覆盖的高级场景。读完本文你将掌握如何在同一个包中声明并切换多个构建系统Autotools/CMake 等、如何让包被spack external find自动识别、如何通过can_splice声明 ABI 兼容性以支持自动拼接splicing以及在环境视图中定制包的合并行为。多构建系统Multiple build systems同一个软件包在不同版本或不同平台上可能采用不同的构建系统。Spack 的设计目标是在单个package.py文件内无缝处理这种情况无需维护多个配方文件。从单一构建系统到多构建系统假设当前curl包一直使用 Autotools 构建其配方最初如下from spack_repo.builtin.build_systems.autotools import AutotoolsPackage class Curl(AutotoolsPackage): depends_on(zlib-api) def configure_args(self): return [f--with-zlib{self.spec[zlib-api].prefix}]要为它增加 CMake 构建路径需要做三件事在Curl类的基类列表中追加另一个构建系统基类本例为cmake.CMakePackage用build_system指令显式声明支持的构建系统把 构建指令如configure_args、cmake_args迁移到独立的 builder 类中。改造后from spack_repo.builtin.build_systems import autotools, cmake class Curl(cmake.CMakePackage, autotools.AutotoolsPackage): build_system(autotools, cmake, defaultcmake) depends_on(zlib-api) class AutotoolsBuilder(autotools.AutotoolsBuilder): def configure_args(self): return [f--with-zlib{self.spec[zlib-api].prefix}] class CMakeBuilder(cmake.CMakeBuilder): def cmake_args(self): return [self.define_from_variant(USE_NGHTTP2, nghttp2)]从源码层面看build_system指令本质上就是声明一个名为build_system、multiFalse的单值 variant其实现位于 directives.pydef build_system(*values, **kwargs): Define the build system used by the package. This defines the build_system variant. default kwargs.get(default, None) or values[0] return variant( build_system, valuestuple(values), descriptionBuild systems supported by the package, defaultdefault, multiFalse, )也就是说build_system的返回值就是variant因此它继承了 variant 指令的全部能力when条件、默认值、取值约束等只是取值被固定为所声明的构建系统名称。总体原则是包元数据与构建指令分离——depends_on、variant、patch等指令放在 package 类中configure、build、install等构建阶段函数以及cmake_args、configure_args等辅助函数全部放入 builder 类。仓库中的 mock 测试仓库提供了完整可运行的双构建系统示例dual_cmake_autotools/package.py其中不仅声明了两个构建系统还演示了generator variant 只在build_systemmock_cmake时生效、以及用with when(build_systemmock_cmake)包裹条件依赖的写法。对应的构建系统基类定义含phases、package_methods、cmake_args等钩子见 build_systems/cmake.py。在 concretize 阶段选择构建系统由于build_system是每个包都可用的 variantconcretize 时可以直接通过命令行指定$ spack install curl build_systemcmakeSpack 会据此为curl选择CMakeBuilder执行cmake、build、install阶段并自动引入该构建系统所需的构建期依赖例如 CMake 构建系统基类中通常声明了depends_on(cmake, typebuild, whenbuild_systemcmake)。覆盖整个构建系统的 phases有时配方需要重写构建系统的整个阶段。假设cp2k包原本这样覆盖 Autotools 的install阶段from spack.package import * from spack_repo.builtin.build_systems import autotools class Cp2k(autotools.AutotoolsPackage): def install(self, spec: Spec, prefix: str) - None: # ...existing code... pass当迁移到多构建系统时阶段方法的签名会发生变化从Package类移动到Builder类后self不再指向包本身因此阶段方法需要把Package实例作为第一个参数显式传入from spack.package import * from spack_repo.builtin.build_systems import autotools, cmake class Cp2k(autotools.AutotoolsPackage, cmake.CMakePackage): build_system(autotools, cmake, defaultcmake) class AutotoolsBuilder(autotools.AutotoolsBuilder): def install(self, pkg: Cp2k, spec: Spec, prefix: str) - None: # ...existing code... pass即install(self, pkg, spec, prefix)pkg是包实例spec与prefix含义不变。按构建系统添加条件依赖很多构建期依赖只对特定构建系统有意义。推荐的写法是用with when(build_system...)块集中管理from spack.package import * from spack_repo.builtin.build_systems import cmake, autotools class Cp2k(cmake.CMakePackage, autotools.AutotoolsPackage): build_system(cmake, autotools, defaultcmake) # Runtime dependencies depends_on(ncurses) depends_on(libxml2) # Lowerbounds for cmake only apply when using cmake as the build system with when(build_systemcmake): depends_on(cmake3.18:, when2.0:, typebuild) depends_on(cmake3:, typebuild) # Specify extra build dependencies used only in the configure script with when(build_systemautotools): depends_on(perl, typebuild) depends_on(pkgconfig, typebuild)注意when条件既可以限定构建系统也可以进一步限定版本范围如上面的when2.0:两者可以叠加使用实现精细的依赖约束。从一个构建系统过渡到另一个版本演进中常见的场景是旧版本用 Autotools新版本切换到 CMake。这可以用 conditional 指令建模条件 variant 取值from spack.package import * from spack_repo.builtin.build_systems import cmake, autotools class Cp2k(cmake.CMakePackage, autotools.AutotoolsPackage): build_system( conditional(cmake, when0.64:), conditional(autotools, when:0.63), defaultcmake, )上述声明意味着从v0.63到v0.64时cp2k的构建系统由 Autotools 强制切换为 CMake——0.64:版本只允许cmake:0.63版本只允许autotoolsconcretizer 会自动依据版本选择唯一合法的构建系统。仓库测试仓库中的 conditional_build_system/package.py 是这一写法的完整可运行范例同时演示了 variant 取值也可使用conditional。继承一个支持多构建系统的包如果只想定制元数据而不改动构建逻辑只需继承已有的包类即可。例如为内置的silo包追加一个新版本from spack_repo.builtin.packages.silo.package import Silo as BuiltinSilo class Silo(BuiltinSilo): # Version not in builtin.silo version(special_version)如果不定义任何 builderSpack 默认复用builtin.silo中已有的 builder。若还需要定制 builder像普通 Python 类一样继承即可from spack_repo.builtin.packages.silo.package import CMakeBuilder as SiloCMakeBuilder class CMakeBuilder(SiloCMakeBuilder): def cmake_args(self): return [self.define_from_variant(USE_NGHTTP2, nghttp2)]让包可被spack external find发现要让一个包在系统上被spack external find自动检测最少需要完成两步定义与包关联的可执行文件实现确定这些可执行文件版本的方法。最小检测实现第一步只需在包级别声明executables属性class Foo(Package): # Each string provided here is treated as a regular expression, and # would match for example foo, foobar, and bazfoo. executables [foo]该属性必须是字符串列表每个字符串都是一个正则表达式用于在系统 PATH 中圈定可能属于该包的可执行文件。例如gcc会同时匹配gcc、gcc-8.3、my-weird-gcc等如果只想精确匹配名为gcc的可执行文件必须使用^gcc$。第二步是实现determine_version类方法为每个可执行文件确定版本classmethod def determine_version(cls, exe): Return either the version of the executable passed as argument or None if the version cannot be determined. Args: exe (str): absolute path to the executable being examined 该方法接收单个可执行文件的绝对路径返回其版本字符串如果无法确定版本或该可执行文件其实是误报false positive必须返回None以保证该可执行文件被丢弃、不进入后续流程。上述两步是强制性的完成后包就具备了以某版本存在于系统上的基础检测能力。注意任何determine_version返回None的可执行文件都会被丢弃不会出现在后续工作流的任何阶段。从源码看spack external find的检测主流程位于 detection/path.pySpack 按前缀prefix对匹配的可执行文件分组调用包的determine_spec_details其默认实现内部会使用上面这些方法组装出检测结果并逐项做 variant 替换与合法性校验不合法的 spec 会被记录为 debug 信息并跳过。检测变体与自定义属性可选实现determine_variants类方法用于探测 spec 的更多细节classmethod def determine_variants(cls, exes, version_str): Return either a variant string, a tuple of a variant string and a dictionary of extra attributes that will be recorded in packages.yaml or a list of those items. Args: exes (list of str): list of executables (absolute paths) that live in the same prefix and share the same version version_str (str): version associated with the list of executables, as detected by determine_version 输入是位于同一前缀、且版本相同的一组可执行文件路径列表以及该版本字符串返回可以是一个 variant 字符串一个由 variant 字符串与额外属性字典组成的元组上述 1 或 2 的列表当一组可执行文件对应多个 spec 时。返回的额外属性会被记录进packages.yaml供后续复用。例如gcc包会默认记录探测到的编译器最终在packages.yaml中的形态为packages: gcc: externals: - spec: gcc9.0.1 languagesc,c,fortran prefix: /usr extra_attributes: compilers: c: /usr/bin/x86_64-linux-gnu-gcc-9 c: /usr/bin/x86_64-linux-gnu-g-9 fortran: /usr/bin/x86_64-linux-gnu-gfortran-9这样做的好处是可以追踪到若由 Spack 构建会采用不同命名的可执行文件例如x86_64-linux-gnu-gcc-9而非简单的gcc。过滤匹配到的可执行文件纯正则往往难以处理边界情况与干扰项red herrings。包可以可选实现filter_detected_exes类方法用任意自定义逻辑过滤候选可执行文件classmethod def filter_detected_exes(cls, prefix, exes_in_prefix): Return a filtered list of the executables in prefix输入一个前缀与其中匹配的可执行文件列表返回过滤后的列表。经典例子是检测 GNU C 编译器如果直接搜索g会误选中同样是 C 编译器的clang用纯正则表达含g但不含clang非常麻烦而用该方法则很直观class Gcc(Package): executables [g] classmethod def filter_detected_exes(cls, prefix, exes_in_prefix): return [x for x in exes_in_prefix if clang not in x]该方法还可以按条件应用不同过滤逻辑例如仅在特定 OS 上做某些决策。校验检测结果为进一步提升检测鲁棒性可以可选实现validate_detected_spec类方法校验检测出的 Spec 对象classmethod def validate_detected_spec(cls, spec, extra_attributes): Validate a detected spec. Raise an exception if validation fails.该方法接收一个检测到的 spec 及其额外属性可用断言assertion或抛出InvalidSpecDetected异常来执行校验校验失败时该 spec 会被丢弃断言或异常附带的 message 会作为丢弃原因被记录。例如校验compilers属性必须存在classmethod def validate_detected_spec(cls, spec, extra_attributes): Check that compilers is in the extra attributes. msg the extra attribute compilers must be set for the detected spec {0}.format(spec) assert compilers in extra_attributes, msg等价写法显式抛出异常classmethod def validate_detected_spec(cls, spec, extra_attributes): Check that compilers is in the extra attributes. if compilers not in extra_attributes: msg the extra attribute compilers must be set for the detected spec {0}.format( spec ) raise InvalidSpecDetected(msg)自定义检测工作流在极少数情况下以上机制都不适用时可以忽略所有上述方法直接在包类中实现完整的determine_spec_details类方法注意executables属性仍然必须声明classmethod def determine_spec_details(cls, prefix, exes_in_prefix): # exes_in_prefix a set of paths, each path is an executable # prefix a prefix that is common to each path in exes_in_prefix # return None or [] if none of the exes represent an instance of # the package. Return one or more Specs for each instance of the # package which is thought to be installed in the provided prefix ...输入是匹配到的可执行文件集合及它们共享的公共前缀返回一个或多个与该包关联的spack.package.Spec也可以返回None表示这些可执行文件都不属于该包。以虚构包foo-package构建可执行文件foo为例class FooPackage(Package): homepage ... url ... version(...) # Each string provided here is treated as a regular expression, and # would match for example foo, foobar, and bazfoo. executables [foo] classmethod def determine_spec_details(cls, prefix, exes_in_prefix): candidates [x for x in exes_in_prefix if os.path.basename(x) foo] if not candidates: return # This implementation is lazy and only checks the first candidate exe_path candidates[0] exe Executable(exe_path) output exe(--version, outputstr, errorstr) version_str ... # parse output for version string return Spec.from_detection(foo-package{0}.format(version_str))为包编写检测测试为确保检测逻辑在多种配置、不同系统上都正确可以在包目录中与package.py同级放置一个detection_test.yaml。该文件包含足够信息让 Spack 模拟一个环境并验证检测逻辑是否产出预期结果。一般规则是detection_test.yaml的顶层属性代表搜索机制每个属性映射到一组用于确认包检测逻辑有效性的测试。运行检测测试$ spack audit externals检测到的错误会直接输出到屏幕。PATH 检查类测试基于 PATH 检查的测试列在paths属性下paths: - layout: - executables: - bin/clang-3.9 - bin/clang-3.9 script: | echo clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2) echo Target: x86_64-pc-linux-gnu echo Thread model: posix echo InstalledDir: /usr/bin platforms: [linux, darwin] results: - spec: llvm3.9.1 clang~lld~lldb若存在platforms属性测试仅在当前主机匹配所列平台之一时运行。每个测试的执行方式是先按layout在临时目录中创建文件系统结构再运行包检测最后核对结果是否与results一致。各字段含义如下选项名描述允许取值是否必填layout指定测试所用的文件系统树对象列表是layout:[0]:executables要创建的 mock 可执行文件的相对路径字符串列表是layout:[0]:script可执行文件的 mock 逻辑任意合法 shell 脚本是results期望结果列表对象列表无期望结果时为空是results:[0]:spec期望从检测中得到的 spec任意合法 spec是results:[0]:extra_attributes关联 Spec 上期望的额外属性嵌套字典键为字符串叶子值为正则表达式否复用其他包的测试在使用自定义仓库时可以定制builtin中已存在的包并复用其外部检测测试在定制后的package.py旁放置一个带includes属性的detection_test.yaml。例如myrepo.llvm的detection_test.yaml可以写成includes: - builtin.llvm该文件指示 Spack 除本地定义的测试外同时运行builtin.llvm中定义的检测测试。指定 ABI 兼容性Specifying ABI Compatibility警告can_splice指令目前仍是实验特性未来版本的 Spack 可能用更高级的接口替代它。包可以通过can_splice指令声明 ABI 兼容信息。例如Foo的 1.1 版本总是可以替换 1.0 版本则可写can_splice(foo1.0, when1.1)对虚拟包而言还可以声明与其他提供同一虚拟的包的 ABI 兼容性。例如zlib-ng可以声明can_splice(zlib1.3.1, when2.2compat)有些包的 ABI 兼容性取决于 variant 取值是否一致全部 variant 或一部分与 ABI 相关的 variant。此时无需写出全部组合爆炸式的声明match_variants关键字可以覆盖所有单值 variant# any value for bar as long as theyre the same can_splice(foo1.1, when1.2, match_variants[bar]) # any variant values if all single-value variants match can_splice(foo1.2, when1.3, match_variants*)第一行要求bar取值一致第二行的*要求所有单值 variant 一致多值 variant 会被*跳过。从实现上看can_splice在 directives.py 中把声明记录为pkg.splice_specs[when_spec] (target_spec, match_variants)其中match_variants只允许None、*或 variant 名称列表若传入其他字符串会直接抛出ValueError。求解器在 solver/asp.py 的package_splice_rules中把这些声明翻译成拼接规则当match_variants为*时会收集包上全部单值 variant 并逐一生成一致性约束。当 自动拼接automatic splicing 启用时concretizer 会利用这些 ABI 兼容信息自动完成拼接。自定义视图Customizing Views警告这是为了完整性而记录的进阶功能实际使用中很少需要定制。Spack 环境会为其包含的包维护一个视图view这是一个通过符号链接把全部已安装包合并到其中的单一目录方便用户直接访问。PackageViewMixin的方法可以被覆盖以定制包加入视图的方式。有时仅靠符号链接可执行文件无法让应用正常工作必须打补丁。例如bin目录下的 Python 脚本其 shebang 通常指向 Python 安装前缀中的解释器而不是视图中的解释器但如果 shebang 指向视图中的解释器会更方便因为该解释器无需设置PYTHONPATH就能找到视图中的其他 Python 包。因此Python 扩展包继承PythonPackage的包会覆盖add_files_to_view以重写 shebang 行。PackageViewMixin定义于 package_base.py核心方法包括view_source()视图源的根目录默认是self.spec.prefix文件按相对视图目标的位置等于相对视图源的位置加入视图view_destination(view)目标根目录默认是view.get_projection_for_spec(self.spec)view_file_conflicts(view, merge_map)报告哪些文件会阻止加入视图默认实现检查目标位置已存在的文件add_files_to_view(view, merge_map, skip_if_existsTrue)给定源文件到视图目标路径的映射后把文件加入视图默认全部链接skip_if_exists控制目标已存在时是否跳过。需要定制时重写这些方法即可改变包的视图合并行为例如跳过某些文件、或在链接时改写文件内容shebang 重写即属此类。小结高级打包能力的核心是把包元数据与构建指令解耦多构建系统依赖build_system指令与 builder 类体系一个package.py即可承载 Autotools、CMake 等多套构建路径并通过conditional支持版本驱动的构建系统过渡外部包检测通过executablesdetermine_version两步起步可选的determine_variants、filter_detected_exes、validate_detected_spec、determine_spec_details与detection_test.yaml逐步增强检测的细节与鲁棒性ABI 拼接由实验性的can_splice指令声明配合match_variants控制 variant 一致性要求交由求解器在启用自动拼接时使用视图定制通过覆盖PackageViewMixin的方法实现是重写 shebang 等场景的关键入口。如需继续深入可阅读同系列的 创建包、构建 与 测试 指南或参考 advanced_topics.rst 中关于拼接的进一步说明。【免费下载链接】spackA flexible package manager that supports multiple versions, configurations, platforms, and compilers.项目地址: https://gitcode.com/GitHub_Trending/sp/spack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考