Skip to content

Latest commit

 

History

History
494 lines (357 loc) · 17.9 KB

File metadata and controls

494 lines (357 loc) · 17.9 KB

🔝 Retour au Sommaire

2.3.2 — Intégration avec CMake et Makefiles

Chapitre 2 · Section 2.3 · Accélération de compilation · Niveau Débutant


Introduction

Dans la section précédente, nous avons vu comment invoquer ccache manuellement (ccache g++ -c main.cpp). En pratique, personne ne tape les commandes de compilation à la main sur un projet réel — c'est le build system (CMake + Ninja, CMake + Make, ou un Makefile écrit à la main) qui orchestre les appels au compilateur. L'enjeu est donc de faire en sorte que chaque appel au compilateur passe par ccache de manière transparente, sans modifier le code ni les fichiers source du projet.

Trois approches existent, chacune adaptée à un contexte différent. Nous les présentons de la plus recommandée à la plus globale.


Approche 1 : CMAKE_<LANG>_COMPILER_LAUNCHER (recommandée)

Principe

Depuis CMake 3.4, la variable CMAKE_<LANG>_COMPILER_LAUNCHER permet de spécifier un programme qui sera inséré devant chaque invocation du compilateur. Quand on configure cette variable avec ccache, CMake génère des règles de build qui ressemblent à :

ccache /usr/bin/g++-15 -std=c++23 -O2 -c main.cpp -o main.o

Le build system (Ninja ou Make) ne voit rien de particulier — il exécute cette commande comme n'importe quelle autre. ccache intercepte l'appel, vérifie le cache, et retourne le résultat caché ou délègue au compilateur réel.

Configuration en ligne de commande

La manière la plus directe :

cmake -B build -G Ninja \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache

Ces deux variables couvrent respectivement les compilations C (gcc) et C++ (g++). Spécifiez les deux pour être certain que tous les fichiers sources passent par ccache.

Vérifier que ccache est actif dans le build

Après la configuration, vous pouvez vérifier que ccache est bien intégré en lançant un build avec l'option verbose :

cmake --build build --verbose 2>&1 | head -20

Vous devriez voir ccache apparaître devant chaque appel au compilateur dans la sortie :

[1/4] ccache /usr/bin/g++-15 -std=c++23 -O2 ... -c main.cpp -o CMakeFiles/programme.dir/main.cpp.o
[2/4] ccache /usr/bin/g++-15 -std=c++23 -O2 ... -c utils.cpp -o CMakeFiles/programme.dir/utils.cpp.o
...

Si ccache n'apparaît pas, vérifiez que le binaire est bien dans votre PATH (which ccache) et que les variables ont été correctement passées lors de la configuration (cmake -B build ... -DCMAKE_CXX_COMPILER_LAUNCHER=ccache).

⚠️ Les variables CMAKE_<LANG>_COMPILER_LAUNCHER sont des variables de cache CMake : elles sont fixées lors de la première configuration (cmake -B build ...) et persistent dans le répertoire de build. Si vous avez déjà configuré le projet sans ccache, deux options s'offrent à vous : supprimer le répertoire de build et reconfigurer, ou modifier la variable dans le cache existant :

# Option A : reconfigurer de zéro
rm -rf build
cmake -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache

# Option B : modifier le cache existant
cmake -B build -DCMAKE_CXX_COMPILER_LAUNCHER=ccache

Configuration dans le CMakeLists.txt

Certaines équipes préfèrent inscrire le support ccache directement dans le CMakeLists.txt du projet, de manière à ce qu'il soit automatiquement activé si ccache est présent sur la machine, et silencieusement ignoré sinon :

cmake_minimum_required(VERSION 3.25)  
project(MonProjet LANGUAGES CXX)  

# Activer ccache automatiquement s'il est disponible
find_program(CCACHE_PROGRAM ccache)  
if(CCACHE_PROGRAM)  
    message(STATUS "ccache trouvé : ${CCACHE_PROGRAM} — activation automatique")
    set(CMAKE_C_COMPILER_LAUNCHER   "${CCACHE_PROGRAM}")
    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
else()
    message(STATUS "ccache non trouvé — compilation sans cache")
endif()

set(CMAKE_CXX_STANDARD 23)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  

add_executable(mon_programme main.cpp utils.cpp)

Cette approche a l'avantage d'être auto-adaptative : sur une machine de développement où ccache est installé, la compilation est accélérée ; sur un serveur de production ou dans un conteneur minimal sans ccache, le build fonctionne normalement. Le message(STATUS ...) informe le développeur de l'état lors de la configuration.

Variante : autoriser la désactivation via une option

Pour les projets d'équipe, il peut être utile d'offrir un contrôle explicite :

option(USE_CCACHE "Utiliser ccache pour accélérer la compilation" ON)

if(USE_CCACHE)
    find_program(CCACHE_PROGRAM ccache)
    if(CCACHE_PROGRAM)
        set(CMAKE_C_COMPILER_LAUNCHER   "${CCACHE_PROGRAM}")
        set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
        message(STATUS "ccache activé : ${CCACHE_PROGRAM}")
    else()
        message(WARNING "USE_CCACHE est ON mais ccache n'est pas trouvé dans le PATH")
    endif()
endif()

Un développeur qui souhaite désactiver ccache peut alors passer -DUSE_CCACHE=OFF lors de la configuration.

Configuration via CMake Presets

Les CMake Presets (fichier CMakePresets.json versionné avec le projet) sont la manière la plus propre de standardiser la configuration de build au sein d'une équipe. Voici un exemple intégrant ccache :

{
    "version": 6,
    "cmakeMinimumRequired": {
        "major": 3,
        "minor": 25,
        "patch": 0
    },
    "configurePresets": [
        {
            "name": "dev-gcc",
            "displayName": "Développement GCC (avec ccache)",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build-gcc",
            "cacheVariables": {
                "CMAKE_CXX_COMPILER": "g++-15",
                "CMAKE_C_COMPILER": "gcc-15",
                "CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
                "CMAKE_C_COMPILER_LAUNCHER": "ccache",
                "CMAKE_CXX_STANDARD": "23",
                "CMAKE_BUILD_TYPE": "Debug"
            }
        },
        {
            "name": "dev-clang",
            "displayName": "Développement Clang (avec ccache)",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build-clang",
            "cacheVariables": {
                "CMAKE_CXX_COMPILER": "clang++-20",
                "CMAKE_C_COMPILER": "clang-20",
                "CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
                "CMAKE_C_COMPILER_LAUNCHER": "ccache",
                "CMAKE_CXX_STANDARD": "23",
                "CMAKE_BUILD_TYPE": "Debug"
            }
        },
        {
            "name": "release",
            "displayName": "Release (sans ccache)",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build-release",
            "cacheVariables": {
                "CMAKE_CXX_COMPILER": "g++-15",
                "CMAKE_C_COMPILER": "gcc-15",
                "CMAKE_CXX_STANDARD": "23",
                "CMAKE_BUILD_TYPE": "Release"
            }
        }
    ]
}

Utilisation :

# Configurer avec le preset dev-gcc (ccache activé)
cmake --preset dev-gcc

# Compiler
cmake --build --preset dev-gcc

# Configurer avec le preset release (sans ccache)
cmake --preset release

Chaque membre de l'équipe utilise les mêmes presets, garantissant une configuration identique. Les presets de développement incluent ccache pour la vitesse ; le preset release peut l'omettre si la reproductibilité bit-à-bit est prioritaire. Nous approfondirons les CMake Presets à la section 27.6.


Approche 2 : Liens symboliques via le PATH

Principe

Cette approche exploite les liens symboliques créés par le paquet ccache dans /usr/lib/ccache. En plaçant ce répertoire en tête du PATH, chaque appel à g++, gcc, clang++, etc. est intercepté par ccache de manière totalement transparente — ni CMake, ni Make, ni aucun autre outil n'a besoin d'être modifié.

Mise en place

Ajoutez cette ligne à votre ~/.bashrc (ou ~/.zshrc selon votre shell) :

# Activer ccache globalement via le PATH
export PATH="/usr/lib/ccache:$PATH"

Rechargez le shell :

source ~/.bashrc

Vérification

which g++
# Output :
# /usr/lib/ccache/g++

# Vérifier que c'est bien un lien vers ccache
file /usr/lib/ccache/g++
# Output :
# /usr/lib/ccache/g++: symbolic link to ../../bin/ccache

# Le vrai compilateur reste accessible
/usr/bin/g++ --version | head -1

Désormais, toute compilation C/C++ sur cette machine passe par ccache, quel que soit l'outil qui l'invoque : CMake, Make, Ninja, scripts manuels, ./configure && make, etc.

Fonctionnement interne

Quand ccache est invoqué sous le nom g++ (via le lien symbolique), il examine le nom sous lequel il a été appelé (argv[0]), détermine que le compilateur réel est /usr/bin/g++, et procède à sa logique habituelle de hash/cache. Ce mécanisme fonctionne pour tous les compilateurs supportés : gcc, g++, cc, c++, clang, clang++, et leurs variantes versionnées (g++-15, clang++-20, etc.).

Gestion des versions de compilateur

Selon votre installation Ubuntu et la version du paquet ccache, les liens symboliques versionnés (comme g++-15 ou clang++-20) peuvent ne pas être présents dans /usr/lib/ccache. Si c'est le cas, vous pouvez les créer manuellement :

sudo ln -sf /usr/bin/ccache /usr/lib/ccache/g++-15  
sudo ln -sf /usr/bin/ccache /usr/lib/ccache/gcc-15  
sudo ln -sf /usr/bin/ccache /usr/lib/ccache/clang++-20  
sudo ln -sf /usr/bin/ccache /usr/lib/ccache/clang-20  

Vérification :

which g++-15
# Output attendu :
# /usr/lib/ccache/g++-15

Avantages et inconvénients

Aspect Verdict
Simplicité de mise en place ✅ Excellente — une ligne dans .bashrc
Couverture ✅ Totale — tous les outils en bénéficient
Transparence ✅ Aucune modification des fichiers de build
Explicite / traçable ❌ Un collègue ne voit pas que ccache est actif
Reproductibilité ❌ Dépend de la configuration du poste
Contrôle fin ❌ Pas de moyen d'exclure un projet spécifique

Désactiver temporairement

Si vous avez besoin de compiler sans ccache ponctuellement (par exemple pour un benchmark de compilation ou pour diagnostiquer un problème) :

# Option 1 : contourner le PATH pour une commande
CCACHE_DISABLE=1 cmake --build build

# Option 2 : utiliser le chemin absolu du compilateur
/usr/bin/g++-15 -std=c++23 -O2 -c main.cpp -o main.o

# Option 3 : retirer temporairement ccache du PATH
export PATH=$(echo "$PATH" | sed 's|/usr/lib/ccache:||')

La variable d'environnement CCACHE_DISABLE=1 est la méthode la plus propre : ccache reçoit l'appel mais le transmet immédiatement au compilateur réel sans consulter ni alimenter le cache.


Approche 3 : Substitution du compilateur CMake (non recommandée)

Une troisième approche, historique mais déconseillée, consiste à définir ccache g++ comme compilateur CMake :

# ⚠️ Non recommandé
cmake -B build -G Ninja -DCMAKE_CXX_COMPILER="ccache;g++-15"

ou avec un script wrapper :

#!/bin/bash
# /usr/local/bin/ccache-g++
exec ccache g++-15 "$@"
cmake -B build -G Ninja -DCMAKE_CXX_COMPILER=/usr/local/bin/ccache-g++

Ces approches fonctionnent techniquement, mais posent des problèmes : CMake ne détecte plus correctement le compilateur (les checks internes échouent parfois), les IDEs peuvent être confus, et l'identification de la version du compilateur dans les messages de CMake est erronée. La variable CMAKE_CXX_COMPILER_LAUNCHER a été créée spécifiquement pour résoudre ces problèmes — utilisez-la.


Intégration avec un Makefile pur

Si vous travaillez avec un Makefile écrit à la main (sans CMake), deux méthodes s'offrent à vous.

Méthode 1 : Préfixer le compilateur dans le Makefile

La méthode la plus explicite :

# Makefile avec ccache
CXX      = ccache g++-15  
CC       = ccache gcc-15  
CXXFLAGS = -std=c++23 -Wall -Wextra -O2  
CFLAGS   = -Wall -Wextra -O2  

SRCS     = main.cpp utils.cpp engine.cpp  
OBJS     = $(SRCS:.cpp=.o)  
TARGET   = programme  

$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: clean

Dans cette configuration, make invoque ccache g++-15 ... pour chaque compilation. La syntaxe ccache g++-15 est traitée par le shell comme une commande (ccache) suivie de ses arguments (g++-15, les flags, etc.).

Méthode 2 : Compter sur le PATH

Si /usr/lib/ccache est en tête de votre PATH (approche 2), vous n'avez rien à modifier dans le Makefile :

# Makefile classique — ccache est transparent via le PATH
CXX      = g++-15  
CC       = gcc-15  
CXXFLAGS = -std=c++23 -Wall -Wextra -O2  

# ... (le reste du Makefile est identique)

g++-15 résout vers /usr/lib/ccache/g++-15, qui est ccache. Aucune modification du Makefile nécessaire.

Méthode 3 : Variable optionnelle

Pour un Makefile qui fonctionne avec ou sans ccache, selon la préférence du développeur :

# Le développeur peut activer ccache avec :  make CCACHE=ccache
CCACHE   ?=  
CXX       = $(CCACHE) g++-15  
CC        = $(CCACHE) gcc-15  
CXXFLAGS  = -std=c++23 -Wall -Wextra -O2  

SRCS     = main.cpp utils.cpp engine.cpp  
OBJS     = $(SRCS:.cpp=.o)  
TARGET   = programme  

$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: clean

Utilisation :

# Sans ccache
make -j$(nproc)

# Avec ccache
make -j$(nproc) CCACHE=ccache

La variable ?= donne une valeur par défaut (vide) qui peut être surchargée en ligne de commande. Quand CCACHE est vide, $(CCACHE) g++-15 se réduit à g++-15 (Make ignore l'espace en tête).


Intégration avec Meson

Si vous travaillez avec le build system Meson (présenté à la section 28.4), l'intégration se fait différemment. Meson ne dispose pas d'un équivalent direct à CMAKE_CXX_COMPILER_LAUNCHER, mais il détecte et utilise ccache automatiquement s'il est présent dans le PATH. Ce comportement est activé par défaut.

Pour forcer ou désactiver cette détection :

# Forcer l'utilisation de ccache (créer un fichier cross/native)
# Dans un native file (ex: native.ini) :
# [built-in options]
# cmake_prefix_path = ...

# Ou simplement via le PATH (méthode recommandée)
export PATH="/usr/lib/ccache:$PATH"  
meson setup build  

Pour désactiver ccache avec Meson :

# Via la variable d'environnement
CCACHE_DISABLE=1 meson setup build

Combinaison ccache + compilateur + linker : la chaîne complète

Pour un temps de build optimal, ccache ne suffit pas : il n'accélère que la compilation (transformation de .cpp en .o). L'étape de liaison (assemblage des .o en exécutable ou bibliothèque) n'est pas cachée par ccache. Pour accélérer cette étape également, combinez ccache avec le linker rapide lld (projet LLVM) ou mold :

# Installation de lld
sudo apt install lld

# Configuration CMake complète : ccache + Ninja + lld
cmake -B build -G Ninja \
    -DCMAKE_CXX_COMPILER=clang++-20 \
    -DCMAKE_C_COMPILER=clang-20 \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \
    -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld"

Avec GCC, vous pouvez également utiliser lld ou mold :

# Installation de mold
sudo apt install mold

# Configuration CMake : ccache + Ninja + mold + GCC
cmake -B build -G Ninja \
    -DCMAKE_CXX_COMPILER=g++-15 \
    -DCMAKE_C_COMPILER=gcc-15 \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" \
    -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold"

La chaîne complète ccache + Ninja + lld (ou mold) est celle qui offre les meilleurs temps de build en 2026 :

Composant Accélère Remplace
ccache Compilation (.cpp.o) Rien — intercepte le compilateur
Ninja Orchestration du build Make
lld / mold Liaison (.o → exécutable) GNU ld

Résumé des approches

Approche Portée Explicite Effort Contexte recommandé
CMAKE_CXX_COMPILER_LAUNCHER Un projet CMake ✅ Oui Faible Projets CMake (recommandé)
CMake Presets Un projet CMake ✅ Oui Moyen Projets d'équipe avec CMake
find_program dans CMakeLists.txt Un projet CMake ✅ Oui Moyen Projets open source
PATH /usr/lib/ccache Toute la machine ❌ Non Faible Développement local, tous outils
Préfixe ccache dans Makefile Un projet Make ✅ Oui Faible Makefiles sans CMake
Wrapper script / substitution Un projet ❌ Non Élevé ❌ Éviter

Pour la suite de cette formation, nous utiliserons systématiquement l'approche CMAKE_CXX_COMPILER_LAUNCHER=ccache, soit en ligne de commande, soit via CMake Presets.


Ce qu'il faut retenir

  • CMAKE_CXX_COMPILER_LAUNCHER=ccache est la méthode recommandée pour les projets CMake : elle est explicite, portable, et compatible avec tous les générateurs (Ninja, Make, etc.).
  • Le PATH /usr/lib/ccache est la méthode la plus simple pour un usage global : une seule ligne dans .bashrc et toute compilation C/C++ de la machine est accélérée, quel que soit le build system.
  • N'utilisez pas la substitution du compilateur (CMAKE_CXX_COMPILER="ccache;g++") — la variable COMPILER_LAUNCHER existe pour ça.
  • Pour un Makefile sans CMake, préfixez la variable CXX ou utilisez l'approche PATH.
  • Combinez ccache avec Ninja (orchestration rapide) et lld ou mold (liaison rapide) pour des temps de build optimaux.
  • CCACHE_DISABLE=1 permet de désactiver ccache ponctuellement sans modifier la configuration.

⏭️ Statistiques et monitoring du cache