Подтвердить что ты не робот

Как передать git SHA1 в компилятор как определение с помощью cmake?

В Makefile это будет сделано с чем-то вроде:

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

Это очень полезно, потому что двоичный код знает точное значение SHA1, чтобы он мог выгрузить его в случае segfault.

Как я могу достичь того же с помощью CMake?

4b9b3361

Ответ 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.

Некоторые из существующих решений:

  1. Используйте execute_process. Он получает информацию о git только во время настройки и может пропустить изменения в хранилище.
  2. Зависит от .git/logs/HEAD. Это только запускает перекомпиляцию, когда что-то в репо изменяется, но пропускает изменения, чтобы получить состояние -dirty.
  3. Используйте пользовательскую команду для перестройки информации о версии при каждом запуске сборки. Это отслеживает изменения, приводящие к состоянию -dirty, но все время вызывает перекомпиляцию (на основе обновленной метки времени файла информации о версии)

Одним из решений третьего решения является использование команды CMake copy_if_different, поэтому временная метка в файле информации о версии изменяется только при изменении содержимого.

Шаги в пользовательской команде:

  1. Собрать информацию о git во временный файл
  2. Используйте copy_if_different, чтобы скопировать временный файл в реальный файл
  3. Удалите временный файл, чтобы пользовательская команда снова запустилась на следующем 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, но я пока не нуждался в нем.)