Как я могу передать git SHA1 компилятору в качестве определения с помощью cmake?
Вопрос
В Makefile это было бы сделано с помощью чего-то вроде:
g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
Это очень полезно, потому что двоичный файл знает точную фиксацию SHA1, поэтому он может сбросить его в случае segfault.
Как я могу добиться того же с помощью CMake?
Решение
Я создал несколько модулей CMake, которые встраиваются в git-репозиторий для управления версиями и для подобных целей - все они находятся в моем хранилище по адресу https://github.com/rpavlik/cmake-modules р>
Хорошая вещь об этих функциях заключается в том, что они будут принудительно переконфигурировать (повторный запуск cmake) перед сборкой каждый раз, когда изменяется фиксация HEAD. В отличие от выполнения чего-либо только один раз с execute_process, вам не нужно повторно делать cmake для обновления определения хеша.
Для этой конкретной цели вам понадобятся как минимум файлы 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 "@GIT_SHA1@"
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 - заголовок с внешним значением не меняется, когда это делает SHA, так что вы можете просто включить это любое место, куда вы хотите сослаться на строку, а затем только сгенерированный CPP необходимо перекомпилировать при каждом коммите, чтобы предоставить вам доступ к SHA везде.
Другие советы
Я сделал это таким образом, чтобы сгенерировать:
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'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)
Для этого требуется 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@";
И 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;
Я бы использовал sth. как это в моем 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}" )
Было бы неплохо иметь решение, которое ловит изменения в хранилище (из git description --dirty
), но запускает перекомпиляцию только в том случае, если что-то изменилось в информации о git.
Некоторые из существующих решений:
<Ол> .git / logs / HEAD
. Это только запускает перекомпиляцию, когда что-то в репо изменяется, но пропускает изменения, чтобы получить состояние '-dirty'. -dirty
, но все время вызывает перекомпиляцию (на основе обновленной временной метки файла информации о версии) Одним из решений третьего решения является использование команды CMake 'copy_if_different', поэтому временная метка в файле информации о версии изменяется только при изменении содержимого.
Шаги в пользовательской команде:
<Ол>Код (в значительной степени заимствованный из решения Кралика):
# 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})
Следующее решение основано на наблюдении, что Git обновляет журнал HEAD всякий раз, когда вы вытягиваете
или делаете
что-то. Обратите внимание, что, например, Предложение Дрю, приведенное выше, будет обновлять информацию 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
полноценного заголовочного файла C ++ с включенными средствами защиты и т. д. оставлено читателю в качестве упражнения: -)
Я не могу помочь вам со стороны CMake, но что касается Сторона мерзавца Я бы порекомендовал взглянуть, как это делает ядро Linux и сам проект Git, через GIT-ВЕРСИЯ-GEN скрипт, или как tig делает это в своем Makefile - файл, используя git describe
если присутствует репозиторий git, возвращаемся к "version
" / "VERSION
" / "GIT-VERSION-FILE
" генерируется и присутствует в архивных файлах, в конечном итоге возвращаясь к значению по умолчанию, жестко закодированному в скрипте (или Makefile).
Первая часть (с использованием git describe
) требует, чтобы вы помечали релизы с помощью аннотированных (и, возможно, подписанных GPG) тегов.Или использовать git describe --tags
использовать также облегченные теги.
Вот мое решение, которое я считаю достаточно коротким, но эффективным; -)
Во-первых, в исходном дереве необходим файл (я называю его 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
снова и снова.
Он также должен быть довольно переносимым - никакие непереносимые внешние инструменты не использовались, и даже чертовски тупой windows cmd поддерживает операторы >
и > >
; -) р>
Решение
Просто добавьте некоторый код только в 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. Значение хеш-функции используется для замены макроса в коде C ++ препроцессором и, следовательно, существует в объектном файле main.o
и в скомпилированных двоичных файлах a.out
. р>
Дополнительное примечание
Другой способ добиться этого - использовать команду CMake с именем configure_file ()
, но я не люблю ее использовать, поскольку файл не существует до запуска CMake.
Если в CMake нет встроенной возможности выполнить эту замену, вы можете написать сценарий оболочки-оболочки, который читает файл шаблона, заменяя хэш SHA1, как указано выше, в правильном месте (используя sed
, например), создает настоящий файл сборки CMake, а затем вызывает CMake для сборки вашего проекта.
Несколько иной подход может сделать замену SHA1 необязательной . Вы должны создать файл CMake с фиктивным хеш-значением, таким как " NO_OFFICIAL_SHA1_HASH "
. Когда разработчики строят свои собственные сборки из своих рабочих каталогов, встроенный код не будет включать хеш-значение SHA1 (только фиктивное значение), поскольку код из рабочего каталога даже не имеет соответствующего хеш-значения SHA1.
С другой стороны, когда ваш сервер сборки делает официальную сборку из источников, извлеченных из центрального репозитория, вы знаете значение хеша SHA1 для исходного кода. В этот момент вы можете заменить значение хеша в файле CMake, а затем запустить CMake.
Для быстрого и грязного, возможно, не переносимого способа вставить git SHA-1 в проект C или 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
, но он мне пока не нужен.)