CI 流水线

Spack 提供了支持在 CI 实例中生成和运行自动化构建流水线的命令。从最高层面来看,其工作原理如下:提供一个描述你所关心的软件包集合的 Spack 环境,并包含这些软件包应如何映射到 Gitlab Runner 的描述。Spack 随后可以生成一个 .gitlab-ci.yml 文件,其中包含所有软件包的作业描述,这些作业可由配置正确的 CI 实例运行。当流水线运行时,它会构建并部署二进制文件,并可选择向 CDash 实例报告构建的健康状况,以便随时间推移进行监控。

流水线入门

要开始使用自动化构建流水线,需要一个版本 >= 12.9 的 Gitlab 实例(关于 Gitlab CI 的更多信息请见此处),并至少配置一个 Runner。通过搭建本地 Gitlab 实例可以快速实现。

虽然可以在 gitlab.com 上设置流水线,但那里的构建受限于 60 分钟的时长和通用硬件。也可以将 Gitlab 连接 到 Google Kubernetes Engine (GKE) 或 Amazon Elastic Kubernetes Service (EKS),尽管这些主题超出了本文档的范围。

在设置好用于运行 CI 的 Gitlab 实例后,配置构建流水线的基本步骤如下

  1. 在启用了 CI 和 Runner 的 Gitlab 实例中创建一个仓库。

  2. 在根目录下添加一个包含你流水线环境的 spack.yaml

  3. 在根目录下添加一个包含两个作业的 .gitlab-ci.yml(一个用于动态生成流水线,另一个用于运行生成的作业)。

  4. 将包含上述 spack.yaml.gitlab-ci.yml 的提交推送到 GitLab 仓库。

请参阅 功能示例 部分获取最小可行性示例。另请参阅 自定义工作流 部分,获取基于 Spack 流水线的自定义工作流示例链接。

Spack 的流水线现在利用 trigger(触发器)语法来运行动态生成的 子流水线。请注意,使用动态子流水线要求 Gitlab 版本 >= 12.9

功能示例

最简单的功能齐全、独立的流水线示例可以在 gitlab.com 上的这个示例 项目 中实时查看。

以下是该示例中用于构建和运行流水线的 .gitlab-ci.yml 文件

stages: ["generate", "build"]

variables:
  SPACK_REPOSITORY: "https://github.com/spack/spack.git"
  SPACK_REF: "develop-2024-10-06"
  SPACK_USER_CONFIG_PATH: ${CI_PROJECT_DIR}
  SPACK_BACKTRACE: 1

generate-pipeline:
  tags:
  - saas-linux-small-amd64
  stage: generate
  image:
    name: ghcr.io/spack/ubuntu20.04-runner-x86_64:2023-01-01
  script:
  - git clone ${SPACK_REPOSITORY}
  - cd spack && git checkout ${SPACK_REF} && cd ../
  - . "./spack/share/spack/setup-env.sh"
  - spack --version
  - spack env activate --without-view .
  - spack -d -v --color=always ci generate --check-index-only --artifacts-root "${CI_PROJECT_DIR}/jobs_scratch_dir" --output-file "${CI_PROJECT_DIR}/jobs_scratch_dir/cloud-ci-pipeline.yml"
  artifacts:
    paths:
    - "${CI_PROJECT_DIR}/jobs_scratch_dir"

build-pipeline:
  stage: build
  trigger:
    include:
    - artifact: jobs_scratch_dir/cloud-ci-pipeline.yml
      job: generate-pipeline
    strategy: depend
  needs:
  - artifacts: true
    job: generate-pipeline

上面需要注意的关键点是有两个作业:第一个要运行的作业 generate-pipeline 运行 spack ci generate 命令来生成动态子流水线并将其写入 YAML 文件,该文件随后被第二个作业 build-jobs 获取,并用于触发下游流水线。

以下是该流水线构建的、以 spack.yaml 文件表示的 Spack 环境

spack:
  view: false
  concretizer:
    unify: true
    reuse: false

  definitions:
  - pkgs:
    - zlib
    - bzip2 ~debug
  - compiler:
    - "%gcc"

  specs:
  - matrix:
    - - $pkgs
    - - $compiler

  ci:
    target: gitlab

    pipeline-gen:
    - any-job:
        tags:
        - saas-linux-small-amd64
        image:
          name: ghcr.io/spack/ubuntu20.04-runner-x86_64:2023-01-01
        before_script:
        - git clone ${SPACK_REPOSITORY}
        - cd spack && git checkout ${SPACK_REF} && cd ../
        - . "./spack/share/spack/setup-env.sh"
        - spack --version
        - export SPACK_USER_CONFIG_PATH=${CI_PROJECT_DIR}
        - spack config blame mirrors

注意

在用于流水线的 Spack 环境中使用 reuse: false 几乎总是你所需要的,因为如果没有它,即使包哈希已更改,你的流水线也不会重新构建软件包。这是因为当 reuse: true 时,具体化器会强烈倾向于使用已知的哈希。

上述环境文件中的 ci 部分包含了 spack ci generate 创建工作流水线所需的最低配置。 target: gitlab 告诉 Spack 所需的流水线输出是针对 GitLab 的。不过,这并非严格要求,因为目前 GitLab 是流水线唯一可能的输出格式。 pipeline-gen 部分包含了指定生成作业属性所需的关键信息。注意,在本例中它包含一个仅有一个元素的列表。在真实的流水线中,它几乎肯定会有更多元素,在这种情况下,顺序很重要:Spack 在应用属性时从列表底部向上执行。

但在这种简单的情况下,我们只使用特殊键 any-job 来表明 Spack 应将指定的属性(tagsimagebefore_script)应用于其生成的任何作业。这包括用于构建/推送所有软件包的作业、流水线末尾的 rebuild-index 作业,以及在不需要重新构建时 GitLab 可能需要的任何 noop 作业。

值得注意的是,在这种简单的情况下,我们依赖 Spack 生成一个合理的脚本用于软件包构建作业(它只是创建一个调用 spack ci rebuild 的脚本)。

另一件需要注意的事是在任何生成的作业中使用 SPACK_USER_CONFIG_DIR 环境变量。其目的是让 Spack 感知示例中的最后一个文件,即包含镜像配置的文件。这个文件 mirrors.yaml 如下所示

mirrors:
  buildcache-destination:
    url: oci://registry.gitlab.com/spack/pipeline-quickstart
    binary: true
    access_pair:
      id_variable: CI_REGISTRY_USER
      secret_variable: CI_REGISTRY_PASSWORD

请注意镜像名称为 buildcache-destination,这是 Spack 0.23 及更高版本的必需项(更多信息见下文)。镜像 URL 简单地指向与项目关联的容器注册表,而 id_variablesecret_variable 指向包含镜像访问凭据的环境变量。

当 Spack 为此示例项目构建软件包时,它们会被推送到项目容器注册表,后续作业可将其作为依赖项安装,或用于其他流水线以构建可运行的容器镜像。

支持流水线的 Spack 命令

Spack 提供了一个带有几个子命令的 ci 命令,用于支持 Spack CI 流水线。这些命令将在本节中详细介绍。

spack ci

与生成流水线和执行流水线作业相关功能的超级命令。

spack ci generate

在本文档中,对“镜像”的引用是指目标镜像,它会被检查是否存在最新的规范,并且是任何计划作业应推送已构建二进制软件包的目标位置。运行 spack ci generate 时,必须配置一个名为 buildcache-destination 的镜像作为目标镜像。允许将任意数量的其他镜像配置为流水线的源,但只有 buildcache-destination 镜像会被用作目标镜像。

对活动环境中的规范进行具体化、暂存(如 .gitlab-ci.yml 生成算法总结 中所述),并将生成的 .gitlab-ci.yml 写入磁盘。在环境具体化过程中,spack ci generate 还会写入一个 spack.lock 文件,该文件随后被提供给生成的子作业,并在所有生成的作业工件中可用,以帮助在本地环境中重现失败的构建。这意味着在你的流水线生成作业(在你的 .gitlab-ci.yml 中定义)中需要导出两个工件。第一个是 spack ci generate 的输出 yaml 文件,另一个是包含具体环境文件的目录。在 功能示例 部分,我们仅在 artifacts paths 列表中提到了一个路径,因为我们使用了 --artifacts-root 作为包含生成流水线 yaml 和具体环境的顶层目录。

使用 --prune-dag--no-prune-dag 配置是否为在镜像上已是最新的规范生成作业。如果通过 --prune-dag 启用 DAG 修剪,可能需要在你的 spack.yaml 文件中提供更多信息,请参阅下文关于 noop-job无操作 (noop) 部分。

可选的 --check-index-only 参数可用于加速流水线生成,方法是告诉 Spack 在检查远程镜像以确定 DAG 中的每个规范是否为最新时,仅考虑远程构建缓存索引。默认行为是 Spack 获取索引并检查它,但如果规范不在索引中,它也会对镜像上的规范执行直接检查。如果远程构建缓存索引已过期(如果未频繁更新,这很容易发生),此行为可确保 Spack 有一种方法可以确定远程镜像上任何具体规范的状态,但会显著减慢流水线生成速度。

可选的 --output-file 参数应该是生成流水线的绝对路径(包括文件名),如果不提供,默认为 ./.gitlab-ci.yml

虽然可选,但 --artifacts-root 参数用于确定具体化环境目录应位于何处。此目录将由 spack ci generate 创建,并将包含 spack.yaml 和生成的 spack.lock,它们随后会作为工件传递给所有子作业。此目录也将是流水线中作业生成的所有工件的根目录。

spack ci rebuild

spack ci rebuild 的目的是获取分配的规范并确保目标镜像上存在成功的构建二进制文件。如果二进制文件尚不存在,则从源代码构建并推送到镜像。关联的独立测试会根据需要针对新构建运行。此外,还会创建用于在 CI 环境之外重现构建的文件,以方便调试。

如果目标镜像上不存在规范的二进制文件,则会创建一个安装 shell 脚本 install.sh 并保存在当前工作目录中。该脚本在一个作业中运行,从源代码安装规范。生成的二进制软件包被推送到镜像。如果为该环境配置了 cdash,构建结果将上传到该站点。

spack.yaml 环境文件的 ci::pipeline-gen 部分中的环境变量和值为此过程提供了输入。环境变量的两个主要来源是 spack ci generate 写入 .gitlab-ci.yml 的变量以及 GitLab CI 运行时。几个关键的 CI 流水线变量在 影响流水线操作的环境变量 中描述。

如果提供了 --tests 选项,则会执行独立测试,但前提是构建成功 软件包未出现在 broken-tests-packages 列表中。会创建一个 shell 脚本 test.sh 并运行以执行测试。完成后,测试日志将导出为作业工件以供审查和方便调试。如果配置了 cdash,测试结果也会上传到该站点。

下面给出了一个示例 spack.yaml 文件的片段,说明了此选项的使用 包含损坏测试的软件包的规范。此处未显示构建 gptune 的规范。注意 --tests 作为 build-job 脚本的一部分传递给 spack ci rebuild

ci:
  pipeline-gen:
  - build-job:
      script:
      - . "./share/spack/setup-env.sh"
      - spack --version
      - cd ${SPACK_CONCRETE_ENV_DIR}
      - spack env activate --without-view .
      - spack config add "config:install_tree:projections:${SPACK_JOB_SPEC_PKG_NAME}:'morepadding/{architecture.platform}-{architecture.target}/{name}-{version}-{hash}'"
      - mkdir -p ${SPACK_ARTIFACTS_ROOT}/user_data
      - if [[ -r /mnt/key/intermediate_ci_signing_key.gpg ]]; then spack gpg trust /mnt/key/intermediate_ci_signing_key.gpg; fi
      - if [[ -r /mnt/key/spack_public_key.gpg ]]; then spack gpg trust /mnt/key/spack_public_key.gpg; fi
      - spack -d ci rebuild --tests > >(tee ${SPACK_ARTIFACTS_ROOT}/user_data/pipeline_out.txt) 2> >(tee ${SPACK_ARTIFACTS_ROOT}/user_data/pipeline_err.txt >&2)

  broken-tests-packages:
  - gptune

在这种情况下,即使 gptune 从源代码成功构建,流水线也不会运行其独立测试,因为该软件包列在 broken-tests-packages 下。

Spack 的云流水线提供了 Spack 使用的 CI/CD 配置和环境文件的实际、最新示例。你可以在 Spack 的 stacks 仓库目录中找到它们。

spack ci rebuild-index

这是一个用于重新构建活动、启用 GitLab 环境中与镜像关联的构建缓存索引的便捷命令(无需指定镜像 URL 或名称)。

spack ci reproduce-build

给定 GitLab 流水线重建作业的 URL,将工件下载并解压到本地目录(可以通过可选的 --working-dir 参数指定),然后在生成的流水线中找到目标作业以提取有关其运行方式的详细信息。假设作业使用了 docker 镜像,该命令将打印一个 docker run 命令行和一些关于如何在本地重现构建的基本说明。

请注意,在流水线中失败的作业将打印消息,提供你可以传递给 spack ci reproduce-build 的参数,以便在本地重现特定的构建。

作业类型

重建 (build)

重建作业(在 pipeline-gen 列表中称为 build-job)是与标记为重建的具体规范关联的作业。默认情况下,会生成一个用于重建的简单脚本,但可以根据需要进行修改。

默认脚本执行三个主要步骤:更改目录到流水线的具体环境,激活该具体环境,并运行 spack ci rebuild 命令。

cd ${concrete_environment_dir}
spack env activate --without-view .
spack ci rebuild

更新索引 (reindex)

默认情况下,虽然流水线作业可能会重建软件包、创建构建缓存条目并将其推送到镜像,但它之后不会自动重新生成镜像的构建缓存索引。因为流水线中的默认重建作业不需要索引,所以在每个作业结束时不更新索引避免了并发作业之间可能的竞争条件,也避免了重新生成索引的计算开销。根据镜像中二进制软件包的数量,这可能会为每个作业节省数分钟。因此,默认情况下,镜像的构建缓存索引在流水线结束时可能无法正确反映镜像的内容。

为了确保构建缓存索引在流水线结束时是最新的,Spack 默认会在每个流水线结束时生成一个作业来更新目标镜像的构建缓存索引。你可以通过在 Spack 环境的 ci 部分内添加 rebuild-index: False 来禁用此行为。

重新索引作业不允许修改 script 属性,因为它是使用 mirrors::mirror 配置中列出的目标镜像自动生成的。

签名 (signing)

此作业在所有重建作业完成后运行,旨在用于签署由受保护的 CI 运行构建的软件包二进制文件。仅当指定了签名作业 script 且 Spack CI 作业类型受到保护时,才会生成签名作业。注意,如果 any-job 部分包含脚本,这不会隐式创建 signing 作业;只有在配置中通过 script 属性明确指定时,签名作业才可能存在。指定没有脚本的签名作业不会创建签名作业,并且作业配置属性将被忽略。签名作业始终被分配运行器标签 awsprotectednotary

无操作 (noop)

如果给定流水线运行期间环境中没有任何规范需要重建(意味着它们在镜像上都已经是最新的),则仍会生成一个成功的作业(NO-OP),以避免空的流水线(GitLab 认为这是错误的)。可以在你的 spack.yaml 中添加 noop-job* 部分,你可以在其中为生成的 NO-OP 作业提供 tagsimagevariables。此部分还支持提供 before_scriptscriptafter_script,以防你希望在流水线为空的情况下采取一些自定义操作。

以下是将此部分添加到 spack.yaml 的示例

spack:
  ci:
    pipeline-gen:
    - noop-job:
        tags: ["custom", "tag"]
        image:
          name: "some.image.registry/custom-image:latest"
          entrypoint: ["/bin/bash"]
        script::
        - echo "Custom message in a custom script"

上面的示例说明了如何在流水线为空的情况下提供用于运行 NO-OP 作业的属性。唯一可能为你生成的 NO-OP 作业字段是 script,但这仅在你没有自己提供脚本时才会发生。注意在此示例中,script 使用了 :: 符号来规定覆盖行为。如果没有它,echo 命令将被预置到自动生成的脚本中,而不是替换它。

ci.yaml

这是一个描述构建流水线的 Spack 配置文件示例

spack:
  ci:
    target: gitlab
    rebuild_index: true
    broken-specs-url: https://broken.specs.url
    broken-tests-packages:
    - gptune
    pipeline-gen:
    - submapping:
      - match:
        - os=ubuntu24.04
        build-job:
          tags:
          - spack-kube
          image: spack/ubuntu-noble
      - match:
        - os=almalinux9
        build-job:
          tags:
          - spack-kube
          image: spack/almalinux9

  cdash:
    build-group: Release Testing
    url: https://cdash.spack.io
    project: Spack
    site: Spack AWS Gitlab Instance

ci 配置部分用于配置如何生成流水线工作负载,主要是如何将构建规范的作业分配给实例上配置的运行器。配置流水线的主要部分是 pipeline-gen,它是一个作业属性部分列表,使用与 Spack 配置相同的规则(作用域优先级)从底部向上合并。应用这些部分的顺序与 Spack 合并列表时确定作用域优先级的顺序一致。主要有两种部分类型:<type>-job 部分和 submapping 部分。

作业属性部分

每种类型的作业都可以通过 pipeline-gen 列表中的部分来添加或删除属性。特定于作业类型的属性可以使用键 <type>-job 来指定,以便将属性添加到所有 <type> 类型的作业,或使用 <type>-job-remove 来删除 <type> 类型的属性。每个部分只能包含一种类型的作业属性规范,即 build-jobnoop-job 不能并存,但 build-jobbuild-job-remove 可以并存。

注意

*-remove 规范在添加属性规范之前应用。例如,在同一个 pipeline-gen 部分中同时列出 build-jobbuild-job-remove 的情况下,该值在应用该部分后仍将存在于合并的 build-job 中。

所有指定的属性都会转发给生成的 CI 作业,但对属性 tagsimagevariablesscriptbefore_scriptafter_script 进行了特殊处理,因为它们是 Spack CI 生成器明确识别的组件。对于 tags 属性,Spack 将从配置中指定的所有作业中删除保留标签(保留标签)。在某些情况下,例如对于 signing 作业,会根据正在运行的 CI 类型添加回保留标签。

一旦选择了运行器来构建发布规范,build-job* 部分将提供决定运行器上下文中作业详细信息的信息。build-job* 部分中至少有一个必须包含 tags 键,这是一个包含至少一个标签的列表,用于从 GitLab 实例已知的运行器中选择运行器。对于 Docker 执行器类型的运行器,image 键用于指定用于构建发布规范的 Docker 镜像(也可以作为带有 name 指定镜像名称以及 entrypoint 覆盖该镜像默认设置的字典出现)。对于其他类型的运行器,variables 键将非常有用,用于向运行器传递其工作所需的任何信息(例如调度程序参数等)。此处提供的任何 variables 将逐字添加到每个作业。

build-job 部分还允许用户提供自定义的 scriptbefore_scriptafter_script 部分,以应用于在该运行器上调度到的每个作业。这允许用户执行适合其特定工作流的任何自定义准备或清理任务,以及根据需要完全自定义规范的重建。Spack 不会为作业生成 before_scriptafter_script,但如果你不提供自定义 script,Spack 将为你生成一个,假定具体环境目录位于你的 --artifacts-root(或未提供时,在你的 $CI_PROJECT_DIR)内,为你激活该环境,并调用 spack ci rebuild

指定脚本(scriptbefore_scriptafter_script)的部分都作为命令列表或命令列表的列表来读取。如果脚本将通过合并组合,建议将脚本编写为列表的列表。列表合并的默认行为将删除重复命令并可能应用不需要的重新排序,而合并列表的列表将保留局部顺序且从不删除重复命令。编写命令到 CI 目标脚本时,所有列表都会展开并展平为单个列表。

子映射部分

属性规范的一个特殊情况是 submapping 部分,它可用于根据与重建作业关联的软件包规范将作业属性应用于构建作业。submapping 指定为与 build-job/build-job-remove 部分关联的规范 match 列表。match_behavior 有两个选项:可以指定 firstmerge。在这两种情况下,submapping 列表都是从底部向上处理的,然后搜索每个 match 列表以寻找满足每个具体规范的检查 spec.satisfies({match_item}) 的字符串。

match_behavior: first 的情况下,submappings 列表中包含满足规范字符串的第一个 match 部分将应用其 build-job* 属性到与该规范关联的重建作业。这是默认行为,如果未指定 match_behavior,这将是采用的方法。

merge 匹配的情况下,submappings 列表中所有包含满足规范字符串的 match 部分,都将应用其关联的 build-job* 属性到与该规范关联的重建作业。同样,属性将从底部匹配向上到顶部匹配进行合并。

如果在子映射部分中未找到匹配项,则不会应用额外属性。

动态映射部分

对于需要成本优化的大规模 CI,动态映射允许使用由 Web 服务提供的实时映射方案。这种类型的映射不支持 -remove 类型的行为,但它遵循其余的配置合并规则。

动态映射服务需要实现一个单一的 REST API 接口来获取请求 GET <URL>[:PORT][/PATH]?spec=<pkg_name@pkg_version +variant1+variant2%compiler@compiler_version>

示例请求。

https://my-dyn-mapping.spack.io/allocation?spec=zlib-ng@2.1.6 +compat+opt+shared+pic+new_strategies arch=linux-ubuntu20.04-x86_64_v3%gcc@12.0.0

响应示例更新了 kubernetes 请求变量,覆盖了 GitLab 的最大重试次数,并预置了关于由 my-dyn-mapping.spack.io 服务所做修改的说明。

200 OK

{
  "variables":
  {
    "KUBERNETES_CPU_REQUEST": "500m",
    "KUBERNETES_MEMORY_REQUEST": "2G",
  },
  "retry": { "max:": "1"}
  "script+:":
  [
    "echo \"Job modified by my-dyn-mapping.spack.io\""
  ]
}

ci.yaml 配置部分接受 URL 端点以及配置响应处理方式的多个选项。

可以在 allowignore 下分别指定允许和忽略的配置属性列表。也可以在 required 部分下配置必需属性。

使用 timeoutverify_ssl 选项配置客户端超时和 SSL 验证的选项。默认情况下,timeout 设置为 config:timeout 中的选项,verify_ssl 设置为 config:verify_ssl 中的选项。

通过 header 部分可以将标题参数传递给请求。传递给标题的变量值可以是运行时展开的环境变量,例如在运行器上配置的私有令牌。

这是一个指向 my-dyn-mapping.spack.io/allocation 的配置示例。

ci:
  pipeline-gen:
  - dynamic-mapping:
      endpoint: my-dyn-mapping.spack.io/allocation
      timeout: 10
      verify_ssl: true
      header:
        PRIVATE_TOKEN: ${MY_PRIVATE_TOKEN}
        MY_CONFIG: "fuzz_allocation:false"
      allow:
      - variables
      ignore:
      - script
      require: []

损坏的规范 URL

可选的 broken-specs-url 键告诉 Spack 对照已知在 develop 中当前已损坏的规范列表进行检查。如果找到任何此类规范,spack ci generate 命令将失败并显示一条错误消息,告知用户遇到了哪些损坏的规范。这允许流水线提前失败,并避免浪费计算资源去尝试构建不会成功的软件包。

CDash

可选的 cdash 部分提供了 spack ci generate 命令(由 spack ci start 调用)将用于向 CDash 报告的信息。从此环境生成的所有作业都将属于 CDash 内的一个可以随时间跟踪的“构建组”。随着发布的进展,这个构建组可能会有作业添加或删除。URL、项目和站点用于指定应向其报告构建结果的 CDash 实例。

查看 Spack 环境文件的 ci 部分的 schema,以了解那里允许的确切语法。

保留标签

Spack 有一个子集标签(publicprotectednotary),它保留用于分类可能需要特殊权限或访问权限的运行器。标签 publicprotected 用于区分使用公共权限的运行器和具有受保护权限的运行器。notary 标签是一个特殊标签,用于表示具有访问高度保护信息的运行器,这些信息用于使用 signing 作业签署二进制文件。

.gitlab-ci.yml 生成算法总结

矩阵产生的所有规范(或环境中的所有规范)的依赖项都会被计算,并且整个生成的规范集合在运行 ci/pipeline-gen 条目之前会一起暂存,其中每个暂存的规范都会被分配一个运行器。“暂存”是用于描述确定规范应以何种顺序构建的过程的名称,它考虑了关于作业/阶段的 Gitlab CI 规则。在暂存过程中,目标是在流水线的任何阶段最大限度地增加作业数量,同时确保任何阶段的作业仅依赖于先前阶段的作业(因为这些作业保证已经完成)。随着为作业确定了运行器,合并的 any-job*build-job* 部分中的信息被用于填充目标 CI 流水线将使用的作业描述的各个部分。一旦所有作业都分配了运行器,.gitlab-ci.yml 就会被写入磁盘。

上面提供的短示例将导致 readlinencursespkgconf 软件包被暂存并构建在由 spack-k8s 标签选择的运行器上。在此示例中,Spack 假设运行器是 Docker 执行器类型的运行器,因此某些作业将在 centos7 容器中运行,而其他作业将在 ubuntu-18.04 容器中运行。生成的 .gitlab-ci.yml 将在三个阶段中包含 6 个作业。一旦作业生成,在 spack ci generate 命令期间 SPACK_CDASH_AUTH_TOKEN 环境变量的存在,将导致所有作业被放入 CDash 上一个名为“Release Testing”的构建组中(如果该组尚不存在,则会创建它)。

CI 工件目录布局

当使用命令 spack ci rebuild 运行 CI 构建时,会创建多个目录用于存储 CI 作业期间生成的数据。工件的默认根目录是 job_scratch_root。这可以通过将参数 --artifacts-root 传递给 spack ci generate 命令或在构建作业脚本中设置 SPACK_ARTIFACTS_ROOT 环境变量来覆盖。

工件根目录下的顶层目录是 concrete_environmentlogsreproductiontestsuser_data。Spack 不限制写入这些目录中的任何内容,也不要求用户指定的文件写入任何特定目录。

concrete_environment

concrete_environment 目录用于传达 spack ci generate 处理后的 spack.yaml 和 CI 环境的具体 spack.lock

logs

logs 目录包含 Spack 构建日志 spack-build-out.txt 和 Spack 构建环境修改文件 spack-build-mod-env.txt。此外,所有由软件包 Builder 属性 archive_files 指定的文件也会被复制到此处(例如 CMakeBuilder 中的 CMakeCache.txt)。

reproduction

reproduction 目录用于存储 spack ci reproduce-build 命令所需的文件。这包括 repro.jsonconcrete_environment 中所有文件的副本、当前正在构建的规范的具体规范 JSON 文件,以及写入工件根目录的所有文件。

repro.json 文件未版本化,仅设计为与运行 Spack CI 的版本配合使用。repro.json 可能看起来的样子在这里有一个示例。

{
  "job_name": "adios2@2.9.2 /feaevuj %gcc@11.4.0 arch=linux-ubuntu20.04-x86_64_v3 E4S ROCm External",
  "job_spec_json": "adios2.json",
  "ci_project_dir": "/builds/spack/spack"
}

tests

tests 目录用于存储运行 spack test <job spec> 的输出。根据构建的软件包和测试的可用性,此目录中可能有数据,也可能没有数据。

user_data

user_data 目录用于存储不应复制到 reproduction 目录的所有其他内容。用户可以使用此目录存储构建作业生成的额外日志或指标或其他类型的文件。

在流水线中使用自定义 Spack

如果你的运行器没有准备好调用的 Spack 版本,或者如果出于其他原因你想要使用自定义版本的 Spack 来运行流水线,本节提供了一个示例,说明如何通过利用用户提供的流水线脚本相当简单地实现这一点。首先,考虑使用变量指定你想要使用的 Spack 的源和版本,这些变量可以直接写入你的 .gitlab-ci.yml,或由在 GitLab UI 中定义或来自某些上游流水线的 CI 变量提供。假设你选择变量名 SPACK_REPOSPACK_REF 来指代你想要运行流水线的特定 Spack 分叉和分支。然后,你可以在从流水线生成作业和重建作业中调用的自定义 shell 脚本中引用它们。这是本文档顶部的 generate-pipeline 作业,已更新为克隆和 source 自定义 Spack

generate-pipeline:
  tags:
  - <some-other-tag>
  before_script:
  - git clone ${SPACK_REPO}
  - pushd spack && git checkout ${SPACK_REF} && popd
  - . "./spack/share/spack/setup-env.sh"
  script:
  - spack env activate --without-view .
  - spack ci generate --check-index-only --artifacts-root "${CI_PROJECT_DIR}/jobs_scratch_dir" --output-file "${CI_PROJECT_DIR}/jobs_scratch_dir/pipeline.yml"
  after_script:
  - rm -rf ./spack
  artifacts:
    paths:
    - "${CI_PROJECT_DIR}/jobs_scratch_dir"

当你的流水线由 spack ci generate 生成时,这负责获取所需的 Spack 版本。你还希望你的生成的重建作业(所有这些)克隆该版本的 Spack,因此接下来你会按如下方式更新你上面的 spack.yaml

spack:
  # ...
  ci:
    pipeline-gen:
    - build-job:
        tags:
        - spack-kube
        image: spack/ubuntu-noble
        before_script:
        - git clone ${SPACK_REPO}
        - pushd spack && git checkout ${SPACK_REF} && popd
        - . "./spack/share/spack/setup-env.sh"
        script:
        - spack env activate --without-view ${SPACK_CONCRETE_ENV_DIR}
        - spack -d ci rebuild
        after_script:
        - rm -rf ./spack

现在所有生成的重建作业在运行其实际工作负载之前都将使用相同的 shell 脚本来克隆 Spack。

现在想象你有很长的流水线,有许多规范需要构建,并且你指向一个倾向于频繁更改的 Spack 仓库和分支,例如主仓库及其 develop 分支。如果每个子作业都检出 develop 分支,那可能会导致一些作业以一个 Spack 的 SHA 运行,而以后的作业以另一个运行。为了帮助避免此问题,流水线生成过程会保存名为 SPACK_VERSIONSPACK_CHECKOUT_VERSION 的全局变量,这些变量捕获用于生成流水线的 Spack 版本。虽然 SPACK_VERSION 变量简单地包含在流水线生成时由 spack -V 产生的人类可读值,但 SPACK_CHECKOUT_VERSION 变量可用于 git checkout 命令,以确保所有子作业都检出用于生成流水线的相同 Spack 版本。要利用这一点,你可以简单地用 git checkout ${SPACK_CHECKOUT_VERSION} 替换示例 spack.yaml 上方的 git checkout ${SPACK_REF}

另一方面,如果你指向的是你自己控制下的 Spack 仓库和分支,使用捕获的 SPACK_CHECKOUT_VERSION 可能没有好处,你可以只使用你定义的变量(上面示例中的 SPACK_REPOSPACK_REF)进行克隆。

自定义工作流

有许多方法可以利用 Spack CI 流水线来实现用于构建软件包或其他资源的自定义工作流。自定义流水线工作流的一个示例是 Spack 教程容器 repo。该项目使用 GitHub(用于源代码控制)、GitLab(用于自动化 Spack CI 流水线)和 DockerHub 自动构建来构建 Docker 镜像(完整且带有完全填充的二进制镜像),供 Spack 教程的讲师和参与者使用。

看看该仓库,了解它是如何使用 Spack CI 流水线实现的,并参阅仓库根目录下的以下 markdown 文件,以获取描述该工作流的说明和文档:DESCRIPTION.mdDOCKERHUB_SETUP.mdGITLAB_SETUP.mdUPDATING.md

影响流水线操作的环境变量

某些秘密和其他一些信息应通过环境变量提供给流水线基础设施,通常是出于安全原因,但在某些情况下是为了支持其他流水线用例,例如 PR 测试。流水线基础设施使用的环境变量在这里描述。

AWS_ACCESS_KEY_ID

可选。仅在二进制镜像为 S3 存储桶时需要。

AWS_SECRET_ACCESS_KEY

可选。仅在二进制镜像为 S3 存储桶时需要。

S3_ENDPOINT_URL

可选。仅在二进制镜像为 AWS 上的 S3 存储桶时需要。

CDASH_AUTH_TOKEN

可选。仅在向 CDash 报告构建组时需要。

SPACK_SIGNING_KEY

可选。仅当你希望 spack ci rebuild 信任你存储在此变量中的密钥时才需要,在这种情况下,它随后将被用于签署和验证二进制软件包(在安装或创建构建缓存时)。你也可以已经信任了 Spack 已知的密钥,或者如果没有密钥存在于任何地方,Spack 将使用 --no-check-signature 安装规范,并使用 -u(用于未签名的二进制文件)创建构建缓存。

SPACK_CI_BUILDCACHE_VIEW

可选。仅在使用指向构建缓存视图的 buildcache-destination 镜像时需要。此选项会影响 reindex 作业的行为(更新索引 (reindex)),并且可以具有值 forceappend。每个选项的行为由 更新构建缓存索引视图 更详细地描述。默认选项是 append,因为这是 Spack 构建农场所使用的选项。

警告

append 选项与构建缓存索引视图一起使用是一种非原子操作。由 CI 维护者负责确保对构建缓存的并发写入得到适当处理。