aissia/docs/implementation/configuration/module-versioning.md
StillHammer f231188880 Refactor documentation structure and add language learning
- Reorganize docs/ (flatten to architecture/ and implementation/)
- Remove MonitoringModule from MVP (no app detection)
- Add LanguageLearningModule to MVP
- Create CLAUDE.md (concise project overview)
- Add language learning to README and architecture
- Update all examples to use SchedulerModule instead of MonitoringModule

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 07:28:34 +08:00

18 KiB

Module Versioning System

Vue d'Ensemble

Le système de versioning des modules utilise un format MAJOR.MINOR.PATCH.BUILD automatiquement généré par CMake lors de la compilation. Ce système permet de :

  • Tracer les versions : Identifier précisément quelle version du code a créé une save
  • Détecter incompatibilités : Alerter si une save utilise une version différente
  • Debugging : Reproduire bugs avec la version exacte du module
  • Développement : Versions incrémentales automatiques sans maintenance manuelle

Format de Version

Structure : MAJOR.MINOR.PATCH.BUILD

0.1.15.3847
│ │ │  │
│ │ │  └─── BUILD    : Auto-increment à chaque build (hash sources)
│ │ └────── PATCH    : Auto-increment à chaque commit
│ └──────── MINOR    : Manuel (fonctionnalités nouvelles)
└────────── MAJOR    : Manuel (breaking changes)

Composantes

MAJOR (Manuel)

  • Incrémenté : Breaking changes, incompatibilités majeures
  • Exemples : Refonte complète architecture, changement format save incompatible
  • Gestion : Modifié manuellement dans fichier VERSION
  • Valeur initiale : 0 (pré-release), 1 (première version stable)

MINOR (Manuel)

  • Incrémenté : Nouvelles fonctionnalités, ajouts non-breaking
  • Exemples : Nouveau type de tank, nouveau système économique
  • Gestion : Modifié manuellement dans fichier VERSION
  • Valeur initiale : 1

PATCH (Automatique)

  • Incrémenté : À chaque commit Git
  • Calcul : Nombre de commits depuis le début du projet
  • Exemples : Bugfixes, optimisations, refactoring mineur
  • Gestion : Calculé automatiquement par CMake via git rev-list --count HEAD

BUILD (Automatique)

  • Incrémenté : À chaque rebuild (si sources modifiées)
  • Calcul : Hash MD5 des fichiers sources (.cpp, .h)
  • But : Distinguer builds de dev avec code non-commité
  • Gestion : Calculé automatiquement par CMake

Exemples de Versions

0.1.0.0      → Première version dev (MAJOR.MINOR manuel, PATCH/BUILD = 0)
0.1.5.1234   → Après 5 commits, build 1234
0.1.5.1235   → Même commit, sources modifiées → nouveau build
0.1.6.1235   → Nouveau commit, sources identiques → même build (théorique)
0.2.10.3847  → MINOR bump (nouvelle feature) + 10 commits
1.0.0.0      → Release stable, reset PATCH/BUILD

Implémentation CMake

Fichier VERSION

Chaque module contient un fichier VERSION à la racine :

modules/tank/VERSION

Contenu du fichier :

0.1

Ce fichier contient uniquement MAJOR.MINOR. Les composantes PATCH.BUILD sont calculées automatiquement.

CMakeLists.txt du Module

cmake_minimum_required(VERSION 3.20)
project(TankModule)

# ============================================================================
# MODULE VERSIONING
# ============================================================================

# 1. Lire MAJOR.MINOR depuis fichier VERSION
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" BASE_VERSION)
    string(STRIP "${BASE_VERSION}" BASE_VERSION)
else()
    set(BASE_VERSION "0.1")
    message(WARNING "No VERSION file found, using default: ${BASE_VERSION}")
endif()

# 2. Calculer PATCH : nombre de commits Git
execute_process(
    COMMAND git rev-list --count HEAD
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    OUTPUT_VARIABLE PATCH_COUNT
    OUTPUT_STRIP_TRAILING_WHITESPACE
    ERROR_QUIET
    RESULT_VARIABLE GIT_RESULT
)

if(NOT GIT_RESULT EQUAL 0)
    set(PATCH_COUNT "0")
    message(WARNING "Git not available, PATCH set to 0")
endif()

# 3. Calculer BUILD : hash des fichiers sources
file(GLOB_RECURSE MODULE_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h"
)

# Concaténer contenu de tous les fichiers
set(SOURCES_CONTENT "")
foreach(SOURCE_FILE ${MODULE_SOURCES})
    file(READ ${SOURCE_FILE} FILE_CONTENT)
    string(APPEND SOURCES_CONTENT ${FILE_CONTENT})
endforeach()

# Hash MD5 du contenu total
string(MD5 SOURCES_HASH "${SOURCES_CONTENT}")

# Convertir hash hex en nombre décimal (4 premiers caractères)
string(SUBSTRING ${SOURCES_HASH} 0 4 BUILD_HEX)
# Calculer valeur numérique (modulo 10000 pour limiter à 4 chiffres)
math(EXPR BUILD_NUM "0x${BUILD_HEX} % 10000")

# 4. Assembler version complète
set(MODULE_VERSION "${BASE_VERSION}.${PATCH_COUNT}.${BUILD_NUM}")

message(STATUS "Module version: ${MODULE_VERSION}")
message(STATUS "  BASE: ${BASE_VERSION} (from VERSION file)")
message(STATUS "  PATCH: ${PATCH_COUNT} (git commits)")
message(STATUS "  BUILD: ${BUILD_NUM} (sources hash)")

# 5. Définir macro C++ pour accès au runtime
add_definitions(-DMODULE_VERSION="${MODULE_VERSION}")

# ============================================================================
# MODULE BUILD
# ============================================================================

# Inclure sources
file(GLOB_RECURSE TANK_SOURCES src/*.cpp)

# Créer bibliothèque partagée (.so)
add_library(tank-module SHARED ${TANK_SOURCES})

# Configuration compilation
target_include_directories(tank-module PRIVATE include)
target_compile_features(tank-module PRIVATE cxx_std_20)

# Sortie
set_target_properties(tank-module PROPERTIES
    OUTPUT_NAME "tank"
    PREFIX ""
    SUFFIX ".so"
)

message(STATUS "Building TankModule v${MODULE_VERSION}")

Utilisation dans le Code C++

// src/TankModule.h
#ifndef MODULE_VERSION
#define MODULE_VERSION "unknown"
#endif

class TankModule : public IModule, public IModuleSave {
public:
    std::string getVersion() const override {
        return MODULE_VERSION;  // Défini par CMake
    }

    nlohmann::json serialize() override {
        json data;
        data["module_name"] = "tank";
        data["module_version"] = getVersion();  // "0.1.15.3847"
        // ... reste de la sérialisation
        return data;
    }
};

Affichage au Runtime

void TankModule::initialize() {
    LOG_INFO("TankModule v{} initializing...", getVersion());
}

Sortie console :

[INFO] TankModule v0.1.15.3847 initializing...

Workflow de Développement

1. Création Nouveau Module

# Créer structure module
mkdir -p modules/new_module/src
cd modules/new_module

# Créer fichier VERSION
echo "0.1" > VERSION

# Créer CMakeLists.txt avec système versioning
# (copier template ci-dessus)

Première compilation :

cmake . && make

Sortie :

-- Module version: 0.1.0.0
--   BASE: 0.1 (from VERSION file)
--   PATCH: 0 (git commits)
--   BUILD: 0 (sources hash)
-- Building NewModule v0.1.0.0

2. Développement Itératif

Scénario 1 : Modifier code sans commit

# Éditer src/NewModule.cpp
nano src/NewModule.cpp

# Rebuild
make

Sortie :

-- Module version: 0.1.0.4582
--   PATCH: 0 (aucun commit)
--   BUILD: 4582 (hash sources a changé)

Scénario 2 : Commit modifications

git add .
git commit -m "Add feature X"

# Rebuild
make

Sortie :

-- Module version: 0.1.1.4582
--   PATCH: 1 (nouveau commit)
--   BUILD: 4582 (sources identiques au commit)

Scénario 3 : Rebuild sans modification

# Rebuild immédiat
make

Sortie :

-- Module version: 0.1.1.4582
   (version identique, aucun changement)

3. Release Mineure (Nouvelle Feature)

# Bump MINOR version
echo "0.2" > VERSION

# Commit
git add VERSION
git commit -m "Bump version to 0.2 (new feature: advanced tactics)"

# Rebuild
cmake . && make

Sortie :

-- Module version: 0.2.2.5123
--   BASE: 0.2 (nouvelle version)
--   PATCH: 2 (commits depuis début projet)
--   BUILD: 5123 (hash actuel)

4. Release Majeure (Breaking Change)

# Bump MAJOR version
echo "1.0" > VERSION

git add VERSION
git commit -m "Release 1.0.0 - Stable API"

cmake . && make

Sortie :

-- Module version: 1.0.3.5123
--   BASE: 1.0 (version stable)
--   PATCH: 3 (total commits)
--   BUILD: 5123

Comparaison de Versions

Dans le Système de Save

Lors du chargement d'une save, comparaison des versions :

bool SaveSystem::loadModule(const std::string& moduleName) {
    auto module = moduleSystem->getModule(moduleName);

    json moduleData = readJsonFile(modulePath);
    std::string savedVersion = moduleData["module_version"];
    std::string currentVersion = module->getVersion();

    if (savedVersion != currentVersion) {
        LOG_WARN("Module {} version mismatch:", moduleName);
        LOG_WARN("  Save version:    {}", savedVersion);
        LOG_WARN("  Current version: {}", currentVersion);

        // Analyser différences
        VersionInfo saved = parseVersion(savedVersion);
        VersionInfo current = parseVersion(currentVersion);

        if (saved.major != current.major) {
            LOG_ERROR("  MAJOR version mismatch - likely incompatible!");
            return false;  // Incompatibilité critique
        }
        else if (saved.minor != current.minor) {
            LOG_WARN("  MINOR version mismatch - some features may differ");
            // Continuer avec warning
        }
        else if (saved.patch != current.patch) {
            LOG_INFO("  PATCH version mismatch - minor changes only");
            // Sûr, juste bugfixes
        }
        else {
            LOG_DEBUG("  BUILD version mismatch - dev builds differ");
            // Totalement sûr
        }
    }

    return module->deserialize(moduleData);
}

Structure VersionInfo

struct VersionInfo {
    int major;
    int minor;
    int patch;
    int build;

    static VersionInfo parse(const std::string& versionStr) {
        VersionInfo info;
        std::sscanf(versionStr.c_str(), "%d.%d.%d.%d",
                    &info.major, &info.minor, &info.patch, &info.build);
        return info;
    }

    bool isCompatibleWith(const VersionInfo& other) const {
        // Compatible si MAJOR identique
        return major == other.major;
    }

    bool hasBreakingChanges(const VersionInfo& other) const {
        return major != other.major;
    }
};

Gestion Multi-Modules

Script CMake Global

Pour centraliser la logique de versioning, créer un fichier CMake réutilisable :

# cmake/ModuleVersioning.cmake

function(setup_module_versioning)
    # Lire VERSION
    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" BASE_VERSION)
        string(STRIP "${BASE_VERSION}" BASE_VERSION)
    else()
        set(BASE_VERSION "0.1")
    endif()

    # Calculer PATCH
    execute_process(
        COMMAND git rev-list --count HEAD
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE PATCH_COUNT
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
        RESULT_VARIABLE GIT_RESULT
    )
    if(NOT GIT_RESULT EQUAL 0)
        set(PATCH_COUNT "0")
    endif()

    # Calculer BUILD
    file(GLOB_RECURSE MODULE_SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
        "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h"
    )
    set(SOURCES_CONTENT "")
    foreach(SOURCE_FILE ${MODULE_SOURCES})
        file(READ ${SOURCE_FILE} FILE_CONTENT)
        string(APPEND SOURCES_CONTENT ${FILE_CONTENT})
    endforeach()
    string(MD5 SOURCES_HASH "${SOURCES_CONTENT}")
    string(SUBSTRING ${SOURCES_HASH} 0 4 BUILD_HEX)
    math(EXPR BUILD_NUM "0x${BUILD_HEX} % 10000")

    # Assembler version
    set(MODULE_VERSION "${BASE_VERSION}.${PATCH_COUNT}.${BUILD_NUM}" PARENT_SCOPE)

    # Log
    message(STATUS "Module version: ${BASE_VERSION}.${PATCH_COUNT}.${BUILD_NUM}")
endfunction()

Utilisation dans Module

# modules/tank/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(TankModule)

# Importer fonction versioning
include(${CMAKE_SOURCE_DIR}/cmake/ModuleVersioning.cmake)

# Setup version automatique
setup_module_versioning()

# Définir macro
add_definitions(-DMODULE_VERSION="${MODULE_VERSION}")

# ... reste du CMakeLists

Best Practices

1. Quand Bumper MAJOR ?

Situations justifiant MAJOR bump :

  • Changement format save incompatible
  • Refonte API du module (signatures fonctions changent)
  • Suppression de fonctionnalités existantes
  • Changement architecture fondamentale

Exemple :

# Avant : TankModule v0.8.x utilise grille 10x10
# Après : TankModule v1.0.0 utilise grille 16x16 (incompatible)
echo "1.0" > VERSION

2. Quand Bumper MINOR ?

Situations justifiant MINOR bump :

  • Ajout nouveau type de tank/composant
  • Nouvelle fonctionnalité (mode formation, tactiques AI)
  • Amélioration significative sans breaking change

Exemple :

# Ajout système de camouflage → 0.1 → 0.2
echo "0.2" > VERSION

3. PATCH et BUILD sont Automatiques

Ne jamais modifier manuellement :

  • PATCH suit automatiquement les commits
  • BUILD suit automatiquement les modifications code

4. Versioning pour Tests

En environnement de test, fixer une version stable :

if(DEFINED ENV{CI_BUILD})
    # Build CI : version fixe pour reproductibilité
    set(MODULE_VERSION "0.0.0.0")
else()
    # Build normal : versioning automatique
    setup_module_versioning()
endif()

5. Affichage Version au Démarrage

void logModuleVersions() {
    LOG_INFO("=== Module Versions ===");
    for (auto& module : moduleSystem->getAllModules()) {
        LOG_INFO("  {}: v{}", module->getName(), module->getVersion());
    }
    LOG_INFO("=======================");
}

Sortie :

[INFO] === Module Versions ===
[INFO]   tank: v0.1.15.3847
[INFO]   economy: v0.2.8.1203
[INFO]   factory: v0.1.20.4512
[INFO]   transport: v0.1.5.982
[INFO] =======================

Debugging avec Versions

Reproduire un Bug

Si un bug est reporté avec une save :

  1. Lire version du module dans la save :
cat saves/abc123/server_1/tank.json | grep module_version
# "module_version": "0.1.15.3847"
  1. Retrouver commit correspondant :
# PATCH = 15 commits
git log --oneline | head -n 15 | tail -n 1
  1. Vérifier hash sources :
# BUILD = 3847 correspond à un certain hash
# Comparer avec hashes de commits proches
  1. Checkout version exacte pour debugging :
git checkout <commit_hash>
cmake . && make
# Version devrait matcher ou être proche

Logs de Version

void TankModule::initialize() {
    LOG_INFO("TankModule v{} initializing", getVersion());
    LOG_DEBUG("  Compiled: {} {}", __DATE__, __TIME__);
    LOG_DEBUG("  Compiler: {}", __VERSION__);
}

Alternatives et Limitations

Alternative 1 : Semantic Versioning Manuel

Avantage : Contrôle total sur versions Inconvénient : Maintenance manuelle, oublis fréquents

Alternative 2 : Git Tags Only

execute_process(
    COMMAND git describe --tags --always
    OUTPUT_VARIABLE MODULE_VERSION
)

Avantage : Simple, suit git Inconvénient : Nécessite tags, pas de granularité build

Limitation Actuelle : Hash Non-Déterministe

Le hash des sources peut varier selon :

  • Ordre de lecture des fichiers (filesystem dépendant)
  • Encodage des fichiers
  • Whitespace differences

Solution future : Hash déterministe avec tri des fichiers et normalisation.

Migration depuis Système Existant

Si des modules existants utilisent un autre système :

# Détecter ancien système
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/version.txt")
    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" OLD_VERSION)
    message(WARNING "Found old version.txt, migrating to VERSION file")

    # Extraire MAJOR.MINOR
    string(REGEX MATCH "^[0-9]+\\.[0-9]+" BASE_VERSION ${OLD_VERSION})
    file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" "${BASE_VERSION}")

    message(STATUS "Migration complete: ${OLD_VERSION} -> ${BASE_VERSION}.x.x")
endif()

Références Croisées

  • systeme-sauvegarde.md : Utilisation des versions dans le système de save
  • architecture-modulaire.md : Interface IModule, contraintes modules
  • claude-code-integration.md : Optimisation builds pour développement AI

Exemples Complets

Module Complet avec Versioning

Voir structure complète dans :

modules/tank/
├── VERSION              # "0.1"
├── CMakeLists.txt       # Setup versioning + build
├── src/
│   ├── TankModule.cpp   # Utilise MODULE_VERSION
│   └── Tank.cpp
└── include/
    └── TankModule.h     # Déclare getVersion()

Test Versioning

#ifdef TESTING
#include <cassert>

void testVersioning() {
    TankModule module;

    std::string version = module.getVersion();
    assert(!version.empty());
    assert(version != "unknown");

    // Parse version
    VersionInfo info = VersionInfo::parse(version);
    assert(info.major >= 0);
    assert(info.minor >= 0);
    assert(info.patch >= 0);
    assert(info.build >= 0);

    std::cout << "Versioning test passed: " << version << std::endl;
}
#endif