有没有简单的方法可以编写可以访问其Git版本哈希的C代码?
我用C语言编写了用于在实验室环境中收集科学数据的软件。我的代码将它收集的数据记录在.yaml文件中,以供以后分析。我的实验每天都在变化,因此我经常不得不修改代码。为了跟踪修订,我使用git存储库。
我希望能够在我的.yaml数据文件中包含Git修订哈希作为注释。这样,我可以查看.yaml文件,确切地知道使用什么代码生成该文件中显示的数据。有没有简单的方法可以自动执行此操作?
有没有简单的方法可以编写可以访问其Git版本哈希的C代码?
我用C语言编写了用于在实验室环境中收集科学数据的软件。我的代码将它收集的数据记录在.yaml文件中,以供以后分析。我的实验每天都在变化,因此我经常不得不修改代码。为了跟踪修订,我使用git存储库。
我希望能够在我的.yaml数据文件中包含Git修订哈希作为注释。这样,我可以查看.yaml文件,确切地知道使用什么代码生成该文件中显示的数据。有没有简单的方法可以自动执行此操作?
Answers:
在我的程序中,我将git版本号和构建日期保存在一个名为的单独文件中version.c
,该文件如下所示:
#include "version.h"
const char * build_date = "2009-11-10 11:09";
const char * build_git_sha = "6b54ea36e92d4907aba8b3fade7f2d58a921b6cd";
还有一个头文件,如下所示:
#ifndef VERSION_H
#define VERSION_H
extern const char * build_date; /* 2009-11-10 11:09 */
extern const char * build_git_sha; /* 6b54ea36e92d4907aba8b3fade7f2d58a921b6cd */
#endif /* VERSION_H */
头文件和C文件都是由Perl脚本生成的,如下所示:
my $git_sha = `git rev-parse HEAD`;
$git_sha =~ s/\s+//g;
# This contains all the build variables.
my %build;
$build{date} = make_date_time ();
$build{git_sha} = $git_sha;
hash_to_c_file ("version.c", \%build, "build_");
这里hash_to_c_file
确实创造了所有的工作version.c
,并version.h
和make_date_time
使一个字符串,如图所示。
在主程序中,我有一个例程
#include "version.h"
// The name of this program.
const char * program_name = "magikruiser";
// The version of this program.
const char * version = "0.010";
/* Print an ID stamp for the program. */
static void _program_id_stamp (FILE * output)
{
fprintf (output, "%s / %s / %s / %s\n",
program_name, version,
build_date, build_git_sha);
}
我对git并不了解,所以如果有更好的方法可以发表评论。
如果使用的是基于make的构建,则可以将其放入Makefile中:
GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)"
(有关开关的功能,请参见man git describe)
然后将其添加到您的CFLAGS中:
-DVERSION=\"$(GIT_VERSION)\"
然后,您可以直接在程序中引用该版本,就像它是#define一样:
printf("Version: %s\n", VERSION);
默认情况下,这仅会打印一个简短的git commit id,但是您可以选择使用以下方式标记特定的发行版:
git tag -a v1.1 -m "Release v1.1"
然后它将打印出:
Version: v1.1-2-g766d
这意味着,在v1.1之后有2次提交,其git commit id以“ 766d”开头。
如果树中有未提交的更改,它将附加“ -dirty”。
没有依赖项扫描,因此您必须执行显式操作make clean
以强制更新版本。但是,这可以解决。
优点是它很简单,不需要像perl或awk这样的额外构建依赖项。我在GNU automake和Android NDK构建中使用了这种方法。
#define GIT_VERSION ...
而不是将其放在带有-D
选项的命令行上;它消除了依赖性问题。另外,为什么要加双下划线?从技术上讲,这是保留的标识符。
GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)"
,没有引号不能正常工作。
我最终使用了与@Kinopiko的答案非常相似的东西,但是我使用了awk而不是perl。如果您卡在安装了mingw而不是perl的awk的Windows机器上,这很有用。运作方式如下。
我的makefile中有一行,它调用git,date和awk来创建ac文件:
$(MyLibs)/version.c: FORCE
$(GIT) rev-parse HEAD | awk ' BEGIN {print "#include \"version.h\""} {print "const char * build_git_sha = \"" $$0"\";"} END {}' > $(MyLibs)/version.c
date | awk 'BEGIN {} {print "const char * build_git_time = \""$$0"\";"} END {} ' >> $(MyLibs)/version.c
每次编译代码时,awk命令都会生成一个version.c文件,如下所示:
/* version.c */
#include "version.h"
const char * build_git_sha = "ac5bffc90f0034df9e091a7b3aa12d150df26a0e";
const char * build_git_time = "Thu Dec 3 18:03:58 EST 2009";
我有一个看起来像这样的静态version.h文件:
/*version.h*/
#ifndef VERSION_H_
#define VERSION_H_
extern const char * build_git_time;
extern const char * build_git_sha;
#endif /* VERSION_H_ */
现在,我的其余代码可以通过仅包含version.h标头来访问构建时和git哈希。总结一下,我告诉git通过在.gitignore文件中添加一行来忽略version.c。这样git不会一直给我合并冲突。希望这可以帮助!
FORCE
不是一个好主意,因为将永远不会满足makefile(每次使您执行新的标头)。取而代之的是,您可以仅在Formula中将依赖项添加到相关的git文件中 $(MyLibs)/version.c : .git/COMMIT_EDITMSG .git/HEAD
。COMMIT_EDITMSG
每次提交时文件都会HEAD
更改,每次浏览历史记录时也会更改,因此,每次相关时文件都会更新。
您可以做两件事:
您可以使Git为您嵌入一些版本信息。
更简单的方法是使用ident
attribute,这意味着放置(例如)
*.yaml ident
在.gitattributes
文件中,并$Id$
在适当的位置。它将自动扩展为文件内容的SHA-1标识符(blob id):这不是文件版本,也不是最后一次提交。
Git确实以这种方式支持$ Id $关键字,以避免触摸在分支切换,倒回分支等过程中未更改的文件。如果您确实希望Git在文件中放置提交(版本)标识符或描述,则可以(滥用)filter
属性,使用干净/污迹过滤器在结帐时扩展某些关键字(例如$ Revision $),并对其进行清理以进行提交。
您可以像Linux内核或Git本身那样,通过构建过程来做到这一点。
查看GIT-VERSION-GEN脚本及其在Git Makefile中的用法,例如,该Makefile在gitweb/gitweb.cgi
文件的生成/配置过程中如何嵌入版本信息。
GIT-VERSION-GEN使用git describe生成版本描述。您需要更好地工作(使用签名/带注释的标签)标记项目的版本/里程碑。
基于njd27的答案,我使用的是带有依赖项扫描的版本,以及带有默认值的version.h文件,用于以不同方式构建代码。包含version.h的所有文件将被重建。
它还包括修订日期作为单独的定义。
# Get git commit version and date
GIT_VERSION := $(shell git --no-pager describe --tags --always --dirty)
GIT_DATE := $(firstword $(shell git --no-pager show --date=short --format="%ad" --name-only))
# recompile version.h dependants when GIT_VERSION changes, uses temporary file version~
.PHONY: force
version~: force
@echo '$(GIT_VERSION) $(GIT_DATE)' | cmp -s - $@ || echo '$(GIT_VERSION) $(GIT_DATE)' > $@
version.h: version~
@touch $@
@echo Git version $(GIT_VERSION) $(GIT_DATE)
我还使用git跟踪科学代码中的更改。我不想使用外部程序,因为它限制了代码的可移植性(例如,如果有人想在MSVS上进行更改)。
我的解决方案是仅使用主分支进行计算,并使用预处理器宏__DATE__
和来输出构建时间__TIME__
。这样我就可以用git log检查它,看看我正在使用哪个版本。参考:http : //gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
解决问题的另一种优雅方法是将git log包含到可执行文件中。用git log制作目标文件,并将其包含到代码中。这次,您使用的唯一外部程序是objcopy,但是编码较少。参考:http : //www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967并将数据嵌入C ++程序
您需要做的是生成一个头文件(例如,使用来自cmd行的回显),如下所示:
#define GIT_HASH \
"098709a0b098c098d0e"
要生成它,请使用如下代码:
echo #define GIT_HASH \ > file.h
echo " > file.h
echo git status <whatever cmd to get the hash> > file.h
echo " > file.h
可能需要使用引号和反斜杠来进行编译,但是您明白了。
--assume-unchanged
在其上设置标记(git update-index --assume-unchanged
)
基于Makefile和Shell的另一个变体
GIT_COMMIT_FILE=git_commit_filename.h
$(GIT_COMMIT_FILE): phony
$(eval GIT_COMMIT_SHA=$(shell git describe --abbrev=6 --always 2>/dev/null || echo 'Error'))
@echo SHA=$(GIT_COMMIT_SHA)
echo -n "static const char *GIT_COMMIT_SHA = \"$(GIT_COMMIT_SHA)\";" > $(GIT_COMMIT_FILE)
文件git_commit_filename.h将以包含静态const char * GIT_COMMIT_SHA =“”的一行结尾。
来自https://gist.github.com/larytet/898ec8814dd6b3ceee65532a9916d406
这是适用于Windows和Linux的CMake项目的解决方案,无需安装任何其他程序(例如,脚本语言)。
git哈希通过脚本写入.h文件,在Linux上编译时为bash脚本,在Windows上编译时为Windows批处理脚本,而CMakeLists.txt中的if子句则选择与平台对应的脚本。代码已编译。
以下2个脚本与CMakeLists.txt保存在同一目录中:
get_git_hash.sh:
#!/bin/bash
hash=$(git describe --dirty --always --tags)
echo "#ifndef GITHASH_H" > include/my_project/githash.h
echo "#define GITHASH_H" >> include/my_project/githash.h
echo "const std::string kGitHash = \"$hash\";" >> include/my_project/githash.h
echo "#endif // GITHASH_H" >> include/my_project/githash.h
get_git_hash.cmd:
@echo off
FOR /F "tokens=* USEBACKQ" %%F IN (`git describe --dirty --always --tags`) DO (
SET var=%%F
)
ECHO #ifndef GITHASH_H > include/my_project/githash.h
ECHO #define GITHASH_H >> include/my_project/githash.h
ECHO const std::string kGitHash = "%var%"; >> include/my_project/githash.h
ECHO #endif // GITHASH_H >> include/my_project/githash.h
在CMakeLists.txt中添加以下行
if(WIN32)
add_custom_target(
run ALL
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND get_git_hash.cmd
)
else()
add_custom_target(
run ALL
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ./get_git_hash.sh
)
endif()
include_directories(include)
在代码中,生成的文件包含在其中,#include <my_project/githash.h>
并且git hash可以用std::cout << "Software version: " << kGitHash << std::endl;
或打印到终端,或以类似方式写入yaml(或任何)文件。