Comment puis-je passer git SHA1 au compilateur en tant que définition en utilisant cmake?

StackOverflow https://stackoverflow.com/questions/1435953

  •  07-07-2019
  •  | 
  •  

Question

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 le commit exact SHA1 afin de pouvoir le vider en cas de segfault.

Comment puis-je obtenir le même résultat avec CMake?

Était-ce utile?

La solution

J'ai créé des modules CMake qui s'apparentent à un dépôt Git pour la gestion des versions et des applications similaires. Ils sont tous dans mon référentiel à l'adresse . 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 validation de HEAD est modifiée. Contrairement à faire quelque chose une fois avec execute_process, vous n'avez pas besoin de vous rappeler de recommencer pour mettre à jour la définition de hachage.

Pour cela, vous avez besoin au minimum des fichiers GetGitRevisionDescription.cmake et GetGitRevisionDescription.cmake.in . Ensuite, dans votre fichier principal CMakeLists.txt , vous obtiendrez 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 l'ajouter en tant que définition à l'échelle du système (ce qui provoquerait malheureusement de nombreuses reconstructions)

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'externe ne change pas lorsque le SHA le fait, vous pouvez donc simplement inclure ce lieu que vous souhaitez faire référence à la chaîne, puis uniquement le code généré. Le CPP doit être recompilé à chaque engagement pour vous donner accès au SHA partout.

Autres conseils

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)

Ceci 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;
  };
}

Puis, dans le code, je peux écrire:

cout << "Build SHA1: " << Version::GIT_SHA1 << endl;

Je voudrais utiliser qc. comme ceci dans mon CMakeLists.txt:

exec_program(
    "git"
    ${CMAKE_CURRENT_SOURCE_DIR}
    ARGS "describe"
    OUTPUT_VARIABLE VERSION )

string( REGEX MATCH "-g.*<*>quot; VERSION_SHA1 ${VERSION} )
string( REGEX REPLACE "[-g]" "" VERSION_SHA1 ${VERSION_SHA1} )

add_definitions( -DGIT_SHA1="${VERSION_SHA1}" )

Il serait intéressant de disposer d'une solution capable de récupérer les modifications apportées au référentiel (à partir de git decrire --dirty ), mais ne déclenche la recompilation que si quelque chose concernant les informations git a changé.

Certaines des solutions existantes:

  1. Utilisez 'execute_process'. Cela n'obtient que les informations git au moment de la configuration et peut manquer des modifications dans le référentiel.
  2. dépend de .git / logs / HEAD . Cela ne déclenche la recompilation que si un élément du référentiel est modifié, mais il manque les modifications pour obtenir l'état "-dirty".
  3. Utilisez une commande personnalisée pour reconstruire les informations de version chaque fois qu'une génération est exécutée. Cela intercepte les modifications entraînant l'état -dirty , mais déclenche une recompilation permanente (en fonction de l'horodatage mis à jour du fichier d'informations de version)

La troisième solution à la solution consiste à utiliser la commande CMake 'copy_if_different', de sorte que l'horodatage du fichier d'informations sur la version ne change que si le contenu change.

Les étapes de la commande personnalisée sont les suivantes:

  1. Collecter les informations git dans un fichier temporaire
  2. Utilisez 'copy_if_different' pour copier le fichier temporaire dans le fichier réel
  3. Supprimez le fichier temporaire pour que la commande personnalisée s'exécute à nouveau lors du prochain 'make'

Code (empruntant fortement à 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 repose sur l'observation que Git met à jour le journal HEAD chaque fois que vous extrayez ou validez quelque chose. Notez que par exemple La suggestion de Drew ci-dessus ne mettra à jour les informations Git que si vous reconstruisez manuellement le cache CMake après chaque commit .

J'utilise une commande CMake " personnalisée " qui génère un fichier d’en-tête d’une ligne $ {SRCDIR} /gitrevision.hh $ {SRCDIR} est la racine de votre arborescence source. Il ne sera refait que lorsqu’un nouveau commit sera effectué. 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. Si vous utilisez % H au lieu de % h , le résumé SHA1 complet sera imprimé. Voir le manuel Git pour plus de détails.

Faire de gitrevision.hh un fichier d’en-tête C ++ à part entière avec des gardes include, etc. est laissé au lecteur comme un exercice: -)

Je ne peux pas vous aider du côté de CMake, mais en ce qui concerne le côté Git , je vous conseillerais de regarder comment le noyau Linux et le projet Git le font, via GIT-VERSION-GEN , ou comment tig le fait dans son Makefile , en utilisant git décris si un référentiel git est présent, puis retourne à " version ". / " VERSION " / " GIT-VERSION-FILE " généré et présent dans les archives, pour finalement revenir à la valeur par défaut codée en dur dans le script (ou le fichier Makefile).

La première partie (en utilisant git describe ) nécessite que vous balisiez les éditions à l'aide de balises annotées (et éventuellement signées par GPG). Vous pouvez également utiliser git decrire --tags pour utiliser également les balises légères.

Voici ma solution qui, à mon avis, est raisonnablement courte et efficace ;-)

Tout d'abord, un fichier est nécessaire dans l'arborescence des sources (je l'appelle git-rev.h.in ), il devrait ressembler à ceci:

#define STR_EXPAND(x) #x
#define STR(x) STR_EXPAND(x)
#define GIT_REV STR(GIT_REV_)
#define GIT_REV_ \ 
 

(S'il vous plaît, ne vous occupez pas de ces macros, c'est un truc un peu fou pour faire une chaîne à partir d'une valeur brute.) Il est essentiel que ce fichier contienne exactement une nouvelle ligne vide à la fin pour que la valeur puisse être ajoutée.

Et maintenant, ce code est placé dans le fichier CMakeLists.txt correspondant:

# --- 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 garantit que git-rev.h.in est copié dans l'arbre de construction en tant que git-rev.h et que la révision de 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 git actuel. hachage de révision en tant que valeur de chaîne.

L'avantage de cette solution est que le git-rev.h est recréé chaque fois que vous construisez la cible associée. Vous n'avez donc pas besoin d'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 > > ; -)

Solution

Ajoutez simplement du code à seulement 2 fichiers: CMakeList.txt et main.cpp .

1. CMakeList.txt

# 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}\"")

2. main.cpp

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
}

Explication

Dans CMakeList.txt , la commande CMake execute_process () est utilisée 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 affecté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 .

Note latérale

Une autre solution 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 encapsuleur qui lit un fichier modèle, remplace le hachage SHA1 comme indiqué ci-dessus à l’emplacement correct (avec 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 facultative . 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 créée par votre serveur de compilation, à 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 exécuter CMake.

Pour un moyen rapide et sale, éventuellement non portable, de faire entrer git SHA-1 dans un projet C ou C ++ à l'aide de 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, 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 votre_programme est construit, le Makefile (ou un autre système de construction, si cela fonctionne sur d'autres systèmes de construction) recrée git_revision.h dans le répertoire source, avec le contenu

#define GIT_REVISION "<SHA-1 of the current git revision>"

Vous pouvez donc #include git_revision.h à partir d’un fichier de code source et l’utiliser de cette façon. Notez que l’en-tête est créé littéralement à chaque construction, c’est-à-dire que même si tous les autres fichiers objet sont à jour, il exécutera toujours cette commande pour recréer git_revision.h. Je pense 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, mais c’est une chose à connaître, et si cela est un problème pour vous, alors ne l'utilisez pas. (Il est probablement possible de pirater une solution de contournement en utilisant add_custom_command mais je n'en ai pas eu besoin jusqu'à présent.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top