¿Cómo puedo pasar git SHA1 al comstackdor como definición usando cmake?

En un Makefile esto se haría con algo como:

g++ -DGIT_SHA1="`git log -1 | head -n 1`" ... 

Esto es muy útil, porque el binary conoce la confirmación exacta SHA1, por lo que puede volcarla en caso de segfault.

¿Cómo puedo lograr lo mismo con CMake?

He creado algunos modules de CMake que analizan un repository git para versiones y propósitos similares. Todos están en mi repository en https://github.com/rpavlik/cmake-modules.

Lo bueno de estas funciones es que forzarán una reconfiguration (una repetición de cmake) antes de una compilation cada vez que la confirmación de HEAD cambie. A diferencia de hacer algo solo una vez con execute_process, no necesita recordar volver a crear para actualizar la definición de hash.

Para este propósito específico, necesitaría al less los files GetGitRevisionDescription.cmake y GetGitRevisionDescription.cmake.in . Luego, en tu file principal CMakeLists.txt , tendrías algo como esto

 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/whereYouPutMyModules/") include(GetGitRevisionDescription) get_git_head_revision(GIT_REFSPEC GIT_SHA1) 

Entonces, podría agregarlo como una definición de todo el sistema (que desafortunadamente causaría muchas reconstrucciones)

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

o, mi alternativa sugerida: crear un file fuente generado. Crea estos dos files en tu fuente:

GitSHA1.cpp.in:

 #define GIT_SHA1 "@GIT_SHA1@" const char g_GIT_SHA1[] = GIT_SHA1; 

GitSHA1.h:

 extern const char g_GIT_SHA1[]; 

Agregue esto a su CMakeLists.txt (suponiendo que tenga una list de files fuente en 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) 

Luego, tiene una variable global que contiene su cadena SHA: el encabezado con el extern no cambia cuando lo hace SHA, por lo que puede include ese lugar al que desee hacer reference a la cadena, y luego solo el CPP generado necesita ser recomstackdo en cada compromiso para darle acceso al SHA en todas partes.

Yo usaría algo así en mi 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}" ) 

Hice esto de tal manera que genere:

 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 el espacio de trabajo que realizó la compilation tiene cambios pendientes y no confirmados, la cadena SHA1 anterior tendrá el sufijo -dirty .

En 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) 

Esto requiere 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@"; 

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

Luego en el código puedo escribir:

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

La siguiente solución se basa en la observación de que Git actualiza el logging HEAD cuando pull o commit algo. Tenga en count que, por ejemplo, la sugerencia anterior de Drew actualizará la información de Git solo si reconstruye el caching de CMake manualmente después de cada commit .

Uso un "command personalizado" de CMake que genera un file de encabezado de una línea ${SRCDIR}/gitrevision.hh donde ${SRCDIR} es la raíz de su tree de origen. Se volverá a crear solo cuando se realice un nuevo compromiso. Aquí está la magia de CMake necesaria con algunos comentarios:

 # 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 # eg 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() 

El contenido de gitrevision.hh se verá así:

 #define GITREVISION cb93d53 2014-03-13 11:08:15 +0100 

Si desea cambiar esto, edite la --pretty=format: según corresponda. Por ejemplo, al usar %H vez de %h se imprimirá el resumen completo de SHA1. Ver el manual de Git para más detalles.

Hacer que gitrevision.hh un file de encabezado C ++ completo con guardias, etc. se deja como ejercicio para el lector 🙂

Sería bueno tener una solución que capte cambios en el repository (de git describe --dirty ), pero solo desencadena la recompilation si algo sobre la información de git ha cambiado.

Algunas de las soluciones existentes:

  1. Use 'execute_process'. Esto solo obtiene la información de git en el momento de la configuration, y puede perder los cambios en el repository.
  2. Depende de .git/logs/HEAD . Esto solo desencadena la recompilation cuando cambia algo en el repository, pero pierde los cambios para get el estado '-dirty'.
  3. Use un command personalizado para rebuild la información de la versión cada vez que se ejecuta una compilation. Esto capta los cambios que resultan en el estado " -dirty , pero desencadena una recompilation todo el time (según la timestamp actualizada del file de información de la versión)

Una solución para la tercera solución es usar el command CMake 'copy_if_different', por lo que la timestamp en el file de información de la versión solo cambia si el contenido cambia.

Los pasos en el command personalizado son:

  1. Recoge la información de git en un file temporal
  2. Use 'copy_if_different' para copyr el file temporal en el file real
  3. Elimine el file temporal para activar el command personalizado para que se ejecute nuevamente en el siguiente 'make'

El código (tomando mucho de la solución 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}) 

No puedo ayudarte con el lado de CMake, pero con respecto al lado de Git , recomendaría echar un vistazo cómo lo hace el kernel Linux y el proyecto Git, a través del script GIT-VERSION-GEN , o cómo lo hace tig en su Makefile , usando git describe si hay un repository git presente, volviendo a " version " / " VERSION " / " GIT-VERSION-FILE " generado y presente en tarballs, finalmente volviendo al valor pnetworkingeterminado hardcoded en script (o Makefile).

La primera parte (usando git describe ) requiere que etiquetes lanzamientos usando tags anotadas (y posiblemente firmadas por GPG). O use git describe --tags para usar también tags livianas.

Aquí está mi solución, que creo que es razonablemente corta pero eficaz 😉

Primero, se necesita un file en el tree fuente (lo llamo git-rev.h.in ), debería git-rev.h.in algo como esto:

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

(Por favor, no importa esas macros, es un truco un poco loco para hacer una cadena con un valor en bruto). Es esencial que este file tenga exactamente una nueva línea vacía al final para que pueda agregarse el valor.

Y ahora este código va en el respectivo file CMakeLists.txt :

 # --- 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 ) 

Este command asegura que git-rev.h.in se copy en el tree de compilation cuando se git-rev.h y la revisión de git en su extremo.

Entonces, todo lo que necesita hacer a continuación es include git-rev.h en uno de sus files y hacer lo que quiera con la macro GIT_REV , que produce el hash de revisión de git actual como valor de cadena.

Lo bueno de esta solución es que el git-rev.h se recrea cada vez que construyes el objective asociado, por lo que no tienes que ejecutar cmake una y otra vez.

También debería ser bastante portátil: no se usaron herramientas externas no portátiles e incluso las sangrientas windows cmd admiten los operadores > y >> 😉

Solución

Simplemente agregue código a solo 2 files: CMakeList.txt y 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 } 

Explicación

En CMakeList.txt , el command CMake execute_process() se usa para llamar al command git log -1 --format=%h que le da la abreviatura corta y única para sus valores SHA-1 en una cadena como 4f34ee8 . Esta cadena está asignada a la variable CMake llamada GIT_COMMIT_HASH . El command CMake add_definitions() define la macro GIT_COMMIT_HASH con el valor de 4f34ee8 justo antes de la compilation de gcc. El valor hash se usa para replace la macro en código C ++ por preprocesador, y por lo tanto existe en el file de object main.o y en los binarys comstackdos a.out .

Nota lateral

Otra forma de lograrlo es usar el command CMake llamado configure_file() , pero no me gusta usarlo porque el file no existe antes de ejecutar CMake.

Si CMake no tiene una capacidad incorporada para hacer esta sustitución, entonces podría escribir un script de shell contenedor que lea un file de plantilla, sustituya el hash SHA1 como se indicó anteriormente en la location correcta (usando sed , por ejemplo), crea el real CMake build file, y luego llama a CMake para build su proyecto.

Un enfoque ligeramente diferente podría ser hacer que la sustitución SHA1 sea opcional . "NO_OFFICIAL_SHA1_HASH" crear el file CMake con un valor hash ficticio como "NO_OFFICIAL_SHA1_HASH" . Cuando los desarrolladores construyen sus propias comstackciones desde sus directorys de trabajo, el código construido no includeía un valor de hash SHA1 (solo el valor ficticio) porque el código del directory de trabajo ni siquiera tiene un valor de hash SHA1 correspondiente.

Por otro lado, cuando tu server de compilation crea una compilation oficial a partir de fonts extraídas de un repository central, conoces el valor hash SHA1 para el código fuente. En ese punto, puede sustituir el valor hash en el file CMake y luego ejecutar CMake.

Para una forma rápida y sucia, posiblemente no portátil, de convertir el git SHA-1 en un proyecto C o C ++ utilizando CMake, lo uso en 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) 

Asume que CMAKE_SOURCE_DIR es parte de un repository de git, y que git está disponible en el sistema, y ​​que el shell va a analizar correctamente una networkingirección de salida.

Luego puede hacer que este objective sea una dependencia de cualquier otro objective usando

 add_dependencies(your_program git_revision.h) 

Cada vez que se your_program , el Makefile (u otro sistema de compilation, si esto funciona en otros sistemas de compilation) recreará git_revision.h en el directory de origen, con los contenidos

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

Por lo tanto, puede #include git_revision.h de algún file de código fuente y usarlo de esa manera. Tenga en count que el encabezado se crea literalmente en cada compilation, es decir, incluso si todos los demás files de object están actualizados, seguirá ejecutando este command para volver a crear git_revision.h. Me imagino que no debería ser un gran problema porque generalmente no reconstruyes la misma revisión de git una y otra vez, pero es algo que debes tener en count, y si es un problema para ti, entonces no lo uses. (Probablemente sea posible add_custom_command una solución mediante add_custom_command pero no la he necesitado hasta el momento).