1. 背景
当一个软件产品,无论独立进程还是运行库,发布到不同现场、不同机器上运行一年半载后,往往或调试bug、或改善性能、或加入新功能,需要基于当初发布的版本来增量开发,如果找不到现场软件对应代码仓库中哪一次提交,这项工作无法精准开展,只能基于某个近似版本开发,此时涉及到几项工作:
- 当初发布版本和近似版本存在新功能、性能、或解决过一些bug的差异,现在得重新填坑。
- 如果直接使用最新版本的软件产品,往往会因为由于新版依赖上下游不同的库、或不同的通信协议、或进程间不同的交互协议,导致工作量更大。
最后只能选择方案1发布到现场测试,你自己都不知道会发生什么,好比瞎子上战场,面对枪林弹雨。
这样的软件自然无法维护,成为烂尾。
2. 对策
下面提供一种以微软VS开发环境为例、集成Git代码仓库的解决方法。其中2. Python3脚本方法运用环境更广泛。
大概是利用VS执行编译之前的事件,去调用某个BAT文件,该文件获取当前仓库的最后git提交号,写入到头文件,后续实际软件代码包含该头文件,再提供一个API,让外部读取该git提交号。
2.1. BAT方法
- 编辑VS工程属性,在其预先生成事件的命令行加入
$(SolutionDir)\make_git_version.bat
。 - 此时再编译工程会调用该bat,并产生
git_version.h
文件。
make_git_version.bat
代码如下:
@echo on
set cmd="git describe --abbrev=7 --dirty --always --tags"
FOR /F "tokens=*" %%i IN (' %cmd% ') DO SET git_commit_id=%%i
set tmp_h=git_version.h.tmp
set git_version_h=git_version.h
echo #define GIT_VERSION #GIT_VERSION# >> %tmp_h%
powershell -Command "(Get-Content %tmp_h%) | ForEach-Object { $_ -replace '#GIT_VERSION#', '\"%git_commit_id%\"' } | Set-Content %git_version_h%"
del %tmp_h%
git_version.h内容示例:
#define GIT_VERSION "20a688e"
2.2. Python3脚本方法
支持C/C++/CSharp多种语言。
- 编辑VS工程属性,在其预先生成事件的命令行加入
python git_version.py
。 - 此时再编译工程会调用该脚本,并产生
git_version.h
文件。 - 产生CSharp语言文件时使用
python git_version.py -o git_version.cs
,会产生git_version.cs
文件,内容大概如下:class GitVersion { public static string GIT_VERSION = "87a96be"; }
git_version.py代码示例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, math, sys, platform, time, signal, fnmatch
import getopt
import hashlib
import threading, subprocess, re
import unittest
from optparse import OptionParser
from datetime import datetime
from functools import reduce
_g_version = "v1.0.0"
def get_timestamp():
return time.strftime("%Y%m%d%H%M%S", time.localtime(time.time()))
def execute_command(cmd):
exit_code = -1
output = ""
try:
proc = subprocess.Popen(cmd, shell=True
, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
line = proc.stdout.readline()
if not line:
break
line = line.decode('utf-8')
line = line.replace("'",'')
line = line.strip()
output += line
exit_code = proc.wait()
except:
print("cmd {} occur error.".format(cmd))
finally:
return exit_code, output
def main(options):
exit_code = 0
cmd = "git describe --abbrev=7 --dirty --always --tags"
exit_code, output = execute_command(cmd)
print(output)
fd = open(options.output, 'w')
if options.output.endswith(".h") \
or options.output.endswith(".c") \
or options.output.endswith(".cpp"):
data = "#define GIT_VERSION \"{}\"".format(output)
elif options.output.endswith(".cs"):
data = "class GitVersion " %(output)
fd.write(data)
fd.close()
sys.exit(exit_code)
if __name__ == '__main__':
appname = os.path.basename(sys.argv[0]).split(".")[0]
usage = "usage: %prog [options] arg"
parser = OptionParser(usage)
parser.add_option("-o", "--output", dest="output"
, default="git_version.h", help="output")
parser.add_option("-v", "--version", action="store_true", dest="version"
, help="print version")
(options, args) = parser.parse_args()
if options.version is not None:
print("{}".format(_g_version))
sys.exit(0)
main(options)
2.3. 编写获取版本号代码如version.h,其会包含git_version.h
,并读取git提交号
#ifndef __VERSION_H__
#define __VERSION_H__
#include "git_version.h"
#ifdef __cplusplus
extern "C" {
#endif
#define VERSION "V2.1.0"
#define STARE "RC"
static const char *get_version(void)
{
static char version[128];
if (version[0] == 0x00) {
static char *mon_tables[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
char date[16] = {0};
char year[16] = {0};
char month[16] = {0};
char day[16] = {0};
int i;
#ifdef _WIN64
char *platform = "64Bits";
#else
char *platform = "32Bits";
#endif
#ifdef _DEBUG
char *configure = "Debug";
#else
char *configure = "Release";
#endif
strcpy_s(date, sizeof(date), __DATE__);
sprintf_s(year, sizeof(year),
"%c%c%c%c/", date[7], date[8], date[9], date[10]);
memmove(month, date, 3);
for (i = 0; i < sizeof(mon_tables) / sizeof(char *); i++) {
if (strcmp(mon_tables[i], month) == 0)
break;
}
sprintf_s(month, sizeof(month), "%02d/", i+1);
memmove(day, &date[4], 2);
day[0] = day[0] == ' ' ? '0' : day[0];
sprintf_s(version, sizeof(version), "%s, %s, git %s, %s/%s, build time: %s%s%s %s"
, VERSION, STARE, GIT_VERSION, configure
, platform, year, month, day, __TIME__);
}
return version;
}
#ifdef __cplusplus
}
#endif
#endif
2.4. 编写读出版本号代码
# include "version.h"
int main(int argc, char **argv)
{
printf("My software version is %s\n", get_version());
return 0;
}
输出
My software version is Version:V2.1.0, RC, git faf8378 Debug/32Bits, build time: 2018/09/17 18:22:42
如果是库形式软件产品,可提供一个API供使用者调用,其实现返回get_version()
的返回值即可,需注意的是该返回值对应内存区域属于Data
段,而不是堆,故不允许调用free
释放。
2.5. 发布软件
每次较大修改后可修改上面versioh.h
中VERSION
,这是大版本号,git提交号只作为回溯git代码仓库提交。
2.6. 备注
文中以Visual Studio和C/C++语言为例,如果SVN或C#,可采用类似BAT的方式,采用Python语言时也类似,只要用好"git describe --abbrev=7 --dirty --always --tags"
命令即可。