infra / github-actions
Matrix builds
Explication
Ce que vous allez apprendre
- Comprendre le principe du produit cartésien qui génère toutes les combinaisons d'une matrice
- Écrire un job matriciel testant plusieurs OS et versions de Node.js en une seule déclaration
- Ajuster finement une matrice avec
include(ajouter un cas) etexclude(en retirer un) - Comprendre le comportement par défaut
fail-fast: trueet savoir quand le désactiver - Anticiper le coût en minutes CI d'une matrice mal dimensionnée
Dans quel contexte ?
Une bibliothèque JavaScript doit rester compatible avec Node 18, 20 et 22, sur Linux, macOS et Windows. Sans matrix build, il faudrait écrire et maintenir neuf jobs quasi identiques, ne différant que par deux paramètres — un vrai cauchemar de maintenance dès qu'il faut changer une seule étape commune à tous. Le matrix build élimine cette duplication : le job est décrit une seule fois, et GitHub Actions génère lui-même les neuf combinaisons à exécuter.
Le problème : dupliquer un job pour chaque combinaison
D'abord, imagine devoir tester ton application sur trois systèmes d'exploitation et trois versions différentes de Node.js : cela ferait neuf combinaisons.
Ce que ça donnerait sans outil dédié
Copier-coller neuf fois le même job en changeant juste deux paramètres serait fastidieux et source d'erreurs de copier-coller.
La solution : décrire le job une seule fois
Le matrix build automatise exactement cela : tu décris une seule fois le job, en indiquant les dimensions qui doivent varier, et GitHub Actions génère et exécute automatiquement toutes les combinaisons.
| Élément de matrice | Effet |
|---|---|
matrix: { os: [...], node: [...] } | Produit cartésien : toutes les combinaisons possibles |
include | Ajoute une combinaison précise, avec un paramètre supplémentaire |
exclude | Retire une combinaison qui n'a pas de sens |
fail-fast: true (défaut) | Un échec arrête immédiatement toutes les autres combinaisons |
fail-fast: false | Toutes les combinaisons s'exécutent jusqu'au bout, même en cas d'échec |
Le principe mathématique derrière une matrice
Le principe du "produit cartésien" est à la base des matrices : si tu déclares 3 systèmes et 3 versions de Node, tu obtiens par défaut 9 exécutions, pas seulement 3.
Piège fréquent
Ajouter une dimension supplémentaire à une matrice multiplie le nombre total d'exécutions, pas ne l'additionne pas : passer de 3x3 (9 exécutions) à 3x3x3 (27 exécutions) en ajoutant une troisième dimension surprend souvent par le coût en minutes CI consommées d'un coup.
Pourquoi c'est important à anticiper
Une matrice mal dimensionnée peut générer beaucoup plus d'exécutions que prévu, avec un coût en temps et en minutes CI consommées.
Ajuster finement au-delà du produit automatique
Une fois cette logique comprise, include et exclude permettent d'y échapper : ajouter une combinaison particulière avec un paramètre supplémentaire, ou au contraire retirer une combinaison qui n'a pas de sens.
Ce que ça permet concrètement
Par exemple activer la couverture de code sur une seule combinaison précise, ou retirer une version de Node trop ancienne incompatible avec un OS donné.
Que faire d'un échec partiel
Par défaut, un échec sur une seule combinaison de la matrice arrête immédiatement toutes les autres, fail-fast: true.
Bonne pratique
Passe fail-fast: false dès que tu veux diagnostiquer un problème de compatibilité multi-plateforme : voir l'état de TOUTES les combinaisons d'un coup (par exemple "ça casse uniquement sur Windows avec Node 18") est bien plus utile qu'un arrêt prématuré à la première combinaison en échec.
Pourquoi ce comportement n'est pas toujours souhaitable
C'est utile pour économiser des ressources dès qu'une erreur évidente est détectée, mais parfois contre-productif : on préfère souvent voir l'état de TOUTES les combinaisons avant de tirer des conclusions.
La solution pour obtenir cette vue complète
Désactiver ce comportement, avec fail-fast: false, donne une vue complète, au prix d'un peu plus de temps d'exécution.
Maintenant que tu sais tester plusieurs configurations à la fois, la leçon suivante s'attaque à un autre gaspillage de temps : réinstaller les mêmes dépendances à chaque exécution, avec le cache.
Commandes & code
Matrix builds
Exécute automatiquement le même job pour toutes les combinaisons d'une matrice de paramètres.
name: Cross-platform tests
on: push
jobs:
test:
strategy:
fail-fast: false # ne stoppe pas les autres combinaisons si une échoue
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: ["18", "20", "22"]
include:
- os: ubuntu-latest
node-version: "20"
coverage: true # ajoute un champ supplémentaire à CETTE combinaison précise
exclude:
- os: windows-latest
node-version: "18" # cette combinaison ne sera jamais exécutée
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- if: matrix.coverage
run: npm run test:coverage# Limiter le nombre de jobs exécutés en parallèle (utile pour ménager des ressources partagées)
jobs:
test:
strategy:
max-parallel: 2
matrix:
shard: [1, 2, 3, 4]
runs-on: ubuntu-latest
steps:
- run: npm test -- --shard=${{ matrix.shard }}/4# Matrice générée dynamiquement à partir d'un job précédent (ex: liste de packages d'un monorepo)
jobs:
discover:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.list.outputs.packages }}
steps:
- uses: actions/checkout@v4
- id: list
run: echo "packages=$(ls packages | jq -R -s -c 'split("\n")[:-1]')" >> "$GITHUB_OUTPUT"
test:
needs: discover
strategy:
matrix:
package: ${{ fromJson(needs.discover.outputs.packages) }}
runs-on: ubuntu-latest
steps:
- run: npm test --workspace=packages/${{ matrix.package }}Résumé
strategy.matrixmultiplie un job pour chaque combinaison de dimensions déclarées.include/excludeajustent finement la matrice (cas particuliers, combinaisons à retirer).fail-fast: falselaisse toutes les combinaisons se terminer même si l'une échoue.- Une matrice peut être générée dynamiquement via
fromJson()depuis un job précédent.
Exercices pratiques
Mission : une matrice qui explose le nombre d'exécutions
Objectif : Redimensionner une matrice mal pensée, exclure une combinaison connue incompatible, et garder une vue complète des échecs.
Contexte
Une équipe teste sa bibliothèque sur 3 systèmes d'exploitation et 4 versions de Node.js (12 exécutions). Pour couvrir aussi l'architecture ARM, quelqu'un ajoute une troisième dimension arch: [x64, arm64] sans réaliser l'effet sur le nombre total d'exécutions, et le budget de minutes CI de l'équipe est englouti en une seule journée. De plus, la combinaison windows-latest + Node 16 est connue pour être incompatible et devrait être exclue, et le fail-fast par défaut masque l'état réel des autres combinaisons dès qu'une seule échoue.