33

给Go程序加入编译版本时间等信息

 4 years ago
source link: https://www.tuicool.com/articles/eaQnArb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
$./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 语言编译时,可以通过 -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

编译期将感兴趣的信息传入程序中

通过上面这种手法,我们可以编写一个用于编译 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 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 就更好啦。 :)

原文链接: https://pengrl.com/p/37397/

原文出处: yoko blog ( https://pengrl.com )

原文作者:yoko

版权声明:本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK