Dans un Makefile, cela se ferait avec quelque chose comme:
g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
Ceci est très utile, car le binaire connaît la validation exacte SHA1, ainsi il peut le vider en cas de segfault.
Comment puis-je obtenir le même résultat avec CMake?
J'ai créé des modules CMake qui s'apparentent à un référentiel git pour la gestion des versions et des applications similaires. Ils sont tous dans mon référentiel à l'adresse suivante: https://github.com/rpavlik/cmake-modules
La bonne chose à propos de ces fonctions est qu’elles forceront une reconfiguration (une réexécution de cmake) avant une construction à chaque fois que la HEAD commit modifie. Contrairement à faire quelque chose une seule fois avec execute_process, vous n'avez pas besoin de vous rappeler de recommencer pour mettre à jour la définition de hachage.
Pour ce but spécifique, vous aurez besoin au moins des fichiers GetGitRevisionDescription.cmake
et GetGitRevisionDescription.cmake.in
. Ensuite, dans votre fichier CMakeLists.txt
principal, vous auriez quelque chose comme ceci
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/whereYouPutMyModules/")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
Ensuite, vous pouvez soit l'ajouter en tant que définition à l'échelle du système (ce qui provoquerait malheureusement beaucoup de reconstruction)
add_definitions("-DGIT_SHA1=${GIT_SHA1}")
ou, mon alternative suggérée: Créer un fichier source généré. Créez ces deux fichiers dans votre source:
GitSHA1.cpp.in:
#define GIT_SHA1 "@GIT_SHA1@"
const char g_GIT_SHA1[] = GIT_SHA1;
GitSHA1.h:
extern const char g_GIT_SHA1[];
Ajoutez ceci à votre CMakeLists.txt
(en supposant que vous ayez une liste de fichiers source dans SOURCES):
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/GitSHA1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/GitSHA1.cpp" @ONLY)
list(APPEND SOURCES "${CMAKE_CURRENT_BINARY_DIR}/GitSHA1.cpp" GitSHA1.h)
Ensuite, vous avez une variable globale contenant votre chaîne SHA - l'en-tête avec l'extern ne change pas lorsque le SHA le fait, vous pouvez donc simplement l'inclure à tout endroit que vous souhaitez référencer. à la chaîne, puis seul le CPP généré doit être recompilé à chaque commit pour vous donner accès au SHA partout.
Je l'ai fait de manière à générer:
const std::string Version::GIT_SHA1 = "e7fb69fb8ee93ac66f006406781138562d0250fb";
const std::string Version::GIT_DATE = "Thu Jan 9 14:17:56 2014";
const std::string Version::GIT_COMMIT_SUBJECT = "Fix all the bugs";
Si l'espace de travail qui a généré la construction comportait des modifications en attente et non validées, la chaîne SHA1 ci-dessus sera suffixée de -dirty
.
Dans CMakeLists.txt
:
# the commit's SHA1, and whether the building workspace was dirty or not
execute_process(COMMAND
"${GIT_EXECUTABLE}" describe --match=NeVeRmAtCh --always --abbrev=40 --dirty
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
# the date of the commit
execute_process(COMMAND
"${GIT_EXECUTABLE}" log -1 --format=%ad --date=local
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_DATE
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
# the subject of the commit
execute_process(COMMAND
"${GIT_EXECUTABLE}" log -1 --format=%s
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_COMMIT_SUBJECT
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
# generate version.cc
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.cc.in" "${CMAKE_CURRENT_BINARY_DIR}/version.cc" @ONLY)
list(APPEND SOURCES "${CMAKE_CURRENT_BINARY_DIR}/version.cc" version.hh)
Cela nécessite version.cc.in
:
#include "version.hh"
using namespace my_app;
const std::string Version::GIT_SHA1 = "@GIT_SHA1@";
const std::string Version::GIT_DATE = "@GIT_DATE@";
const std::string Version::GIT_COMMIT_SUBJECT = "@GIT_COMMIT_SUBJECT@";
Et version.hh
:
#pragma once
#include <string>
namespace my_app
{
struct Version
{
static const std::string GIT_SHA1;
static const std::string GIT_DATE;
static const std::string GIT_COMMIT_SUBJECT;
};
}
Alors dans le code je peux écrire:
cout << "Build SHA1: " << Version::GIT_SHA1 << endl;
J'utiliserais qc. comme ceci dans mon CMakeLists.txt:
exec_program(
"git"
${CMAKE_CURRENT_SOURCE_DIR}
ARGS "describe"
OUTPUT_VARIABLE VERSION )
string( REGEX MATCH "-g.*$" VERSION_SHA1 ${VERSION} )
string( REGEX REPLACE "[-g]" "" VERSION_SHA1 ${VERSION_SHA1} )
add_definitions( -DGIT_SHA1="${VERSION_SHA1}" )
Ce serait bien d'avoir une solution qui capte les modifications dans le référentiel (depuis git describe --dirty
), mais ne déclenche la recompilation que si quelque chose à propos des informations git a changé.
Certaines des solutions existantes:
.git/logs/HEAD
. Cela ne déclenche la recompilation que lorsque quelque chose dans le référentiel change, mais manque les modifications pour obtenir l'état '-dirty'.-dirty
, mais déclenche une recompilation tout le temps (en fonction de l'horodatage mis à jour du fichier d'informations sur la version).Une solution à la troisième solution consiste à utiliser la commande CMake 'copy_if_different', de sorte que l'horodatage sur le fichier d'informations sur la version ne change que si le contenu change.
Les étapes de la commande personnalisée sont les suivantes:
Le code (empruntant lourdement à la solution de Kralyk):
# The 'real' git information file
SET(GITREV_BARE_FILE git-rev.h)
# The temporary git information file
SET(GITREV_BARE_TMP git-rev-tmp.h)
SET(GITREV_FILE ${CMAKE_BINARY_DIR}/${GITREV_BARE_FILE})
SET(GITREV_TMP ${CMAKE_BINARY_DIR}/${GITREV_BARE_TMP})
ADD_CUSTOM_COMMAND(
OUTPUT ${GITREV_TMP}
COMMAND ${CMAKE_COMMAND} -E echo_append "#define GIT_BRANCH_RAW " > ${GITREV_TMP}
COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD >> ${GITREV_TMP}
COMMAND ${CMAKE_COMMAND} -E echo_append "#define GIT_HASH_RAW " >> ${GITREV_TMP}
COMMAND ${GIT_EXECUTABLE} describe --always --dirty --abbrev=40 --match="NoTagWithThisName" >> ${GITREV_TMP}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GITREV_TMP} ${GITREV_FILE}
COMMAND ${CMAKE_COMMAND} -E remove ${GITREV_TMP}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
VERBATIM
)
# Finally, the temporary file should be added as a dependency to the target
ADD_EXECUTABLE(test source.cpp ${GITREV_TMP})
La solution suivante est basée sur l'observation que Git met à jour le journal HEAD à chaque fois que vous pull
ou commit
quelque chose. Notez que par exemple La suggestion de Drew ci-dessus mettra à jour les informations Git uniquement si vous reconstruisez manuellement le cache CMake après chaque commit
.
J'utilise une "commande personnalisée" CMake qui génère un fichier d'en-tête d'une ligne ${SRCDIR}/gitrevision.hh
, où ${SRCDIR}
est la racine de votre arborescence source. Il sera refait seulement quand un nouveau commit sera fait. Voici la magie nécessaire CMake avec quelques commentaires:
# Generate gitrevision.hh if Git is available
# and the .git directory is present
# this is the case when the software is checked out from a Git repo
find_program(GIT_SCM git DOC "Git version control")
mark_as_advanced(GIT_SCM)
find_file(GITDIR NAMES .git PATHS ${CMAKE_SOURCE_DIR} NO_DEFAULT_PATH)
if (GIT_SCM AND GITDIR)
# Create gitrevision.hh
# that depends on the Git HEAD log
add_custom_command(OUTPUT ${SRCDIR}/gitrevision.hh
COMMAND ${CMAKE_COMMAND} -E echo_append "#define GITREVISION " > ${SRCDIR}/gitrevision.hh
COMMAND ${GIT_SCM} log -1 "--pretty=format:%h %ai" >> ${SRCDIR}/gitrevision.hh
DEPENDS ${GITDIR}/logs/HEAD
VERBATIM
)
else()
# No version control
# e.g. when the software is built from a source tarball
# and gitrevision.hh is packaged with it but no Git is available
message(STATUS "Will not remake ${SRCDIR}/gitrevision.hh")
endif()
Le contenu de gitrevision.hh
ressemblera à ceci:
#define GITREVISION cb93d53 2014-03-13 11:08:15 +0100
Si vous voulez changer cela, éditez la spécification --pretty=format:
en conséquence. Par exemple. utiliser %H
au lieu de %h
imprimera l'intégralité du résumé SHA1. Voir le manuel Git pour plus de détails.
Faire de gitrevision.hh
un fichier d’en-tête C++ à part entière avec include guards, etc. est laissé au lecteur comme un exercice :-)
Je ne peux pas vous aider du côté de CMake, mais en ce qui concerne Côté Git , je vous conseillerais de regarder comment le noyau Linux et le projet Git le font, via GIT-VERSION-GEN script , ou comment tig le fait dans son Makefile , en utilisantgit describe
si un dépôt git est présent, retombant sur "version
"/"VERSION
"/"GIT-VERSION-FILE
" généré et présent dans les archives, retomber enfin à la valeur par défaut codée en dur dans le script (ou le Makefile).
La première partie (à l'aide de git describe
) requiert que vous tagguiez les versions à l'aide de balises annotées (et éventuellement signées par GPG). Ou utilisez git describe --tags
pour utiliser également des balises légères.
Voici ma solution qui, à mon avis, est assez courte et efficace ;-)
Tout d'abord, un fichier est nécessaire dans l'arborescence des sources (je l'appelle git-rev.h.in
), elle devrait ressembler à ceci:
#define STR_EXPAND(x) #x
#define STR(x) STR_EXPAND(x)
#define GIT_REV STR(GIT_REV_)
#define GIT_REV_ \
(Ne faites jamais attention à ces macros, c’est un truc un peu fou pour créer une chaîne à partir d’une valeur brute.) Il est essentiel que ce fichier ait exactement une nouvelle ligne vide à la fin pour que cette valeur puisse être ajoutée.
Et maintenant, ce code va dans le fichier CMakeLists.txt
respectif:
# --- Git revision ---
add_dependencies(your_awesome_target gitrev) #put name of your target here
include_directories(${CMAKE_CURRENT_BINARY_DIR}) #so that the include file is found
set(gitrev_in git-rev.h.in) #just filenames, feel free to change them...
set(gitrev git-rev.h)
add_custom_target(gitrev
${CMAKE_COMMAND} -E remove -f ${CMAKE_CURRENT_BINARY_DIR}/${gitrev}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${gitrev_in} ${CMAKE_CURRENT_BINARY_DIR}/${gitrev}
COMMAND git rev-parse HEAD >> ${CMAKE_CURRENT_BINARY_DIR}/${gitrev}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} #very important, otherwise git repo might not be found in shadow build
VERBATIM #portability wanted
)
Cette commande indique que le git-rev.h.in
est copié dans l'arborescence de construction en tant que git-rev.h
et que la révision git est ajoutée à la fin.
Il ne vous reste donc plus qu'à inclure git-rev.h
dans l'un de vos fichiers et à faire ce que vous voulez avec la macro GIT_REV
, qui génère le hachage de révision git actuel en tant que valeur de chaîne.
La bonne chose à propos de cette solution est que le git-rev.h
est recréé chaque fois que vous construisez la cible associée, de sorte que vous n'avez pas à exécuter cmake
encore et encore.
Il devrait également être assez portable - aucun outil externe non portable n'a été utilisé et même la stupide stupide fenêtre cmd supporte les opérateurs >
et >>
;-)
Ajoutez simplement du code à seulement 2 fichiers: CMakeList.txt
et main.cpp
.
# git commit hash macro
execute_process(
COMMAND git log -1 --format=%h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
inline void LogGitCommitHash() {
#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized
#endif
std::cout << "GIT_COMMIT_HASH[" << GIT_COMMIT_HASH << "]"; // 4f34ee8
}
Dans CMakeList.txt
, CMake commandexecute_process()
est utilisé pour appeler la commande git log -1 --format=%h
qui vous donne l'abréviation courte et unique de vos valeurs SHA-1 dans une chaîne telle que 4f34ee8
. Cette chaîne est assignée à la variable CMake appelée GIT_COMMIT_HASH
. La commande CMake add_definitions()
définit la macro GIT_COMMIT_HASH
sur la valeur de 4f34ee8
juste avant la compilation gcc. La valeur de hachage est utilisée pour remplacer la macro dans le code C++ par le préprocesseur; elle existe donc dans le fichier objet main.o
et dans les fichiers binaires compilés a.out
.
Une autre méthode consiste à utiliser la commande CMake appelée configure_file()
, mais je n'aime pas l'utiliser parce que le fichier n'existe pas avant l'exécution de CMake.
Si CMake n’a pas la capacité intégrée d’effectuer cette substitution, vous pouvez écrire un script shell wrapper qui lit un fichier modèle, substitue le hachage SHA1 comme indiqué ci-dessus à l’emplacement correct (à l’aide de sed
, par exemple), crée le fichier de construction CMake réel, puis appelle CMake pour générer votre projet.
Une approche légèrement différente pourrait consister à rendre la substitution SHA1 optionnel . Vous créeriez le fichier CMake avec une valeur de hachage factice telle que "NO_OFFICIAL_SHA1_HASH"
. Lorsque les développeurs construisent leurs propres versions à partir de leurs répertoires de travail, le code généré n'inclut pas de valeur de hachage SHA1 (uniquement la valeur fictive) car le code du répertoire de travail n'a même pas encore de valeur de hachage SHA1 correspondante.
D'autre part, quand une compilation officielle est faite par votre serveur de build, à partir de sources extraites d'un référentiel central, vous connaissez la valeur de hachage SHA1 du code source. À ce stade, vous pouvez remplacer la valeur de hachage dans le fichier CMake, puis lancer CMake.
Pour un moyen rapide et sale, peut-être pas portable, de faire entrer git SHA-1 dans un projet C ou C++ en utilisant CMake, je l’utilise dans CMakeLists.txt:
add_custom_target(git_revision.h
git log -1 "--format=format:#define GIT_REVISION \"%H\"%n" HEAD > git_revision.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} VERBATIM)
Cela suppose que CMAKE_SOURCE_DIR
fait partie d'un référentiel git, et que git est disponible sur le système, et qu'une redirection de sortie sera correctement analysée par le shell.
Vous pouvez ensuite transformer cette cible en dépendance de toute autre cible à l'aide de
add_dependencies(your_program git_revision.h)
Chaque fois que your_program
est construit, le Makefile (ou un autre système de construction, si cela fonctionne sur d’autres systèmes de construction) va recréer git_revision.h dans le répertoire source, avec le contenu.
#define GIT_REVISION "<SHA-1 of the current git revision>"
Vous pouvez donc utiliser #include git_revision.h
à partir d’un fichier de code source et l’utiliser de cette manière. Notez que l’en-tête est créé littéralement every build, c’est-à-dire que même si tous les autres fichiers objet sont à jour, la commande sera toujours exécutée pour recréer git_revision.h. Je suppose que cela ne devrait pas être un gros problème car en général, vous ne reconstruisez pas la même révision git encore et encore, mais il faut en être conscient, et si est un problème pour vous, alors n'utilise pas ça. (Il est probablement possible de modifier une solution de contournement à l'aide de add_custom_command
mais je n'en ai pas eu besoin jusqu'à présent.)