Spec 语法¶
Spack 有一种特定的语法来描述软件包约束。每个约束单独被称为一个 spec(规格)。Spack 使用 spec 来
引用软件包的特定构建配置,或
通过配置文件表达软件包的需求或偏好,或
查询已安装的软件包或构建缓存
Spec 不仅仅是软件包名称和版本;您可以使用它们来指定构建的编译器、编译器版本、架构、编译选项和依赖项选项。在本节中,我们将介绍 spec 的完整语法。
以下是使用复杂 spec 来安装 mpileaks 特定配置的示例
$ spack install mpileaks@1.2:1.4 +debug ~qt target=x86_64_v3 %gcc@15 ^libelf@1.1 %clang@20
下图有助于您了解构成此 spec 的各个部分
安装时,您将获得
mpileaks软件包,版本介于1.2和1.4之间(含),启用了
debug选项,且没有qt支持,针对
x86_64_v3架构进行了优化,使用
gcc版本15构建,依赖于
libelf版本1.1,该依赖项使用clang版本20构建。
大多数 spec 不会像这个例子这样复杂,但这是 spec 功能的一个很好的演示。我们可以从第一个例子中推断出几条通用规则
用户在描述软件包构建细节时,可以根据需要模糊或精确
Spec 语法是递归的,即
%或^之后的每个依赖项本身也是一个 spec传递依赖项位于
^符号之后,它们始终指向根软件包直接依赖项位于
%符号之后,它们指向根软件包或最后定义的传递依赖项
Spec 语法在指定构建细节时提供的灵活性使得 Spack 对初学者和专家同样友好。
软件模型¶
为了真正理解上述内容,我们需要考虑软件是如何构建的。可执行文件或库通常依赖于其他库才能运行。我们可以将软件包与其依赖项之间的关系表示为图。这是 mpileaks 的简化依赖图
上图中的每个方框都是一个软件包,每个箭头代表对其他软件包的依赖。例如,我们说 mpileaks 依赖于 callpath 和 mpich。mpileaks 还间接依赖于 dyninst、libdwarf 和 libelf,因为这些库是 callpath 的依赖项。要安装 mpileaks,Spack 必须构建所有这些软件包。Spack 中的依赖图必须是无环的,并且依赖于关系是有方向的,因此这是一个有向无环图,即 DAG。
Spec 中的软件包名称标识符是某个依赖 DAG 的根,而 DAG 本身是隐式的。Spack 知道软件包之间精确的依赖关系,但用户不需要知道完整的 DAG 结构。完整 spec 中的每个 ^ 都指代根软件包的传递依赖项。每个 % 都指代直接依赖项,即根软件包或最后定义的传递依赖项。
在需要一致性的情况下,Spack 仅允许每个软件包存在单一配置。在上面的例子中,mpileaks 和 callpath 都依赖于 mpich,但 mpich 在 DAG 中仅出现一次。您不能构建一个依赖于某个 mpich 版本,同时又依赖于一个依赖于其他 mpich 版本的 callpath 的 mpileaks 版本。通常情况下,这种配置在运行时可能会表现异常,Spack 强制执行此规则以确保运行环境的一致性。
Spec 的目的是为 Spack 用户抽象掉整个 DAG。完全不关心 DAG 的用户只需简单地输入以下内容即可引用 mpileaks
mpileaks
如果用户知道 mpileaks 间接使用 dyninst 并希望使用特定版本的 dyninst,spec 会变得稍微复杂一点
mpileaks ^dyninst@8.1
Spack 会在安装 spec 之前补全其余细节。用户只需要了解软件包名称及其关系的最基本细节。您可以在依赖项 spec 上使用与根 spec 相同的修饰符。也就是说,您可以像指定任何其他 spec 一样指定它们的版本、变体和架构。说明符与其左侧最近的软件包名称相关联。
虚拟依赖项¶
我们上面看到的 mpileaks 依赖图并不完全准确。mpileaks 使用 MPI,这是一种具有多种实现的接口。上面我们展示了 mpileaks 和 callpath 依赖于 mpich,这是 MPI 的一种特定实现。然而,我们可以使用其他实现来构建它们,例如 openmpi 或 mvapich。
Spack 使用虚拟依赖项来表示此类接口。对于 mpileaks,真正的依赖 DAG 看起来像这样
请注意,mpich 现在已被 mpi 取代。没有真实的 MPI 软件包,但某些软件包提供 MPI 接口,当构建 mpileaks 时,这些软件包可以替代 mpi。
Spack 的独特之处在于其虚拟软件包可以像普通软件包一样进行版本控制。软件包的特定版本可以提供虚拟软件包的特定版本。软件包可以依赖于虚拟软件包的特定版本。例如,如果应用程序需要 MPI-2 功能,它可以依赖于 mpi@2:,以表明它需要提供 MPI-2 功能的实现。
以下是关于您可以添加到 spec 中的说明符的更多详细信息。
版本说明符¶
版本说明符
pkg@specifier
位于软件包名称之后,并以 @ 开头。它可以是匹配多个已知版本的抽象描述,也可以是特定版本。
版本说明符通常表示版本范围
# All versions between v1.0 and v1.5.
# This includes any v1.5.x version
@1.0:1.5
# All versions up to and including v3
# This would include v3.4 etc.
@:3
# All versions above and including v4.2
@4.2:
但也可以是特定版本
# Exactly version v3.2, will NOT match v3.2.1 etc.
@=3.2
作为简写,@3 等同于范围 @3:3,包含主要版本为 3 的任何版本。版本按其组件进行字典序排序。有关排序的更多详细信息,请参阅 打包指南。
请注意,您可以区分特定版本 @=3.2 和范围 @3.2。这对于遵循省略零补丁版本号的软件包很有用:3.2、3.2.1、3.2.2 等。通常,使用范围语法 @3.2 更可取,因为范围也匹配带有后缀的版本,例如 3.2-custom。
版本说明符也可以是以逗号分隔的范围和特定版本的列表。例如
@1.0:1.5,=1.7.1
匹配范围 1.0:1.5 中的任何版本和特定版本 1.7.1。
Git 版本¶
注意
希望仅匹配基于分支或标签的特定提交版本的用户应分配 commit 变体(commit=<40 char sha>)。Spack 专门保留此变体来跟踪基于 git 的版本的来源。Spack 将尝试在具体化(concretization)过程中自动计算此值,如果无法分配提交,则会发出警告。更多详细信息可在 Git 版本来源 中找到。
对于具有 git 属性的软件包,可以指定 git 引用(即分支、标签和提交)来代替数字版本。Spack 将基于所提供的 git 引用进行暂存和构建。可接受的语法包括
# commit hashes
foo@abcdef1234abcdef1234abcdef1234abcdef1234 # 40 character hashes are automatically treated as git commits
foo@git.abcdef1234abcdef1234abcdef1234abcdef1234
# branches and tags
foo@git.develop # use the develop branch
foo@git.0.19 # use the 0.19 tag
Spack 总是需要将 Spack 版本与 git 引用关联,该版本用于版本比较。此 Spack 版本是通过启发式方法从 git 引用的祖先中最接近的有效 git 标签获取的。
一旦 Spack 版本与 git 引用关联,它总是会与 git 引用一起打印。例如,如果提交 @git.abcdefg 被标记为 0.19,则 spec 将显示为 @git.abcdefg=0.19。
如果 git 引用不完全是标签,则到最近标签的距离也是解析版本的一部分。@git.abcdefg=0.19.git.8 意味着该提交距离 0.19 标签有 8 次提交。
在 Spack 无法从 git 引用解析出合理版本的情况下,用户可以指定用于该 git 引用的 Spack 版本。这可以通过在 git 引用后追加 = 和 Spack 版本来实现。例如
foo@git.my_ref=3.2 # use the my_ref tag or branch, but treat it as version 3.2 for version comparisons
foo@git.abcdef1234abcdef1234abcdef1234abcdef1234=develop # use the given commit, but treat it as develop for version comparisons
关于版本如何比较以及 Spack 如何确定一个版本是否小于另一个版本的详细信息,请参阅开发者指南。
变体(Variants)¶
变体是与特定软件包关联的命名选项,通常用于在构建时启用或禁用某些功能。它们是可选的,因为每个软件包必须为其提供的每个变体提供默认值。
特定软件包可用的变体由软件包作者定义。spack info <package> 将提供有关可用构建变体的信息。
变体有不同类型。
布尔变体¶
通常用于在编译时启用或禁用功能。例如,软件包可能具有一个 debug 变体,可以通过以下方式显式启用
+debug
并通过以下方式禁用
~debug
单值变体¶
通常用于设置默认值。例如,软件包可能有一个 compression 变体来确定默认压缩算法,用户可以将其设置为
compression=gzip
或
compression=zstd
多值变体¶
软件包可能具有一个 fabrics 变体,用于确定支持哪些网络架构(fabrics)。用户可以同时激活多个值。例如
fabrics=verbs,ofi
同时启用了 InfiniBand verbs 和 OpenFabrics 接口。这些值以逗号分隔。
fabrics=verbs,ofi 的含义是启用至少指定的架构,但也可能启用其他架构。如果意图是仅启用指定的架构,则应使用
fabrics:=verbs,ofi
语法及 := 运算符。
变体向依赖项的传播¶
Spack 允许变体通过对布尔变体使用 ++、-- 和 ~~ 将其值传播到软件包的依赖项。例如,对于 debug 变体
mpileaks ++debug # enabled debug will be propagated to dependencies
mpileaks +debug # only mpileaks will have debug enabled
为了传播非布尔变体的值,Spack 使用 name==value。例如,对于 stackstart 变体
mpileaks stackstart==4 # variant will be propagated to dependencies
mpileaks stackstart=4 # only mpileaks will have this variant value
Spack 还允许变体从不具备该变体的软件包中进行传播。
编译器标志¶
编译器标志使用与非布尔变体相同的语法,但实现不同的目的。变体的功能由软件包设置,而编译器标志则被编译器包装器用于将标志注入构建的编译命令行。此外,可以通过使用 == 将编译器标志继承给依赖项。spack install libdwarf cppflags=="-g" 将安装 libdwarf 和 libelf,并将 -g 标志注入它们的编译命令行中。
请注意,如果编译器标志的值包含空格,则必须加引号。cppflags=-O3、cppflags="-O3"、cppflags='-O3' 和 cppflags="-O3 -fPIC" 都是可接受的,但 cppflags=-O3 -fPIC 不可接受。此外,如果编译器标志的值不是行尾内容,则必须后跟一个空格。命令 spack install libelf cppflags="-O3"%intel 将被解释为尝试设置 cppflags="-O3%intel"。
这六个编译器标志按 GNU Autotools 中隐式 make 命令相同的顺序注入。如果设置了所有标志,对于 C 和 C++,顺序为 $cppflags $cflags|$cxxflags $ldflags <command> $ldlibs;对于 Fortran,顺序为 $fflags $cppflags $ldflags <command> $ldlibs。
架构说明符¶
Spec 依赖图中的每个节点都有一个架构属性。该属性是平台、操作系统和处理器的三元组。您可以通过使用保留关键字 platform、os 和 target 分别指定这些元素
$ spack install libelf platform=linux
$ spack install libelf os=ubuntu18.04
$ spack install libelf target=broadwell
通常,如果用户正在为当前主机安装软件,则无需费心指定架构,因为在这种情况下会自动检测这些值。如果您需要对哪些软件包使用哪些目标(或对所有软件包的默认目标)进行精细控制,请参阅 软件包偏好设置。
对特定微架构的支持¶
Spack 知道如何检测许多特定的微架构并对其进行优化,并将此信息编码在架构规范的 target 部分中。可以通过以下方式获取 Spack 已知的微架构完整列表
$ spack arch --known-targets
Generic architectures (families)
aarch64 armv8.1a armv8.3a armv8.5a armv9.0a ppc64 ppcle sparc x86 x86_64_v2 x86_64_v4
arm armv8.2a armv8.4a armv8.6a ppc ppc64le riscv64 sparc64 x86_64 x86_64_v3
GenuineIntel - x86
i686 pentium2 pentium3 pentium4 prescott
GenuineIntel - x86_64
nocona nehalem sandybridge haswell skylake cannonlake cascadelake sapphirerapids
core2 westmere ivybridge broadwell mic_knl skylake_avx512 icelake
AuthenticAMD - x86_64
k10 bulldozer piledriver zen steamroller zen2 zen3 excavator zen4 zen5
IBM - ppc64
power7 power8 power9 power10
IBM - ppc64le
power8le power9le power10le
Cavium - aarch64
thunderx2
Fujitsu - aarch64
a64fx
ARM - aarch64
cortex_a72 neoverse_n1 neoverse_v1 neoverse_v2 neoverse_n2
Ampere - aarch64
ampere1 ampere1a
Apple - aarch64
m1 m2 m3 m4
SiFive - riscv64
u74mc
SpacemiT - riscv64
x60
安装 spec 时,Spack 会将所使用的编译器与目标微架构进行匹配,以便在编译时注入适当的优化标志。给出如下命令
$ spack install zlib target=icelake %gcc@14
将生成类似于以下的编译行
$ /usr/bin/gcc-14 -march=icelake-client -mtune=icelake-client -c ztest10532.c
$ /usr/bin/gcc-14 -march=icelake-client -mtune=icelake-client -c -fPIC -O2 ztest10532.
...
其中标志 -march=icelake-client -mtune=icelake-client 由 Spack 根据请求的目标和编译器注入。
如果 Spack 确定所请求的编译器无法为所请求的目标进行优化,或者完全无法为该目标构建二进制文件,它将退出并显示有意义的错误消息
$ spack install zlib target=icelake %gcc@5
==> Error: cannot produce optimized binary for micro-architecture "icelake" with gcc@5.5.0 [supported compiler versions are 8:]
相反,如果为较新的微架构选择了较旧的编译器,Spack 将针对最佳匹配进行优化,而不会失败
$ spack arch
linux-ubuntu18.04-broadwell
$ spack spec zlib%gcc@4.8
Input spec
--------------------------------
zlib%gcc@4.8
Concretized
--------------------------------
zlib@1.2.11%gcc@4.8+optimize+pic+shared arch=linux-ubuntu18.04-haswell
$ spack spec zlib%gcc@9.0.1
Input spec
--------------------------------
zlib%gcc@9.0.1
Concretized
--------------------------------
zlib@1.2.11%gcc@9.0.1+optimize+pic+shared arch=linux-ubuntu18.04-broadwell
例如,在上面的代码片段中,当使用 gcc@4.8 编译时,微架构被降级为 haswell,因为 broadwell 的优化支持从 gcc@4.9: 开始。
最后,如果 Spack 没有匹配编译器和目标的信息,它将继续安装,但避免注入任何特定于微架构的标志。
依赖项¶
DAG 中的每个节点都可以使用 % 或 ^ 符号指定依赖项
%符号标识直接依赖项,这意味着必须有一条边将依赖项连接到它们所引用的节点。^符号标识传递依赖项,这意味着依赖项只需要存在于它们所引用的节点的子 DAG 中。
编写 spec 时,传递依赖项的顺序无关紧要。例如,这两个 spec 代表完全相同的配置
mpileaks ^callpath@1.0 ^libelf@0.8.3
mpileaks ^libelf@0.8.3 ^callpath@1.0
使用 % 指定的直接依赖项要么应用于最近的传递依赖项(^),如果不存在,则应用于 spec 中的根软件包。因此,在 spec 中
root %dep1 ^transitive %dep2 %dep3
dep1 是 root 的直接依赖项,而 dep2 和 dep3 都是 transitive 的直接依赖项。
约束虚拟软件包¶
安装依赖于虚拟软件包的软件包时,请参阅 虚拟依赖项,您可以选择指定要使用的特定提供程序,或者让 Spack 进行选择。例如,如果您只是输入
$ spack install mpileaks
那么 Spack 将根据站点策略为您选择一个 mpi 提供程序。如果您确实想要特定的版本,例如 mpich,那么您可以改为运行
$ spack install mpileaks ^mpich
这强制 Spack 使用某个版本的 mpich 作为其实现。一如既往,您可以更加具体,要求特定的 mpich 版本
$ spack install mpileaks ^mpich@3
特别是 mpileaks 软件包只需要 MPI-1 命令,因此任何 MPI 实现都可以。如果另一个软件包依赖于 mpi@2,而您尝试为其提供不足的 MPI 实现(例如,仅提供 mpi@:1),则 Spack 将引发错误。同样,如果您尝试插入某些不提供 MPI 的软件包,Spack 也会引发错误。
虚拟依赖项的显式绑定¶
有些软件包提供的不仅仅是一个虚拟依赖项。在与它们交互时,用户可能只想使用它们可以提供的一小部分,而使用其他提供程序来满足他们需要的虚拟依赖项。
可以使用特殊的语法更明确地告诉 Spack 哪个依赖项应该提供哪个虚拟依赖项
$ spack spec strumpack ^mpi=intel-parallel-studio+mkl ^lapack=openblas
将上面的 spec 具体化会产生以下 DAG
其中 intel-parallel-studio 可以提供 mpi、lapack 和 blas,但仅被用于前者。lapack 和 blas 依赖项由 openblas 满足。
依赖边属性¶
某些 spec 需要关于软件包与其依赖项之间关系的额外信息。此信息位于两者之间的边上,并且可以通过在依赖项符号后加上方括号 [] 来指定。边属性总是作为键值对指定
root ^[key=value] dep
在以下章节中,我们将讨论 spec 语法中当前允许的边属性。
虚拟依赖(Virtuals)¶
软件包可以提供或依赖多个虚拟软件包。用户可以通过指定 virtuals 边属性来选择使用哪个依赖项的哪些虚拟依赖项
$ spack install mpich %[virtuals=c,cxx] clang %[virtuals=fortran] gcc
上面的命令告诉 Spack 使用 clang 提供 c 和 cxx 虚拟依赖,并使用 gcc 提供 fortran 虚拟依赖。
我们在 虚拟依赖项的显式绑定 中看到的特殊语法是指定 virtuals 边属性的一种更紧凑的方式。例如,上述命令的等效写法是
$ spack install mpich %c,cxx=clang %fortran=gcc
条件依赖项¶
条件依赖项允许仅在特定条件下应用依赖约束。我们可以通过指定 when 边属性来表达条件约束
$ spack install hdf5 ^[when=+mpi] mpich@3.1
这告诉 Spack,如果 hdf5 配置了 MPI 支持,则它应依赖于 mpich@3.1。
依赖传播¶
节点上的依赖说明可以使用双百分号 %% 符号进行传播。这在指定编译器时特别有用。例如,以下命令
$ spack install hdf5+cxx+fortran %%c,cxx=clang %%fortran=gfortran
告诉 Spack 使用 Clang 作为 C 和 C++ 编译器,并使用 GCC 作为 Fortran 编译器来安装 hdf5。它还告诉 Spack 将相同的选择作为 强偏好 传播给 hdf5 的运行时子 DAG。构建工具不受影响,仍可以选择使用不同的编译器。
通过哈希指定 Spec¶
复杂的 spec 在命令行中输入可能会变得繁琐,尤其是当需要许多限定条件来区分相似的安装时。为避免这种情况,在引用现有 spec 时,Spack 允许您通过哈希值引用它们。我们之前讨论过 Spack 计算的 spec 哈希值。在任何命令中,用 /<hash> 替换 spec,其中 <hash> 是 spec 哈希值开头的任意长度部分。
例如,假设您意外安装了两个不同的 mvapich2 安装。如果您想卸载其中一个但不知道区别是什么,您可以运行
$ spack find --long mvapich2
==> 2 installed packages.
-- linux-centos7-x86_64 / gcc@6.3.0 ----------
qmt35td mvapich2@2.2%gcc
er3die3 mvapich2@2.2%gcc
然后,您可以使用以下命令卸载后者
$ spack uninstall /er3die3
或者,如果您想将特定的安装作为依赖项进行构建,可以使用
$ spack install trilinos ^/er3die3
如果给定的 spec 哈希足够长且唯一,Spack 将用它所指代的 spec 替换该引用。否则,它会提示输入更具体的哈希。
注意,对于由 spack uninstall --force 卸载的依赖项,此方法无法用于重新安装。
命令行上的 Spec¶
Spec 语法中使用的字符是经过精心选择的,以便与大多数 Shell 良好配合。然而,在某些情况下,Shell 可能会在 Spack 有机会解析 spec 之前对其进行解释,从而导致意外结果。在此,我们记录两种此类情况以及如何避免它们。
Unix Shell¶
在类 Unix 系统上,Shell 可能会将 ~foo 扩展为名为 foo 的用户的主目录,因此 Spack 不会将其视为 已禁用的布尔变体 foo。要在不使用引号的情况下解决此问题,您可以避免在软件包名称和布尔变体之间留空格
mpileaks ~debug # shell may expand this to `mpileaks /home/debug`
mpileaks~debug # use this instead
或者,您可以使用连字符 - 字符来禁用变体,但请注意这需要在软件包名称和变体之间留出空格
mpileaks-debug # wrong: refers to a package named "mpileaks-debug"
mpileaks -debug # right: refers to a package named mpileaks with debug disabled
作为最后手段,debug=False 也可以用来禁用布尔变体。
Windows CMD¶
在 Windows CMD 中,插入符号 ^ 是一个转义字符,需要对其自身进行转义。同样,等号 = 在 CMD 中也具有特殊含义。
要在 spec 中使用插入符号和等号字符,您可以像这样引用并转义它们
C:\> spack install mpileaks "^^libelf" "foo=bar"
PowerShell 中不存在这些问题。有关更多详细信息,请参阅 GitHub issue #42833 和 #43348。