web-dev-qa-db-ja.com

cmakeを使用して、定義としてgit SHA1をコンパイラに渡すにはどうすればよいですか?

Makefileでは、これは次のようなもので行われます。

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

バイナリは正確なコミットSHA1を知っているため、segfaultの場合にそれをダンプできるため、これは非常に便利です。

CMakeで同じことをどのように達成できますか?

48
Łukasz Lew

バージョニングや同様の目的でgitリポジトリにピアリングするいくつかのCMakeモジュールを作成しました-それらはすべて https://github.com/rpavlik/cmake-modules の私のリポジトリにあります

これらの関数の良い点は、HEAD commitが変更をコミットするたびにビルドの前に再構成(cmakeの再実行)を強制することです。execute_processで1回だけ行うのとは異なり、ハッシュ定義を更新するために再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}")

または、私の代替案:生成されたソースファイルを作成します。ソースに次の2つのファイルを作成します。

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が変化してもexternのヘッダーは変更されないため、これを含めることができます文字列を参照したい場所であれば、コミットごとに生成されたCPPのみを再コンパイルして、SHAのどこにでもアクセスできるようにする必要があります。

92
Ryan Pavlik

私はこれを生成するような方法でこれを行いました:

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;
15
Drew Noakes

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}" )
9
the Ritz

git describe --dirtyからの)リポジトリへの変更をキャッチするソリューションがあればいいのですが、git情報に関する何かが変更された場合にのみ再コンパイルをトリガーします。

既存のソリューションの一部:

  1. 「execute_process」を使用します。これは、構成時にgit情報のみを取得し、リポジトリへの変更を見逃す可能性があります。
  2. .git/logs/HEADに依存します。これは、リポジトリ内の何かが変更された場合にのみ再コンパイルをトリガーしますが、「-ダーティ」状態を取得するための変更を見逃します。
  3. カスタムコマンドを使用して、ビルドが実行されるたびにバージョン情報を再構築します。これにより、-dirty状態になる変更をキャッチしますが、常に再コンパイルをトリガーします(バージョン情報ファイルの更新されたタイムスタンプに基づく)。

3番目の解決策の1つの修正は、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})
6
Mark Dewing

次の解決策は、Gitがpullまたはcommitの何かを記録するたびにHEADログを更新するという観察に基づいています。たとえば、上記のDrewの提案はGitを更新することに注意してください。 commitごとにCMakeキャッシュを手動で再構築する場合のみの情報。

1行のヘッダーファイル${SRCDIR}/gitrevision.hhを生成するCMakeの「カスタムコマンド」を使用します。ここで、${SRCDIR}はソースツリーのルートです。新しいコミットが行われると、再作成されますonly。ここにいくつかのコメント付きの必要な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++ヘッダーファイルにすることは、読者への課題として残されています:-)

5
Laryx Decidua

私はCMake側であなたを助けることはできませんが、Git側に関しては、LinuxカーネルとGitプロジェクト自体がどのようにそれを行うかを見てみることをお勧めします、 GIT-VERSION-GEN スクリプト、またはtigが Makefile でどのように実行するかgit describegitリポジトリが存在する場合、 "version"/"VERSION"/"GIT-VERSION-FILE"にフォールバックして生成され、tarballに存在し、最終的にフォールスクリプト(またはMakefile)にハードコードされたデフォルト値に戻します。

最初の部分(git describeを使用)では、注釈付き(およびおそらくGPG署名付き)タグを使用してリリースにタグを付ける必要があります。または、git describe --tagsを使用して軽量タグも使用します。

3
Jakub Narębski

これが私の解決策ですが、私はそれがかなり短くても効果的だと思います;-)

まず、ソースツリーにファイルが必要です(私はgit-rev.h.inという名前です)、次のようになります。

#define STR_EXPAND(x) #x
#define STR(x) STR_EXPAND(x)
#define GIT_REV STR(GIT_REV_)
#define GIT_REV_ \ 
 

(これらのマクロを気にしないでください。これは、生の値から文字列を作成するための少しめちゃくちゃなトリックです。)このファイルの最後にexactly空の改行を1つ入れて、値を添付。

そして今、このコードはそれぞれの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.ingit-rev.hとしてビルドツリーにコピーされ、最後にgitリビジョンが追加されます。

したがって、次に行う必要があるのは、ファイルの1つにgit-rev.hを含め、GIT_REVマクロを使用して、現在のgitリビジョンハッシュを文字列値として生成することです。

このソリューションの良い点は、関連するターゲットをビルドするたびにgit-rev.hが再作成されるため、cmakeを何度も実行する必要がないことです。

また、移植性が高い必要があります-ポータブルでない外部ツールは使用されておらず、血まみれの愚かなウィンドウcmdでも>および>>演算子をサポートしています;-)

3
kralyk

解決

単に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 commandexecute_process()を使用してコマンド_git log -1 --format=%h_を呼び出し、_4f34ee8_のような文字列のSHA-1値の一意の省略形を提供します。この文字列は、_GIT_COMMIT_HASH_というCMake変数に割り当てられます。 CMakeコマンドadd_definitions()は、gccコンパイルの直前にマクロ_GIT_COMMIT_HASH_を_4f34ee8_の値に定義します。ハッシュ値はC++コードのマクロをプリプロセッサで置き換えるために使用されるため、オブジェクトファイル_main.o_およびコンパイル済みバイナリ_a.out_に存在します。

サイドノート

もう1つの方法は、configure_file()と呼ばれるCMakeコマンドを使用することですが、CMakeを実行する前にファイルが存在しないため、使用したくありません。

2
etoricky

CMakeにこの置換を行う組み込み機能がない場合は、テンプレートファイルを読み取り、上記のSHA1ハッシュを正しい場所に置換するラッパーシェルスクリプトを記述できます(sedを使用して例)、実際のCMakeビルドファイルを作成し、CMakeを呼び出してプロジェクトをビルドします。

少し異なるアプローチは、SHA1置換オプションを作成することです。 CMakeファイルは、"NO_OFFICIAL_SHA1_HASH"などのダミーハッシュ値で作成します。開発者が作業ディレクトリから独自のビルドをビルドする場合、作業ディレクトリのコードには対応するSHA1ハッシュ値がまだないため、ビルドされたコードにはSHA1ハッシュ値(ダミー値のみ)が含まれません。

一方、ビルドサーバーによって公式リポジトリが中央リポジトリからプルされたソースから作成された場合、ソースコードのSHA1ハッシュ値がわかります。その時点で、CMakeファイルのハッシュ値を置き換えてから、CMakeを実行できます。

1
Greg Hewgill

CMakeを使用してgit SHA-1をCまたはC++プロジェクトにすばやく簡単に移植できない方法として、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を使用して、その方法で使用できます。ヘッダーは文字通りeveryビルドで作成されることに注意してください。つまり、他のすべてのオブジェクトファイルが最新の場合でも、このコマンドを実行してgit_revision.hを再作成します。私は通常、同じgitリビジョンを何度も再構築しないので、それは大きな問題ではないはずですが、それは知っておくべきことであり、それがisの問題である場合、その後、これを使用しないでください。 (add_custom_commandを使用して回避策を作成することはおそらく可能ですが、今のところ必要はありません。)

0
David Z