Makefileでは、これは次のようなもので行われます。
g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
バイナリは正確なコミットSHA1を知っているため、segfaultの場合にそれをダンプできるため、これは非常に便利です。
CMakeで同じことをどのように達成できますか?
バージョニングや同様の目的で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のどこにでもアクセスできるようにする必要があります。
私はこれを生成するような方法でこれを行いました:
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.*$" VERSION_SHA1 ${VERSION} )
string( REGEX REPLACE "[-g]" "" VERSION_SHA1 ${VERSION_SHA1} )
add_definitions( -DGIT_SHA1="${VERSION_SHA1}" )
(git describe --dirty
からの)リポジトリへの変更をキャッチするソリューションがあればいいのですが、git情報に関する何かが変更された場合にのみ再コンパイルをトリガーします。
既存のソリューションの一部:
.git/logs/HEAD
に依存します。これは、リポジトリ内の何かが変更された場合にのみ再コンパイルをトリガーしますが、「-ダーティ」状態を取得するための変更を見逃します。-dirty
状態になる変更をキャッチしますが、常に再コンパイルをトリガーします(バージョン情報ファイルの更新されたタイムスタンプに基づく)。3番目の解決策の1つの修正は、CMake 'copy_if_different'コマンドを使用することです。そのため、バージョン情報ファイルのタイムスタンプは、内容が変更された場合にのみ変更されます。
カスタムコマンドの手順は次のとおりです。
コード(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})
次の解決策は、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++ヘッダーファイルにすることは、読者への課題として残されています:-)
私はCMake側であなたを助けることはできませんが、Git側に関しては、LinuxカーネルとGitプロジェクト自体がどのようにそれを行うかを見てみることをお勧めします、 GIT-VERSION-GEN スクリプト、またはtigが Makefile でどのように実行するかgit describe
gitリポジトリが存在する場合、 "version
"/"VERSION
"/"GIT-VERSION-FILE
"にフォールバックして生成され、tarballに存在し、最終的にフォールスクリプト(または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_ \
(これらのマクロを気にしないでください。これは、生の値から文字列を作成するための少しめちゃくちゃなトリックです。)このファイルの最後に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.in
がgit-rev.h
としてビルドツリーにコピーされ、最後にgitリビジョンが追加されます。
したがって、次に行う必要があるのは、ファイルの1つにgit-rev.h
を含め、GIT_REV
マクロを使用して、現在のgitリビジョンハッシュを文字列値として生成することです。
このソリューションの良い点は、関連するターゲットをビルドするたびにgit-rev.h
が再作成されるため、cmake
を何度も実行する必要がないことです。
また、移植性が高い必要があります-ポータブルでない外部ツールは使用されておらず、血まみれの愚かなウィンドウcmdでも>
および>>
演算子をサポートしています;-)
単に2つのファイルにコードを追加するだけです:_CMakeList.txt
_および_main.cpp
_。
_# 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}\"")
_
_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を実行する前にファイルが存在しないため、使用したくありません。
CMakeにこの置換を行う組み込み機能がない場合は、テンプレートファイルを読み取り、上記のSHA1ハッシュを正しい場所に置換するラッパーシェルスクリプトを記述できます(sed
を使用して例)、実際のCMakeビルドファイルを作成し、CMakeを呼び出してプロジェクトをビルドします。
少し異なるアプローチは、SHA1置換オプションを作成することです。 CMakeファイルは、"NO_OFFICIAL_SHA1_HASH"
などのダミーハッシュ値で作成します。開発者が作業ディレクトリから独自のビルドをビルドする場合、作業ディレクトリのコードには対応するSHA1ハッシュ値がまだないため、ビルドされたコードにはSHA1ハッシュ値(ダミー値のみ)が含まれません。
一方、ビルドサーバーによって公式リポジトリが中央リポジトリからプルされたソースから作成された場合、ソースコードのSHA1ハッシュ値がわかります。その時点で、CMakeファイルのハッシュ値を置き換えてから、CMakeを実行できます。
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
を使用して回避策を作成することはおそらく可能ですが、今のところ必要はありません。)