矩阵构建 Matrix
上一章我们写了一个 Node 项目的 CI:在 ubuntu-latest 上、用 Node 18 跑测试。可现实里你往往想要更多:
- "我的库要同时支持 Node 18 / 20 / 22。"
- "我得确认它在 Linux、Windows、macOS 上都能跑。"
- "最好这些组合都能并行测一遍。"
如果为每个组合都手写一个 job,YAML 会膨胀得没法维护。GitHub Actions 给出的答案就是 矩阵(matrix):用一份配置,自动展开成多个并行 job 实例。
1. 矩阵的基本心智模型
strategy.matrix 定义在 job 上(不是 step 上)。你声明若干"维度",每个维度是一个取值列表;GitHub Actions 会做笛卡尔积,把每种组合都生成一个独立的 job 实例并并行运行。
最简单的单维度例子——在三个 Node 版本上各跑一遍:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test这里 matrix.node-version 是一个占位符:对 18、20、22 三个值,GitHub 会分别启动 3 个 job 实例,每个实例里 ${{ matrix.node-version }} 被替换成对应的值。于是你只写了 1 个 job 定义,却得到了 3 次测试。
strategy.matrix 只能写在 jobs.<job_id> 下面,不能写在某个 step 里。矩阵决定的是"这个 job 被复制成几份",而不是"某个 step 怎么变"。
2. 多维度:版本 × 操作系统
矩阵真正的威力在多维度。下面同时指定 node-version 和 os 两个维度:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test注意这里 runs-on 也用了 ${{ matrix.os }},因为不同操作系统要在不同 runner 上跑。
组合数量 = os 取值数 × node-version 取值数 = 3 × 3 = 9 个并行 job。也就是说,9 种"操作系统 × Node 版本"组合,你只写了 1 份配置。
在 step 中引用矩阵值的方式始终是:${{ matrix.维度名 }}。例如:
${{ matrix.node-version }}${{ matrix.os }}
node-version、os 只是约定俗成的名字,你可以任意命名维度(比如 python-version、browser)。引用时只要 ${{ matrix.你的维度名 }} 对得上即可。
3. fail-fast:一个失败要不要连累其他
默认情况下矩阵的 fail-fast 为 true:只要任意一个组合失败,GitHub 会尽快取消其余还在排队的组合。这能节省资源,但在"我想看清楚到底哪些组合挂了"的调试阶段会很烦——因为看到一个失败,其他还没跑出来的结果就被取消了。
把它设为 false 可以关闭这种行为:所有组合都会跑完,互不cancel:
strategy:
fail-fast: false
matrix:
node-version: [18, 20, 22]当你在排查"到底哪个版本不兼容"时,把 fail-fast: false 打开(设为 false),让所有组合都跑完,你就能在结果页一次看到完整的兼容性矩阵,而不是反复重试。
4. continue-on-error:允许某些组合失败
有时你想表达"这个组合是实验性的,挂了也不要阻断整个 workflow"。比如"正式支持到 Node 20,但想顺带在 Node 22 上探探路,挂了无所谓"——这时给那个组合单独标 continue-on-error: true:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
include:
# 给 Node 22 这个组合打上"允许失败"标记
- node-version: 22
experimental: true
continue-on-error: ${{ matrix.experimental || false }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test这里我们用到了 include(见下节)给 Node 22 追加了一个 experimental: true 变量,再在 job 级别用 continue-on-error: ${{ matrix.experimental || false }} 让它"允许失败"。即使这个组合测试挂了,整个 workflow 仍标记为成功。
continue-on-error: true 的组合失败了,workflow 整体仍算"成功",但它依然会在结果里标记为失败(黄色),方便你事后查看。别把它当成"悄无声息忽略错误"的开关。
5. exclude 与 include:精细裁剪组合
笛卡尔积有时候会生成你不想要的组合,或者想额外补充的组合。这就是 exclude 和 include 的用武之地。
exclude:从笛卡尔积里剔除指定组合。include:向结果里追加组合(或给已有组合补充额外变量)。
经典例子:我们不想在 Windows 上测老旧的 Node 18(比如某依赖在 Windows 上只支持新 Node),就从矩阵里去掉 windows-latest + node-version 18:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
exclude:
# 去掉 "Windows + Node 18" 这个我们不需要的组合
- os: windows-latest
node-version: 18
include:
# 额外追加一个组合:在 ubuntu 上用 Node 20 跑,并标记它是"带覆盖率"的变体
- os: ubuntu-latest
node-version: 20
coverage: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
# coverage 变量只在 include 追加的组合上为 true
- run: npx nyc report --reporter=lcov
if: ${{ matrix.coverage }}- 想"减掉某些组合" →
exclude(写你要删掉的那组维度值)。 - 想"加组合 / 给某组合塞额外变量" →
include(写完整的一组维度值,可多带字段)。 - 二者常配合:先用大矩阵铺开,再用
exclude删冗余,用include补特例。
6. 矩阵的最大扇出与可维护性
矩阵让"多版本 × 多 OS"变得轻而易举,但也要有节制:
- GitHub 对每个 workflow 的并发 job 数、矩阵总组合数有上限(默认单次运行矩阵展开的 job 受账户配额限制)。
- 维度不要贪多:3 个维度各 3 个值就是 27 个 job,跑得慢、还烧配额。
- 把"必然一起失败"的组合用
exclude砍掉,省时间也省钱。
高级用法里,矩阵的某个维度可以取"对象列表",而不只是标量。例如 node-version: [{node: 18, npm: 9}, {node: 20, npm: 10}],这样每个组合能同时携带多个相关字段。入门阶段用标量列表就够。
7. 小结
本章你学到了:
strategy.matrix写在 job 上,多个维度做笛卡尔积,自动展开成多个并行 job 实例。- 在 step /
runs-on中用${{ matrix.维度名 }}引用当前实例的矩阵值。 fail-fast: false:一个组合失败不取消其他组合(调试友好);默认fail-fast为true。continue-on-error: true:允许某些组合失败而不阻断整体 workflow。exclude删除不需要的组合,include追加额外组合或补充变量。- 矩阵让我们用一套配置覆盖"多版本 × 多 OS",极大减少重复 YAML。
到目前为止,每个 job 都是"自给自足"——自己 checkout、自己跑。但真实项目里常常需要 job A 产出文件、job B 接着用(比如先构建出 dist/,再部署)。下一章(Artifacts 构件传递)就来讲:job 之间如何传递文件。
-
下面这段矩阵的
node-version: [16, 18, 20]与os: [ubuntu-latest, macos-latest],最终会展开成几个 job?分别是什么组合?strategy: matrix: node-version: [16, 18, 20] os: [ubuntu-latest, macos-latest] -
我想"只要有一个组合失败,就立刻取消其余组合",矩阵里的
fail-fast应该设成true还是false?默认是什么? -
写出一段矩阵配置:在
ubuntu-latest和windows-latest上测 Node 18 和 20,但排除windows-latest + node-version 18这个组合。 -
exclude和include各自用来干什么?请用一句话分别概括。