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

StackOverflow https://stackoverflow.com/questions/1435953

  •  07-07-2019
  •  | 
  •  

Вопрос

В 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.

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

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

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

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

    Следующее решение основано на наблюдении, что 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 , но он мне пока не нужен.)

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top