yan's blog

Go 编译的时候带上版本信息

在我们平时使用程序的时候,程序的版本其实是一个很重要的信息。在 Go 语言编译程序的时候,怎么把版本信息尽可能方便的传递下去呢?本文谈谈我的想法。

最开始的想法

最开始的时候,我采用的是一种很笨的办法,那就是直接在代码里面hard code,比如

package main

import (
	"flag"
	"fmt"
)

func main() {
	version := "v1.0.1"
	v := flag.Bool("v", false, "show version")
	flag.Parse()
	if *v {
		fmt.Println(version)
	}
}

这样会有一个问题,当要发布新版本的时候,我什么时候才能修改 version 的值。

  • 如果先修改 version的值,然后提交 commit。如果跑 CI 的时候发现有问题,这个时候需要修改代码,然后还需要记得修改 version,不然用 -v 打印出的两个版本一样。这样会带来很大的心理负担,需要随时注意version 的值。
  • 如果先不动 version 的值,等 CI 跑通之后,再单独用一个 commit 来修改 version 的值也行。但是这样感觉不太优雅,同样也会带来心理负担,生怕忘记修改值了。

参考其他开源软件

因为上面方法的心理负担,于是我开始查看比较出名的一些开源库,比如 hugo, cloudflared,看看他们是怎么处理这个问题的。

hugo

我首先看了 hugo 的处理,发现跟我上面的第二点做法有类似之处,也是用了一个单独的 commit 来处理这个版本信息

cloudflared

然后我看了 cloudflared 的处理,在它的 Makefile 中有这样的处理(我删减了跟version无关的部分)

VERSION       := $(shell git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
DATE          := $(shell date -u '+%Y-%m-%d-%H%M UTC')
VERSION_FLAGS := -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"
LDFLAGS := -ldflags='$(VERSION_FLAGS) $(LINK_FLAGS)'

# 这一句是我删减了的
go build $(LDFLAGS) 

这里用到了 ldflags 来修改 main 中 Version 的值,不了解 ldflags 的朋友可以看看 Digital Ocean 的这篇文章

这个处理就比较友好了。在我们代码提交并且通过了 CI 之后,我们就可以打上 git tag 发布了。在 makefile 编译的时候,会把当前 git 的 tag 信息传递给我们的程序,这样我们不用一直记着来修改这个版本信息,也不用单独用一个 commit 来配置版本了。

示例

一个简单的main.go文件,打印当前的版本号

package main

import "fmt"

var version string

func main() {
	fmt.Println(version)
}

我们可以通过命令

go build -ldflags='-X "main.version=v1.5"'

来在编译时修改 main 中的 version 的值。当编译后执行时,结果为

v1.5

你有没有注意到上面的 version 不是 Version,虽然没有 exported 但是一样可以设置哦

如果将这次 git commit 提交的 SHA-1 checksum 作为版本号,那么makefile的编译命令可以这么写

version := $(shell git describe --always)
build:
	go build -ldflags='-X "main.version=${version}"'

另外,如果是需要修改我们内部包的变量,也是可以设置的哦。只需要把 main 改成内部包的包名就可以了。比如我想要修改 my-module/app/configkey 的值,那么只需要把上面命令中的 main.version 改成 my-module/app/config.key=1234455 即可。

go build -ldflags='-X "my-module/app/config.key=1234455" -X "main.version=${version}"'