- ai-framework.md: Unified decision framework with scoring system, RL integration, doctrines - systeme-diplomatique.md: Relations (shared), intentions (bilateral), threat, reliability systems - calcul-menace.md: Contextual threat calculation with sword & shield mechanics - systeme-sauvegarde.md: V1 save system with JSON, metachunks, module autonomy - module-versioning.md: Automatic MAJOR.MINOR.PATCH.BUILD versioning via CMake 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
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 :
- Lire version du module dans la save :
cat saves/abc123/server_1/tank.json | grep module_version
# "module_version": "0.1.15.3847"
- Retrouver commit correspondant :
# PATCH = 15 commits
git log --oneline | head -n 15 | tail -n 1
- Vérifier hash sources :
# BUILD = 3847 correspond à un certain hash
# Comparer avec hashes de commits proches
- 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 savearchitecture-modulaire.md: Interface IModule, contraintes modulesclaude-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