给Go程序加入编译版本时间等信息
先看效果
$./myapp -v
GitCommitLog=d97d098e5bb4ad38a2a7968f273a256e10a0108f mod bininfo comment
GitStatus=cleanly
BuildTime=2019.10.26.194341
GoVersion=go version go1.13 darwin/amd64
runtime=darwin/amd64
myapp 是一个演示用的 demo 程序,输入 -v 参数运行时,打印出程序的一些信息。以上信息对应的说明如下:
# GitCommitLog
d97d098e5bb4ad38a2a7968f273a256e10a0108f: 源码最近一次 commit 的 sha 值
mod bininfo comment: 源码最近一次 commit 的描述信息
# GitStatus
cleanly: 表示本地代码相对于最近一次 commit,并没有任何修改
如果本地代码有修改,此处会显示修改过的文件
# BuildTime
2019.10.26.194341: 程序的编译时间为2019年10月26号19点43分41秒
# GoVersion
go version go1.13 darwin/amd64: 程序编译使用的 Go 版本为1.13,darwin 即 macos
# runtime
程序运行时的平台,因为 Go 的跨平台编译做的比较好,为了避免混淆,我
们在 GoVersion 打印了编译平台的同时,把运行平台也打印出来
ok,下面就来介绍是如何实现的。
依赖的知识点
Go 语言编译时,可以通过 go build -ldflags 的方式向程序中指定的包中的变量传递值。
拿下面这个十来行的程序做个演示:
package main
import "fmt"
var Foo string
func main() {
if Foo == "" {
fmt.Println("Foo is empty.")
} else {
fmt.Printf("Foo=%s\n", Foo)
}
}
如果直接使用 go build 编译,运行的结果是 Foo is empty.。
如果使用 go build -ldflags "-X 'main.Foo=test'" 编译,则运行的结果为 Foo=test。它的格式为 -X '<包名>.<变量名>=<值>'
编译期将感兴趣的信息传入程序中
通过上面这种手法,我们可以编写一个用于编译 Go 程序的 shell 脚本,在脚本中获取一些编译时期的信息,传递到程序中。
比如:
# 获取源码最近一次 git commit log,包含 commit sha 值,以及 commit message
GitCommitLog=`git log --pretty=oneline -n 1`
# 检查源码在最近一次 git commit 基础上,是否有本地修改,且未提交的文件
GitStatus=`git status -s`
# 获取当前时间
BuildTime=`date +'%Y.%m.%d.%H%M%S'`
# 获取Go的版本
BuildGoVersion=`go version`
# 之后将上面这些变量传递到 go build -ldflags 中,编译 Go 程序。
# 完整的 shell 脚本地址后文有。
更进一步
其实前面也提到, Go 不仅仅支持在编译时向 main 包中的变量传递值,也支持向非 main 包传递。
基于以上前提,为了以后写不同应用程序时,减少模板代码的拷贝,我专门写了一个 package (package bininfo github地址),代码如下:
package bininfo
import (
"fmt"
"runtime"
"strings"
)
var (
// 初始化为 unknown,如果编译时没有传入这些值,则为 unknown
GitCommitLog = "unknown"
GitStatus = "unknown"
BuildTime = "unknown"
BuildGoVersion = "unknown"
)
// 返回单行格式
func StringifySingleLine() string {
return fmt.Sprintf("GitCommitLog=%s. GitStatus=%s. BuildTime=%s. GoVersion=%s. runtime=%s/%s.",
GitCommitLog, GitStatus, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}
// 返回多行格式
func StringifyMultiLine() string {
return fmt.Sprintf("GitCommitLog=%s\nGitStatus=%s\nBuildTime=%s\nGoVersion=%s\nruntime=%s/%s\n",
GitCommitLog, GitStatus, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}
// 对一些值做美化处理
func beauty() {
if GitStatus == "" {
// GitStatus 为空时,说明本地源码与最近的 commit 记录一致,无修改
// 为它赋一个特殊值
GitStatus = "cleanly"
} else {
// 将多行结果合并为一行
GitStatus = strings.Replace(strings.Replace(GitStatus, "\r\n", " |", -1), "\n", " |", -1)
}
}
func init() {
beauty()
}
然后我们用一个 demo 程序 myapp.go 来演示如何使用,代码如下:
package main
import (
"flag"
"fmt"
"os"
"github.com/q191201771/naza/pkg/bininfo"
)
func main() {
v := flag.Bool("v", false, "show bin info")
flag.Parse()
if *v {
_, _ = fmt.Fprint(os.Stderr, bininfo.StringifyMultiLine())
os.Exit(1)
}
fmt.Println("my app running...")
fmt.Println("bye...")
}
最后,是我们的 build.sh 脚本,源码如下:
#!/usr/bin/env bash
set -x
# 获取源码最近一次 git commit log,包含 commit sha 值,以及 commit message
GitCommitLog=`git log --pretty=oneline -n 1`
# 将 log 原始字符串中的单引号替换成双引号
GitCommitLog=${GitCommitLog//\'/\"}
# 检查源码在git commit 基础上,是否有本地修改,且未提交的内容
GitStatus=`git status -s`
# 获取当前时间
BuildTime=`date +'%Y.%m.%d.%H%M%S'`
# 获取 Go 的版本
BuildGoVersion=`go version`
# 将以上变量序列化至 LDFlags 变量中
LDFlags=" \
-X 'github.com/q191201771/naza/pkg/bininfo.GitCommitLog=${GitCommitLog}' \
-X 'github.com/q191201771/naza/pkg/bininfo.GitStatus=${GitStatus}' \
-X 'github.com/q191201771/naza/pkg/bininfo.BuildTime=${BuildTime}' \
-X 'github.com/q191201771/naza/pkg/bininfo.BuildGoVersion=${BuildGoVersion}' \
"
ROOT_DIR=`pwd`
# 如果可执行程序输出目录不存在,则创建
if [ ! -d ${ROOT_DIR}/bin ]; then
mkdir bin
fi
# 编译多个可执行程序
cd ${ROOT_DIR}/demo/add_blog_license && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/add_blog_license &&
cd ${ROOT_DIR}/demo/add_go_license && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/add_go_license &&
cd ${ROOT_DIR}/demo/taskpool && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/taskpool &&
cd ${ROOT_DIR}/demo/slicebytepool && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/slicebytepool &&
cd ${ROOT_DIR}/demo/myapp && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/myapp &&
ls -lrt ${ROOT_DIR}/bin &&
cd ${ROOT_DIR} && ./bin/myapp -v &&
echo 'build done.'
写在最后
本文中的 package bininfo,编译脚本,示例代码都在我的 github 项目 naza (https://github.com/q191201771/naza) 中。
这个仓库包含了我平时学习 Go 练手写的一些基础库代码。有些已经在我的线上服务中使用了。后续我还会写一些文章介绍这个仓库中的其他包。
感谢阅读,如果觉得文章还不错的话,顺手给我的 github 项目 来个 star 就更好啦。
给Go程序加入编译版本时间等信息的更多相关文章
- 你的程序支持复杂的时间调度嘛?如约而来的 java 版本
你的程序支持复杂的时间调度嘛? 这篇文章介绍了时间适配器的c#版本,是给客户端用的,服务器自然也要有一套对应的做法,java版本的 [年][月][日][星期][时间] [*][*][*][*][*] ...
- 如何查看程序被哪个版本编译器编译的linux-gcc
如何查看程序被哪个版本编译器编译的linux-gcc http://bbs.csdn.net/topics/380000949 那是不可能的,除非你加入了调试信息,也就是编译的时候加入了-g参数,然后 ...
- 【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程
一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (a ...
- gcc 学习笔记(一) - 编译C程序 及 编译过程
一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (a ...
- 详解Qt,并举例说明动态编译(shared)和静态编译(static)以及debug and release 编译版本区别(可产生静态版的Debug版本,需要把-release 改为 –debug-and-release)
作为初入Qt学习的新人,花了整整一两天时间,对Qt编译版本等问题进行了一步步探索,首先感谢网站博客中文章,开始也不是很明白一些几个问题: 1.Qt版本问题 作为初学者,可能下载时这么多版本,如何选择呢 ...
- 浅谈VB.Net 程序的编译和动态编译
---恢复内容开始--- 一般,我们都是通过Visual Studio(下面简称vs)来编写和编译vb.net应用程序的,但是,不少的人并不知道vs是通过何种方式编译程序的.今天,我们就来探讨一下编译 ...
- 修改maven默认的JDK编译版本
1.全局模式(settings.xml) <profiles> <profile> <id>jdk-1.8</id> <activation> ...
- 关于一个程序的编译过程 zkjg面试
http://blog.csdn.net/gengyichao/article/details/6544266 一 以下是C程序一般的编译过程: 从图中看到: 将编写的一个c程序(源代码 )转换成可以 ...
- .NET程序的编译和运行
程序的编译和运行,总得来说大体是:首先写好的程序是源代码,然后编译器编译为本地机器语言,最后在本地操作系统运行. 下图为传统代码编译运行过程: .NET的编译和运行过程与之类似,首先编写好的源代码,然 ...
随机推荐
- CDA数据分析【数据收集】
一.机器收集数据 机器收集数据会从不同角度对数据进行抓取和采集,与之前手动收集数据不同,机器收集数据不再是用小样本.特定样本来采集和分析整体数据,而是采用大样本或整体数据进行分析,这打破了原来的数据分 ...
- Linux从入门到放弃、零基础入门Linux(第一篇):计算机操作系统简介、linux介绍
一.计算机操作系统简介 操作系统的定义: 操作系统是一个用来协调.管理和控制计算机硬件和软件资源的系统程序,它位于硬件和应用程序之间. 操作系统的内核的定义: 操作系统的内核是一个管理和控制程序,负责 ...
- 红黑树实现(c/c++)
红黑树 简介 一直想写的一种数据结构,非常厉害的思想,插入,删除,查找,修改,都是\(log_2 n\)的时间复杂度. 比AVL更强大的是,插入删除综合效率比AVL要优秀一点. 性质 一颗红黑树是满足 ...
- JS数据打印的几种方式
/** * 在页面输出内容! */ document.write("This's Great!"); /** * 控制台打印: */ console.log("This ...
- Nginx 高级配置--关于favicon.ico
Nginx 高级配置--关于favicon.ico 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.浏览器会默认帮咱们访问官网的图标 1>.浏览器访问网站"htt ...
- javascript中事件总结&通用的事件侦听器函数封装&事件委托
前言: JAVASCRIPT与HTML之间的交互是通过事件来实现的.事件,就是文档或浏览器窗口中发生的一些特定交互瞬间.可以使用侦听器( 或处理程序 )来预定事件,以便事件发生时执行相应的代码.这种在 ...
- guava布隆过滤器
pom引入依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava&l ...
- Python 实现基于信息熵的 ID3 算法决策树模型
版本说明 Python version: 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 11:21:07) [MSC v.1900 32 bit (Int ...
- CSP 201903-2 24点
这是上一次考csp时遇到的一道简单的问题,但是当时太菜了没有写出来. 问题描述: 直接上图 解决思路: 标准的表达式求解,可以用符号栈和数值栈来存放运算符和数值,需要注意的是从左到右扫描的时候 遇到 ...
- 五个goland进行go开发的小技巧
五个goland进行go开发的小技巧 本文译自5 Tips To Speed Up Golang Development With IntelliJ Or Goland 确实很实用. 1. 实现int ...