結論

たとえば、こんなci.ymlだったとき、実行されるジョブはmatrix下のそれぞれのリストの直積となります。つまり、(ubuntu-latest, 3.7), (ubuntu-latest, 3.8), (ubuntu-latest, 3.9), (macos-latest, 3.8)

1
2
3
4
5
6
7
8
jobs:
<job_id>:
strategy:
matrix:
os: [ ubuntu-latest, macos-latest ]
python: [ 3.7, 3.8, 3.9]

runs-on: ${{ matrix.os }}

この場合だと2*3=6個のジョブが走るため、無料枠の2000分を使い切ってしまいそうです。実際にはCIで動作確認する組み合わせはもっと少なくしたいので、jobs.<job_id>.strategy.matrix.includejobs.<job_id>.strategy.matrix.excludeを使って必要なケースだけにすることもできます(次の例は特に意味はありません)。しかし、excludeが多くなるにつれて行数が多くなり、しかもexcludeは直感的ではないので設定しづらいです。ましてやmatrix下のリストの数が増えたりしたら非常に面倒です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jobs:
<job_id>:
strategy:
matrix:
os: [ ubuntu-latest, macos-latest ]
python: [ 3.7, 3.8, 3.9]
exclude:
- os: ubuntu-latest
python: 3.8
- os: ubuntu-latest
python: 3.9
- os: macos-latest
python: 3.7

runs-on: ${{ matrix.os }}

そこで次のように解決することができます。jobs.<job_id>.matrix.<var>のリストの要素にはオブジェクトも入れることができるので次のようにすることが可能です(configは任意の名前です)。あくまでもmatrixの下のリストの直積なので、この場合はリストが一つ、つまりその直積は「このリストの各要素を唯一の要素とするタプル」の集合です。

1
2
3
4
5
6
7
8
9
10
jobs:
<job_id>:
strategy:
matrix:
config:
- { os: ubuntu-latest, python: 3.7 }
- { os: macos-latest, python: 3.6 }
- { os: macos-latest, python: 3.8 }

runs-on: ${{ matrix.config.os }}

こっちのほうが明らかにわかりやすいですね。使うときには$のようにします。

参考