Как я могу передать git SHA1 компилятору в качестве определения с помощью cmake? - PullRequest
44 голосов
/ 17 сентября 2009

В Makefile это можно сделать с помощью чего-то вроде:

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

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

Как мне добиться того же с CMake?

Ответы [ 10 ]

90 голосов
/ 01 декабря 2010

Я сделал несколько модулей 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 везде.

14 голосов
/ 09 января 2014

Я сделал это таким образом, чтобы сгенерировать:

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;
9 голосов
/ 01 июля 2010

Я бы использовал 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}" )
6 голосов
/ 19 января 2017

Было бы неплохо иметь решение, которое ловит изменения в хранилище (с 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'

Код (заимствуя из решения Кралика):

# 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 голосов
/ 14 марта 2014

Следующее решение основано на наблюдении, что 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 полноценного заголовочного файла C ++ с включенными охранниками и т. Д. Оставлено читателю в качестве упражнения: -)

3 голосов
/ 24 сентября 2011

Вот мое решение, которое я считаю достаточно коротким, но эффективным; -)

Во-первых, в исходном дереве необходим файл (я называю его 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 поддерживает операторы > и >>; -)

3 голосов
/ 17 сентября 2009

Я не могу помочь вам со стороной CMake, но в отношении сторона Git Я бы порекомендовал посмотреть, как это делает ядро ​​Linux и сам проект Git, через GIT-VERSION- Сценарий GEN , или как tig делает это в Makefile , используя git describe, если присутствует git-репозиторий, возвращаясь к "version" / "VERSION "/" GIT-VERSION-FILE "генерируется и присутствует в тарболах, в конце концов возвращаясь к значению по умолчанию, жестко заданному в скрипте (или Makefile).

Первая часть (с использованием git describe) требует, чтобы вы отмечали релизы с помощью аннотированных (и, возможно, подписанных GPG) тегов. Или используйте git describe --tags, чтобы использовать также легкие теги.

2 голосов
/ 28 сентября 2016

Решение

Просто добавьте некоторый код только в 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.

1 голос
/ 17 сентября 2009

Если в CMake нет встроенной возможности выполнить эту замену, вы можете написать сценарий оболочки-обертки, который читает файл шаблона и заменяет хэш SHA1, как указано выше, в правильном месте (используя пример), создает реальный файл сборки CMake, а затем вызывает CMake для сборки вашего проекта.

Несколько иной подход может сделать замену SHA1 необязательной . Вы должны создать файл CMake с фиктивным хеш-значением, таким как "NO_OFFICIAL_SHA1_HASH". Когда разработчики создают свои собственные сборки из своих рабочих каталогов, встроенный код не будет включать хеш-значение SHA1 (только фиктивное значение), поскольку код из рабочего каталога даже не имеет соответствующего хеш-значения SHA1.

С другой стороны, когда ваш сервер сборки делает официальную сборку из источников, извлеченных из центрального репозитория, вы знаете значение хеша SHA1 для исходного кода. В этот момент вы можете заменить значение хеша в файле CMake, а затем запустить CMake.

0 голосов
/ 18 января 2013

Для быстрого и грязного, возможно, не переносимого способа вставить 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, но он мне пока не нужен.)

...