Skip to content

Latest commit

 

History

History
514 lines (375 loc) · 17.2 KB

File metadata and controls

514 lines (375 loc) · 17.2 KB

🔝 Retour au Sommaire

36.1.1 — Installation et premiers pas

Section 36.1 : CLI11 — Parsing d'arguments professionnel


Introduction

Cette sous-section couvre l'intégration de CLI11 dans un projet C++ sur Ubuntu, de l'installation à la compilation d'un premier programme fonctionnel. CLI11 étant header-only, l'installation se résume à rendre les headers disponibles pour votre compilateur. Plusieurs méthodes existent, de la plus simple (copie d'un fichier unique) à la plus intégrée (CMake FetchContent).


Méthode 1 : CMake FetchContent (recommandée)

C'est la méthode recommandée pour tout projet utilisant CMake. FetchContent télécharge CLI11 au moment de la configuration et l'intègre comme une dépendance de première classe, avec gestion automatique des versions.

Structure du projet

mon-outil/
├── CMakeLists.txt
├── src/
│   └── main.cpp
└── build/           # Créé lors de la compilation

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)  
project(mon-outil VERSION 1.0.0 LANGUAGES CXX)  

set(CMAKE_CXX_STANDARD 23)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  

# --- Dépendance CLI11 via FetchContent ---
include(FetchContent)  
FetchContent_Declare(  
    cli11
    GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
    GIT_TAG        v2.6.0   # Vérifier la dernière version stable
)
FetchContent_MakeAvailable(cli11)

# --- Exécutable ---
add_executable(mon-outil src/main.cpp)  
target_link_libraries(mon-outil PRIVATE CLI11::CLI11)  

# --- Bonnes pratiques : warnings stricts ---
target_compile_options(mon-outil PRIVATE
    -Wall -Wextra -Wpedantic -Werror
)

Points importants :

  • CLI11::CLI11 est la target CMake exportée par CLI11. C'est la seule chose à lier — les include paths sont configurés automatiquement.
  • GIT_TAG pointe vers un tag de release. Ne pointez jamais vers main dans un projet de production : épinglez toujours une version précise pour la reproductibilité des builds (voir section 27.2 sur Conan et la gestion des dépendances).
  • CLI11 étant header-only, target_link_libraries ne lie aucune librairie binaire — il ne fait qu'ajouter les chemins d'inclusion.

Compilation

cd mon-outil  
cmake -B build -G Ninja          # Configuration avec le générateur Ninja  
cmake --build build               # Compilation  
./build/mon-outil --help          # Test

💡 Ninja comme générateur : nous recommandons -G Ninja plutôt que le Makefile par défaut pour ses temps de build plus rapides, particulièrement sur les projets multi-fichiers (voir section 26.5 et section 28.3).

Première exécution de FetchContent

Lors du premier cmake -B build, CMake clone le dépôt CLI11. Ce téléchargement n'intervient qu'une seule fois — les exécutions suivantes utilisent le cache. Si vous travaillez dans un environnement CI, pensez à mettre en cache le répertoire build/_deps/ pour accélérer les builds (voir section 38.3 sur l'accélération CI).


Méthode 2 : Conan 2.0

Si votre projet utilise déjà Conan pour la gestion des dépendances, CLI11 est disponible dans le dépôt central ConanCenter.

conanfile.txt

[requires]
cli11/2.4.2

[generators]
CMakeDeps  
CMakeToolchain  

Intégration CMake

cmake_minimum_required(VERSION 3.16)  
project(mon-outil VERSION 1.0.0 LANGUAGES CXX)  

set(CMAKE_CXX_STANDARD 23)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  

find_package(CLI11 REQUIRED)

add_executable(mon-outil src/main.cpp)  
target_link_libraries(mon-outil PRIVATE CLI11::CLI11)  

Installation et compilation

cd mon-outil  
conan install . --output-folder=build --build=missing  
cmake -B build --preset conan-release  
cmake --build build  

La target CMake reste identique (CLI11::CLI11) — seul le mécanisme de résolution change. Le choix entre FetchContent et Conan est une question d'écosystème de projet : si vous gérez déjà plusieurs dépendances avec Conan, restez cohérent. Pour un projet autonome avec CLI11 comme seule dépendance externe, FetchContent est plus simple.


Méthode 3 : vcpkg

Avec le gestionnaire de paquets de Microsoft :

vcpkg install cli11

Puis dans CMake, en utilisant le toolchain file vcpkg :

find_package(CLI11 REQUIRED)  
target_link_libraries(mon-outil PRIVATE CLI11::CLI11)  
cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake  
cmake --build build  

Méthode 4 : Paquet système (Ubuntu)

CLI11 est packagé dans les dépôts Ubuntu :

sudo apt install libcli11-dev

Cette méthode est la plus rapide pour démarrer, mais présente un inconvénient : la version disponible dans les dépôts Ubuntu peut être en retard par rapport aux releases officielles. Vérifiez la version installée :

apt show libcli11-dev | grep Version

L'intégration CMake est identique :

find_package(CLI11 REQUIRED)  
target_link_libraries(mon-outil PRIVATE CLI11::CLI11)  

⚠️ En production, préférez FetchContent ou Conan pour maîtriser la version exacte de la dépendance. Le paquet système est adapté au prototypage rapide ou aux environnements de développement local.


Méthode 5 : Header unique (single-file)

Pour les cas où vous voulez zéro infrastructure de dépendance, CLI11 fournit un header unique consolidé. Cette approche est adaptée aux petits utilitaires autonomes ou aux situations où le réseau n'est pas disponible au moment du build.

mkdir -p mon-outil/external  
cd mon-outil/external  
wget https://github.com/CLIUtils/CLI11/releases/download/v2.6.0/CLI11.hpp  

Structure résultante :

mon-outil/
├── CMakeLists.txt
├── external/
│   └── CLI11.hpp
└── src/
    └── main.cpp

CMakeLists.txt minimal :

cmake_minimum_required(VERSION 3.16)  
project(mon-outil VERSION 1.0.0 LANGUAGES CXX)  

set(CMAKE_CXX_STANDARD 23)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  

add_executable(mon-outil src/main.cpp)  
target_include_directories(mon-outil PRIVATE external)  

Dans ce cas, l'inclusion dans le code source utilise des guillemets plutôt que des chevrons :

#include "CLI11.hpp"      // Header unique local
// au lieu de :
// #include <CLI/CLI.hpp>  // Installation via package ou FetchContent

💡 Versionnement : si vous commitez le fichier CLI11.hpp dans votre dépôt, documentez la version dans un fichier external/VERSIONS.md ou un commentaire. Une dépendance dont on ne connaît pas la version est une dette technique silencieuse.


Récapitulatif des méthodes d'installation

Méthode Avantages Inconvénients Cas d'usage
FetchContent Version épinglée, intégré CMake, rien à installer Nécessite un accès réseau au premier build Nouveau projet CMake (recommandé)
Conan Gestion centralisée, profils multi-plateformes Configuration Conan initiale Projet avec plusieurs dépendances Conan
vcpkg Intégration Microsoft, large catalogue Toolchain file requis Projet multi-plateforme avec vcpkg
apt Installation en une commande Version potentiellement datée Prototypage rapide
Header unique Zéro dépendance, fonctionne hors-ligne Version figée manuellement Petit utilitaire, environnement contraint

Premier programme : un outil de ping HTTP

Mettons en pratique avec un programme réaliste — un outil qui vérifie la disponibilité d'un endpoint HTTP. Ce premier exemple illustre les fondamentaux de CLI11 : options typées, valeurs par défaut, validation et aide automatique.

src/main.cpp

#include <CLI/CLI.hpp>
#include <print>
#include <string>
#include <vector>
#include <chrono>

int main(int argc, char* argv[]) {
    // --- Création de l'application ---
    CLI::App app{"httpcheck — Vérification de disponibilité HTTP"};

    // Afficher la version avec --version
    app.set_version_flag("--version,-V", "httpcheck 1.0.0");

    // --- Déclaration des options ---
    std::string url;
    app.add_option("url", url, "URL à vérifier")
        ->required();

    int timeout_ms = 5000;
    app.add_option("--timeout,-t", timeout_ms, "Timeout en millisecondes")
        ->check(CLI::Range(100, 60'000))
        ->default_str("5000");

    int retries = 3;
    app.add_option("--retries,-r", retries, "Nombre de tentatives")
        ->check(CLI::Range(1, 20));

    int interval_ms = 1000;
    app.add_option("--interval,-i", interval_ms, "Intervalle entre tentatives (ms)")
        ->check(CLI::Range(100, 30'000));

    bool verbose = false;
    app.add_flag("--verbose,-v", verbose, "Afficher les détails de chaque tentative");

    bool quiet = false;
    app.add_flag("--quiet,-q", quiet, "Afficher uniquement le résultat final");

    // verbose et quiet sont mutuellement exclusifs
    app.get_option("--verbose")->excludes(app.get_option("--quiet"));
    app.get_option("--quiet")->excludes(app.get_option("--verbose"));

    std::string format = "text";
    app.add_option("--format,-f", format, "Format de sortie")
        ->check(CLI::IsMember({"text", "json"}));

    std::vector<std::string> headers;
    app.add_option("--header,-H", headers,
                   "Headers HTTP (répétable, format Clé:Valeur)");

    // --- Parsing ---
    CLI11_PARSE(app, argc, argv);

    // --- Logique métier ---
    // (Dans un vrai programme, ici se trouverait l'appel HTTP)
    if (verbose) {
        std::println("Configuration :");
        std::println("  URL       : {}", url);
        std::println("  Timeout   : {} ms", timeout_ms);
        std::println("  Tentatives: {}", retries);
        std::println("  Intervalle: {} ms", interval_ms);
        std::println("  Format    : {}", format);
        for (const auto& h : headers) {
            std::println("  Header    : {}", h);
        }
    }

    if (format == "json") {
        std::println(R"({{"url":"{}","status":"ok","attempts":1}})", url);
    } else {
        if (!quiet) {
            std::println("✓ {} — accessible (1/{} tentatives)", url, retries);
        }
    }

    return 0;
}

Tests en ligne de commande

Compilez et testez les différents scénarios :

# Build
cmake -B build -G Ninja && cmake --build build

# Usage basique
$ ./build/httpcheck https://example.com
✓ https://example.com — accessible (1/3 tentatives)

# Mode verbeux avec options personnalisées
$ ./build/httpcheck https://api.example.com -v --timeout 10000 -r 5 \
    -H "Authorization:Bearer tok123" -H "Accept:application/json"
Configuration :
  URL       : https://api.example.com
  Timeout   : 10000 ms
  Tentatives: 5
  Intervalle: 1000 ms
  Format    : text
  Header    : Authorization:Bearer tok123
  Header    : Accept:application/json
✓ https://api.example.com — accessible (1/5 tentatives)

# Sortie JSON (exploitable dans un pipeline)
$ ./build/httpcheck https://example.com --format json
{"url":"https://example.com","status":"ok","attempts":1}

# Sortie JSON + jq
$ ./build/httpcheck https://example.com -f json | jq '.status'
"ok"

# Version
$ ./build/httpcheck --version
httpcheck 1.0.0

# Validation : timeout hors limites
$ ./build/httpcheck https://example.com --timeout 0
Error: --timeout: Value 0 not in range [100, 60000]  
Run with --help for more information.  

# Validation : format inconnu
$ ./build/httpcheck https://example.com --format xml
Error: --format: Check CLI::IsMember FAILED

# Exclusion mutuelle : verbose + quiet
$ ./build/httpcheck https://example.com -v -q
Error: --verbose: excludes --quiet  
Run with --help for more information.  

# Aide auto-générée
$ ./build/httpcheck --help
httpcheck — Vérification de disponibilité HTTP  
Usage: httpcheck [OPTIONS] url  

Positionals:
  url TEXT REQUIRED             URL à vérifier

Options:
  -h,--help                     Print this help message and exit
  --version,-V                  Display program version information and exit
  --timeout,-t INT:INT in [100 - 60000] [5000]
                                Timeout en millisecondes
  --retries,-r INT:INT in [1 - 20]
                                Nombre de tentatives
  --interval,-i INT:INT in [100 - 30000]
                                Intervalle entre tentatives (ms)
  --verbose,-v                  Afficher les détails de chaque tentative
  --quiet,-q                    Afficher uniquement le résultat final
  --format,-f TEXT:{text,json}  Format de sortie
  --header,-H TEXT ...          Headers HTTP (répétable, format Clé:Valeur)

Analyse du code : concepts CLI11 utilisés

Reprenons les éléments de ce premier programme pour identifier les concepts CLI11 en jeu :

CLI::App — Le point d'entrée

CLI::App app{"httpcheck — Vérification de disponibilité HTTP"};

Chaque programme CLI11 commence par la création d'un objet CLI::App. La chaîne passée au constructeur est la description affichée dans l'aide. Cet objet est le conteneur central sur lequel toutes les options, flags et sous-commandes sont enregistrés.

add_option — Options typées

app.add_option("--timeout,-t", timeout_ms, "Timeout en millisecondes");

Le premier argument est la spécification de nommage : nom long, virgule, nom court. CLI11 gère les deux formes automatiquement. Le deuxième argument est une référence vers la variable de destination — c'est le binding direct. Le troisième est la description pour l'aide.

add_option retourne un pointeur CLI::Option* sur lequel on chaîne des modificateurs. C'est un pattern fluent :

app.add_option("--timeout,-t", timeout_ms, "Timeout")
    ->required()                    // L'option est obligatoire
    ->check(CLI::Range(100, 60000)) // Validation par intervalle
    ->default_str("5000");          // Texte affiché dans l'aide

add_flag — Flags booléens

app.add_flag("--verbose,-v", verbose, "Mode verbeux");

Un flag est une option sans valeur : sa seule présence sur la ligne de commande met la variable à true. CLI11 distingue add_option (attend une valeur) de add_flag (présence/absence).

Options répétables avec std::vector

std::vector<std::string> headers;  
app.add_option("--header,-H", headers, "Headers HTTP (répétable)");  

Quand la variable de destination est un std::vector, CLI11 autorise automatiquement l'option à apparaître plusieurs fois. Chaque occurrence ajoute une entrée au vecteur. C'est le pattern standard pour les options comme -H de curl.

CLI11_PARSE — La macro de parsing

CLI11_PARSE(app, argc, argv);

Cette macro encapsule l'appel à app.parse(argc, argv) dans un bloc try/catch. En cas d'erreur de parsing, elle affiche le message d'erreur et quitte avec le code de retour approprié. En cas de --help ou --version, elle affiche l'information demandée et quitte avec le code 0.

Pour un contrôle plus fin, vous pouvez remplacer la macro par un parsing explicite :

try {
    app.parse(argc, argv);
} catch (const CLI::ParseError& e) {
    return app.exit(e);
}

Cela permet par exemple de réaliser un nettoyage ou un logging avant la sortie en cas d'erreur. En pratique, CLI11_PARSE suffit dans la grande majorité des cas.

set_version_flag — Gestion de la version

app.set_version_flag("--version,-V", "httpcheck 1.0.0");

Cette méthode enregistre un flag qui, lorsqu'il est invoqué, affiche la chaîne de version et termine le programme. La convention veut que le nom court soit -V (majuscule) pour éviter le conflit avec -v (verbose), mais c'est configurable.

💡 Bonne pratique : dans un vrai projet, la version vient de CMake, pas d'une constante en dur. On utilise configure_file pour injecter PROJECT_VERSION dans un header :

# CMakeLists.txt  
configure_file(src/version.h.in src/version.h)  
target_include_directories(mon-outil PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)  
// src/version.h.in  
#pragma once  
#define APP_VERSION "@PROJECT_VERSION@"  
// src/main.cpp  
#include "version.h"  
app.set_version_flag("--version,-V", APP_VERSION);  

Ainsi, la version est définie à un seul endroit (project(... VERSION 1.0.0)) et propagée partout automatiquement.


Vérification de l'installation

Pour confirmer que CLI11 est correctement intégré à votre projet, un test simple consiste à compiler un programme minimal et à vérifier que l'aide est bien générée :

// test_cli11.cpp
#include <CLI/CLI.hpp>

int main(int argc, char* argv[]) {
    CLI::App app{"Test CLI11"};
    CLI11_PARSE(app, argc, argv);
    return 0;
}
$ g++ -std=c++23 -I/chemin/vers/cli11/include test_cli11.cpp -o test_cli11
$ ./test_cli11 --help
Test CLI11  
Usage: test_cli11 [OPTIONS]  

Options:
  -h,--help    Print this help message and exit

Si ce programme compile et affiche l'aide, votre installation est fonctionnelle. Vous êtes prêt à passer aux sections suivantes, où nous explorerons en profondeur les options, flags, sous-commandes (36.1.2), la validation (36.1.3) et la personnalisation de l'aide (36.1.4).

⏭️ Options, flags et sous-commandes