Retour au cours

infra / github-actions

Matrix builds

Leçon 51 exercice

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) et exclude (en retirer un)
  • Comprendre le comportement par défaut fail-fast: true et 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 matriceEffet
matrix: { os: [...], node: [...] }Produit cartésien : toutes les combinaisons possibles
includeAjoute une combinaison précise, avec un paramètre supplémentaire
excludeRetire 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: falseToutes 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.

yaml
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
yaml
# 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
yaml
# 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.matrix multiplie un job pour chaque combinaison de dimensions déclarées.
  • include/exclude ajustent finement la matrice (cas particuliers, combinaisons à retirer).
  • fail-fast: false laisse 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

1 disponible
1

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.

Résoudre l’exercice →