Как передать git SHA1 в компилятор как определение с помощью cmake?
В Makefile это будет сделано с чем-то вроде:
g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
Это очень полезно, потому что двоичный код знает точное значение SHA1, чтобы он мог выгрузить его в случае segfault.
Как я могу достичь того же с помощью CMake?
Ответы
Ответ 1
Я создал несколько модулей CMake, которые подключаются к репозитории git для версий и аналогичных целей - все они находятся в моем репозитории на https://github.com/rpavlik/cmake-modules
Хорошая вещь об этих функциях заключается в том, что они будут принудительно перенастраивать (повторение cmake) перед сборкой каждый раз, когда изменяется фиксация HEAD. В отличие от выполнения всего одного раза с помощью exec_process, вам не нужно помнить, что нужно обновить хеш-определение.
Для этой конкретной цели вам понадобятся, по крайней мере, файлы GetGitRevisionDescription.cmake
и GetGitRevisionDescription.cmake.in
. Затем в главном файле CMakeLists.txt
у вас будет что-то вроде этого
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/whereYouPutMyModules/")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
Затем вы можете добавить его как общесистемное определение (которое, к сожалению, вызвало бы большую перестройку)
add_definitions("-DGIT_SHA1=${GIT_SHA1}")
или, моя предложенная альтернатива: Сделать сгенерированный исходный файл. Создайте эти два файла в своем источнике:
GitSHA1.cpp.in:
#define GIT_SHA1 "@[email protected]"
const char g_GIT_SHA1[] = GIT_SHA1;
GitSHA1.h:
extern const char g_GIT_SHA1[];
Добавьте это в свой CMakeLists.txt
(если у вас есть список исходных файлов в 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)
Затем у вас есть глобальная переменная, содержащая вашу строку SHA - заголовок с extern не изменяется, когда SHA делает, поэтому вы можете просто включить, что любое место, которое вы хотите сослаться на строку, а затем только сгенерированное CPP необходимо перекомпилировать для каждой фиксации, чтобы предоставить вам доступ к SHA повсюду.
Ответ 2
Я сделал это так, чтобы генерировать:
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";
Если рабочее пространство, в котором выполнялась сборка, находилось в ожидании, незафиксированные изменения, указанная выше строка SHA1 будет помечена -dirty
.
В CMakeLists.txt
:
# the commit 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)
Для этого требуется version.cc.in
:
#include "version.hh"
using namespace my_app;
const std::string Version::GIT_SHA1 = "@[email protected]";
const std::string Version::GIT_DATE = "@[email protected]";
const std::string Version::GIT_COMMIT_SUBJECT = "@[email protected]";
И 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;
};
}
Тогда в коде я могу написать:
cout << "Build SHA1: " << Version::GIT_SHA1 << endl;
Ответ 3
Я бы использовал sth. как это в моем 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}" )
Ответ 4
Было бы неплохо иметь решение, которое ловит изменения в хранилище (из git describe --dirty
), но запускает перекомпиляцию только в том случае, если что-то изменилось в информации о git.
Некоторые из существующих решений:
- Используйте
execute_process
. Он получает информацию о git только во время настройки и может пропустить изменения в хранилище.
- Зависит от
.git/logs/HEAD
. Это только запускает перекомпиляцию, когда что-то в репо изменяется, но пропускает изменения, чтобы получить состояние -dirty
.
- Используйте пользовательскую команду для перестройки информации о версии при каждом запуске сборки. Это отслеживает изменения, приводящие к состоянию
-dirty
, но все время вызывает перекомпиляцию (на основе обновленной метки времени файла информации о версии)
Одним из решений третьего решения является использование команды CMake copy_if_different
, поэтому временная метка в файле информации о версии изменяется только при изменении содержимого.
Шаги в пользовательской команде:
- Собрать информацию о git во временный файл
- Используйте
copy_if_different
, чтобы скопировать временный файл в реальный файл
- Удалите временный файл, чтобы пользовательская команда снова запустилась на следующем
make
Код (в значительной степени заимствуя из решения 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})
Ответ 5
Следующее решение основано на наблюдении, что Git обновляет журнал HEAD всякий раз, когда вы pull
или commit
что-то. Следует отметить, что, например, Приведенное выше предложение будет обновлять информацию Git только в том случае, если вы восстанавливаете CMake-кеш вручную после каждого commit
.
Я использую пользовательскую команду CMake, которая генерирует однострочный заголовочный файл ${SRCDIR}/gitrevision.hh
, где ${SRCDIR}
является корнем вашего исходного дерева. Он будет перезаписан только тогда, когда будет сделан новый коммит. Вот необходимая магия CMake с некоторыми комментариями:
# 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()
Содержимое gitrevision.hh
будет выглядеть так:
#define GITREVISION cb93d53 2014-03-13 11:08:15 +0100
Если вы хотите изменить это, отредактируйте соответствующую спецификацию --pretty=format:
. Например. используя %H
вместо %H
распечатает полный SHA1 дайджест. Подробнее см. В руководстве Git.
Создание gitrevision.hh
полноценного файла заголовка С++ с включением охранников и т.д. остается в качестве упражнения для читателя: -)
Ответ 6
Я не могу помочь вам со стороны CMake, но по отношению к Git стороне я бы рекомендовал взглянуть, как это делает сам ядро Linux и Git сам проект, через GIT-VERSION-GEN script, или как tig делает это в Makefile, используя git describe
, если присутствует репозиторий Git, отбрасывается на "version
" / "version
" / "GIT-VERSION-FILE
" сгенерировано и представлено в tarballs, наконец, возвращается к стандартным значениям, жестко закодированным в script (или Makefile).
Первая часть (с использованием git describe
) требует, чтобы вы отпускали теги, используя аннотированные (и, возможно, подписанные GPG) теги. Или используйте git describe --tags
, чтобы использовать также легкие теги.
Ответ 7
Здесь мое решение, которое, по моему мнению, достаточно короткое, но эффективное; -)
Сначала в исходном дереве необходим файл (я называю его git-rev.h.in
), он должен выглядеть примерно так:
#define STR_EXPAND(x) #x
#define STR(x) STR_EXPAND(x)
#define GIT_REV STR(GIT_REV_)
#define GIT_REV_ \
(Пожалуйста, не обращайте внимания на эти макросы, это немного безумный трюк, чтобы сделать строку из необработанного значения.)
Очень важно, чтобы этот файл имел ровно одну пустую новую строку в конце, чтобы можно было добавить значение.
И теперь этот код идет в соответствующем файле 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
)
Эта команда гарантирует, что git-rev.h.in
будет скопирована в дереве сборки в виде git-rev.h
и git. Версия добавлена в конец.
Итак, все, что вам нужно сделать дальше, включает git-rev.h
в один из ваших файлов и делает все, что вам нужно, с макросом GIT_REV
, который возвращает текущий хеш версии git как строковое значение.
Самое приятное в этом решении заключается в том, что git-rev.h
воссоздается каждый раз при создании связанной цели, поэтому вам не нужно запускать cmake
снова и снова.
Он также должен быть довольно портативным - не использовались не портативные внешние инструменты, и даже кровавые глупые окна cmd поддерживают операторы >
и >>
; -)
Ответ 8
Решение
Просто добавьте код только в 2 файла: CMakeList.txt
и 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
}
Описание
В CMakeList.txt
команда CMake execute_process()
используется для вызова команды git log -1 --format=%h
, которая дает короткую и уникальную аббревиатуру для ваших значений SHA-1 в строке, например 4f34ee8
. Эта строка назначается переменной CMake под названием GIT_COMMIT_HASH
. Команда CMake add_definitions()
определяет макрос GIT_COMMIT_HASH
до значения 4f34ee8
непосредственно перед компиляцией gcc. Хэш-значение используется для замены макроса в коде С++ препроцессором и, следовательно, существует в объектном файле main.o
и в скомпилированных двоичных файлах a.out
.
Боковое примечание
Другой способ добиться - использовать команду CMake под названием configure_file()
, но я не люблю ее использовать, потому что файл не существует до запуска CMake.
Ответ 9
Если CMake не имеет встроенной возможности для этой подстановки, вы можете написать оболочку оболочки script, которая читает файл шаблона, заменяет хэш SHA1, как указано выше, в правильном месте (используя sed
, например), создает реальный файл сборки CMake, а затем вызывает CMake для создания вашего проекта.
Несколько иной подход может заключаться в том, чтобы сделать замену SHA1 опциональной. Вы должны создать файл CMake с фиктивным значением хэша, например "NO_OFFICIAL_SHA1_HASH"
. Когда разработчики строят свои собственные сборки из своих рабочих каталогов, встроенный код не будет содержать хеш-значение SHA1 (только фиктивное значение), потому что код из рабочего каталога еще не имеет соответствующего хэш-значения SHA1.
С другой стороны, когда официальная сборка создается вашим сервером сборки, из источников, вытащенных из центрального репозитория, тогда вы знаете значение хеша SHA1 для исходного кода. В этот момент вы можете заменить хеш-значение в файле CMake, а затем запустить CMake.
Ответ 10
Для быстрого и грязного, возможно не переносимого способа получить git SHA-1 в проект C или С++ с помощью CMake, я использую это в 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)
Предполагается, что CMAKE_SOURCE_DIR
является частью репозитория git и что git доступен в системе и что перенаправление вывода будет правильно проанализировано оболочкой.
Затем вы можете сделать эту цель зависимой от любой другой цели, используя
add_dependencies(your_program git_revision.h)
Каждый раз, когда your_program
создается, Makefile (или другая система сборки, если это работает в других системах сборки), воссоздает git_revision.h в исходном каталоге с содержимым
#define GIT_REVISION "<SHA-1 of the current git revision>"
Итак, вы можете #include git_revision.h
из какого-то файла исходного кода и использовать его таким образом. Обратите внимание, что заголовок создается буквально в каждой сборке, то есть даже если каждый другой объектный файл обновлен, он все равно выполнит эту команду для воссоздания git_revision.h. Я полагаю, что это не должно быть огромной проблемой, потому что обычно вы не перестраиваете одну и ту же версию git снова и снова, но это то, о чем нужно знать, и если это проблема для вас, тогда не используйте это. (Возможно, возможно взломать обходной путь с помощью add_custom_command
, но я пока не нуждался в нем.)