Contexte technique

Dans un projet où un Makefile invoque un autre make (appel récursif), la variable d’environnement jobserver doit être transmise afin que le sous‑make partage le même nombre de processus parallèles que le parent. Sans cette transmission, GNU Make désactive le mode parallèle et affiche un avertissement, ce qui annule le gain de performance recherché.

ser: make -C subdir
par2: make -j2 -C subdir
parplus: +make -j -C subdir
sermake: $(MAKE) -C subdir

Le texte décrit plusieurs variantes de cette invocation : appel direct (make), appel préfixé par +, et appel via la variable spéciale $(MAKE). Chaque variante a été testée avec les options -j (1, 2, 4 ou « ∞ ») sur les versions GNU Make 3.81, 4.0, 4.2.1 et 4.3.

Comportement du jobserver selon les versions

Les tableaux fournis montrent trois types d’avertissements :

a : « jobserver unavailable: using -j1 » – le sous‑make ne reçoit aucune information de jobserver et revient à un seul processus.

b : « -jN forced in submake: disabling/resetting jobserver mode » – le sous‑make reçoit une valeur -jN explicite, ce qui désactive le mode partagé et impose son propre degré de parallélisme.

Avec GNU Make 3.81, toute invocation sans + ou $(MAKE) déclenche l’avertissement a. En version 4.0, l’avertissement a apparaît uniquement lorsque la règle parent ne comporte pas le préfixe +. Les versions 4.2.1 et 4.3 introduisent le message b enrichi du nombre de jobs forcés (-j0, -j2, etc.) et modifient le comportement des cibles par, parplus et parmake, qui passent de « ∞ » à « ∞ » avec un « b » indiquant que le jobserver a été réinitialisé.

Analyse des mécanismes sous‑jacents

Le jobserver repose sur un pipe partagé entre processus ; le parent écrit le nombre de slots disponibles et les enfants les consomment. Lorsqu’une règle n’est pas marquée +, GNU Make considère la ligne comme potentiellement non‑déterministe et ferme le pipe avant d’exécuter la commande, ce qui empêche la transmission du token. L’utilisation de $(MAKE) force GNU Make à reconnaître la commande comme un sous‑make légitime, conservant ainsi le pipe ouvert. Cependant, même avec $(MAKE), le sous‑make peut recevoir une option -jN explicite (ex. make -j2 -C subdir), ce qui déclenche l’avertissement b et réinitialise le jobserver, car le sous‑make estime que le parent a imposé un degré de parallélisme qui ne correspond pas à la capacité du jobserver partagé.

Les versions 4.2.1 et 4.3 ont ajouté la capacité de signaler le nombre exact de jobs forcés, ce qui aide à diagnostiquer les conflits, mais ne résout pas le problème fondamental : le jobserver ne peut pas être partagé lorsque le sous‑make spécifie explicitement -j. Le seul moyen fiable d’éviter les deux avertissements consiste à laisser le sous‑make hériter du jobserver sans ajouter d’option -j, et à préfixer la ligne avec + ou $(MAKE) afin que le pipe reste ouvert.

Recommandations pratiques

Pour garantir un parallélisme cohérent dans des builds récursifs, il faut :

1. Utiliser $(MAKE) ou préfixer la commande par + dans chaque règle qui invoque un sous‑make.

2. Éviter d’ajouter -j dans les recettes récursives ; laisser le parent contrôler le nombre de jobs.

3. Vérifier les messages d’avertissement lors de la migration entre versions de GNU Make, car les libellés a et b évoluent mais reflètent le même problème de perte du jobserver.

En suivant ces principes, les projets C/C++ ou tout autre code compilé avec GNU Make peuvent exploiter pleinement le parallélisme, même sur des chaînes de compilation profondes.