一个相当常见的要求,方法是:我希望myapp --version
显示版本和Git提交哈希(包括存储库是否脏)。该应用程序是通过Makefile
构建的(实际上是由qmake
生成的,但现在让它保持“简单”状态)。我对Makefiles相当熟悉,但是这一点让我感到困惑。
我可以轻松获得所需的输出like this:
$ git describe --always --dirty --match 'NOT A TAG'
e0e8556-dirty
C ++代码期望提交哈希可以作为名为GIT_COMMIT
的预处理器宏使用,例如:
#define GIT_COMMIT "e0e8556-dirty" // in an include file
-DGIT_COMMIT=e0e8556-dirty // on the g++ command line
以下是我尝试将git describe
输出传递到C ++的几种不同方法。它们都不完美。
$(shell)
函数。我们使用make的$(shell)
函数运行shell命令并将结果粘贴到make变量中:
GIT_COMMIT := $(shell git describe --always --dirty --match 'NOT A TAG')
main.o: main.cpp
g++ -c -DGIT_COMMIT=$(GIT_COMMIT) -o$@ $<
这对于干净的版本有效,但是有一个问题:如果我更改Git哈希(例如,通过提交或修改干净的工作副本中的某些文件),make不会看到这些更改,而二进制文件不会重建。
version.h
在这里,我们使用make配方来生成一个version.h
文件,其中包含必要的预处理程序定义。目标是伪造的,因此总是可以重新构建(否则,在第一次构建之后,它总是会被视为最新的)。
.PHONY: version.h
version.h:
echo "#define GIT_COMMIT \"$(git describe --always --dirty --match 'NOT A TAG')\"" > $@
main.o: main.cpp version.h
g++ -c -o$@ $<
这可靠地工作,并且不会丢失对Git commit哈希的任何更改,但是这里的问题是,它始终会重建version.h
及其相关的所有内容(包括相当长的链接阶段)。
version.h
(如果已更改)这个想法:如果我将输出写入version.h.tmp
,然后将其与现有的version.h
进行比较,并且仅在后者不同时才覆盖后者,我们就不必总是重建。
但是,在实际开始运行任何配方之前,先弄清楚需要重建的内容。因此,这必须在该阶段之前完成,即也必须从$(shell)
函数运行。
这是我的尝试:
$(shell echo "#define GIT_COMMIT \"$$(git describe --always --dirty --match 'NOT A TAG')\"" > version.h.tmp; if diff -q version.h.tmp version.h >/dev/null 2>&1; then rm version.h.tmp; else mv version.h.tmp version.h; fi)
main.o: main.cpp version.h
g++ -c -o$@ $<
此几乎起作用:每当Git哈希值更改时,第一个构建重新生成version.h
并重新编译,而第二个构建也是如此。从那时起,make决定一切都是最新的。
因此,似乎make甚至在运行$(shell)
函数之前就决定要重建什么,这也使这种方法无效。
这似乎是一件很平常的事情,并且由于使make成为一个如此灵活的工具,我很难相信没有办法使这100%正确。 是否存在这种方法?
答案 0 :(得分:2)
直接使用.PHONY
意味着假定目标文件不存在,而对于实际文件则不需要。要强制可能重新构建文件的配方,请使其依赖于伪造目标。像这样:
.PHONY: force
version.c: force
printf '"%s"' `git describe --always --dirty` | grep -qsf - version.c \
|| printf >version.c 'const char version[]="%s";\n' `git describe --always --dirty`
(除非markdown无法识别标签,否则您必须在粘贴中修复该问题)
和version.c
配方将每次运行,因为假定它的假性依赖关系不存在,但是依赖于version.c的事物将检查真实文件,只有在其内容不正确的情况下,该文件才会真正更新。没有最新版本。
或者您可以像在问题中的“第二次处理”设置一样在version.h
中生成版本字符串,重要的是不要告诉make
真实文件是假的。
答案 1 :(得分:1)
首先,您可以生成一个假音version.h
,但只能在定义其他地方使用的version.cpp
函数的print_version
中使用它。每次调用make时,只要不做任何更改,都只会花费version.cpp
的超快速编译加上相当长的链接阶段。没有其他重新编译。
接下来,您可以使用一些递归make来解决您的问题:
TARGETS := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) ...
ifeq ($(MODE),)
$(TARGETS): version
$(MAKE) MODE=1 $@
.PHONY: version
version:
VERSION=$$(git describe --always --dirty) && \
printf '#define GIT_COMMIT "%s"\n' "$$VERSION" > version.tmp && \
if [ ! -f version.h ] || ! diff --brief version.tmp version.h &> /dev/null; then \
cp version.tmp version.h; \
fi
else
main.o: main.cpp version.h
g++ -c -o$@ $<
...
endif
仅当$(MAKE) MODE=1 $@
已被第一次make调用修改(或者无论如何还是必须重建目标)时,version.h
调用才会执行某些操作。并且只有当提交哈希值更改时,第一个make调用才会修改version.h
。
答案 2 :(得分:1)
为什么services.AddIdentityServer
取决于您的 String currentNetworkName = "";
ConnectivityManager connectivityManager =
((ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE));
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
boolean connected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
if (connected) {
// prevent duplicate connect broadcasts
String extraInfo = activeNetwork.getExtraInfo();
if(! currentNetworkName.equals(extraInfo)) {
// to do: handle network changes
currentNetworkName = extraInfo;
}
} else {
Log.d(TAG, "is not connected");
isConnected = false;
currentNetworkName = "";
}
文件?只要您在暂存区中提交或更改某些内容(通常不经常发生),便会触及到这一点。
version.h
如果您计划在某个时候不使用Git进行构建,那么您当然需要进行更改...
答案 3 :(得分:1)
事实证明,我的第三种方法毕竟还不错:$(shell)
确实在确定要重建的内容之前先运行。问题是,在隔离测试期间,我无意中将version.h
提交给存储库,这导致了两次重建。
但是,由于@BasileStarynkevitch和@RenaudPacalet,仍有改进的空间:如果从多个文件中使用version.h
,则最好将哈希存储在version.cpp
文件中,所以我们只需要重新编译一个小文件并重新链接。
这是最终的解决方案:
version.h
#ifndef VERSION_H
#define VERSION_H
extern char const *const GIT_COMMIT;
#endif
制作文件
$(shell echo -e "#include \"version.h\"\n\nchar const *const GIT_COMMIT = \"$$(git describe --always --dirty --match 'NOT A TAG')\";" > version.cpp.tmp; if diff -q version.cpp.tmp version.cpp >/dev/null 2>&1; then rm version.cpp.tmp; else mv version.cpp.tmp version.cpp; fi)
# Normally generated by CMake, qmake, ...
main: main.o version.o
g++ -o$< $?
main.o: main.cpp version.h
g++ -c -o$@ $<
version.o: version.cpp version.h
g++ -c -o$@ $<
感谢大家使用替代方法!
答案 4 :(得分:0)
我建议生成一个微小的自给自足的C文件version.c
,该文件定义一些全局变量,并确保在myapp
可执行文件的每个成功链接处都重新生成该文件。
因此在您的makefile中
version.c:
echo "const char version_git_commit[]=" > $@
echo " \"$(git describe --always --dirty)\";" >> $@
然后有一些C ++标头声明它:
extern "C" const char version_git_commit[];
顺便说一句,请查看我的bismon存储库(提交c032c37be992a29a1e),其Makefile
,目标文件__timestamp.c
来激发灵感。请注意,对于二进制可执行文件bismonion,在每次成功链接后,make
都会删除 __timestamp.c
。
您可以改进Makefile
,以在每次成功链接可执行文件后删除version.c
和version.o
(例如,在$(LINK.cc)
可执行文件的myapp
行之后)。因此,您将在您的makefile中:
myapp: #list of dependencies, with version.o ....
$(LINK.cc) .... version.o ... -o $@
$(RM) version.o version.c
因此您每次只能重建version.c
和version.o
,而且速度很快。
答案 5 :(得分:0)
您可以通过直接从可执行文件中调用git rev-parse --short HEAD
命令来获取它
这是我所做的:
在CMakeLists.txt中
add_definitions("-DPROJECT_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"")
以及您的源文件中:
#include <array>
#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
inline std::string execCommand(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}
int main()
{
std::string git_command = "cd ";
git_command += PROJECT_DIR; // get your project directory from cmake variable
git_command += " && git rev-parse --short HEAD"; // get the git commit id in your project directory
std::cout << "Git commit id is :" << execCommand(git_command.c_str()) << std::endl;
return 0;
}
答案 6 :(得分:0)
我找到了一个不错的解决方案here:
在您的rsconf = {
_id : "amsdb",
members: [
{ _id : 0, host : "database:34000"},
{ _id : 1, host : "database2:34001" },
{ _id : 2, host : "database3:34002" }
]
}
rs.initiate(rsconf);
rs.conf();
中放置:
CMakeLists.txt
然后在您的源代码中定义:
# Get the current working branch
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Get the latest commit hash
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE)
现在在源中,它可以作为target_compile_definitions(${PROJECT_NAME} PRIVATE
"-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
来使用。可能需要通过 includes
#define
然后您就可以使用了,例如:
#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "?"
#endif