0%

golang在程序编译时使用ldflags加入版本信息和git信息等


概述

今天在查一个很久之前运行的程序的问题,结果发现自己根本不知道那个代码是什么时候,用什么版本代码编译的,导致查找问题时异常困难。所以以后写代码时,很有必要将相关编译信息加入到程序中,这样在某些时候,查找问题会方便很多,避免低级错误。


编译时加入自定义信息到程序中

通常情况下,我们直接使用go build来编译程序,要想加入自定义信息到程序中,很简单,编译时使用-ldflags就行。

例如有如下程序test.go:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

var AAA string
var BBB string

func main() {
fmt.Println("AAA =", AAA)
fmt.Println("BBB =", BBB)
}

在编译时加入-ldflags:

1
go build -ldflags "-X 'main.AAA=111' -X 'main.BBB=222'"

运行程序:

1
2
3
4
$ ./test

AAA = 111
BBB = 222

可以看到AAA和BBB两个变量的值变成了111和222,也就是在编译时加入的信息。

文档中可以看到相关参数的说明:

hdr-Compile_packages_and_dependencies

Command Line

1
2
3
4
5
6
7
8
9
-ldflags '[pattern=]arg list'
arguments to pass on each go tool link invocation.

-X importpath.name=value
Set the value of the string variable in importpath named name to value.
This is only effective if the variable is declared in the source code either uninitialized
or initialized to a constant string expression. -X will not work if the initializer makes
a function call or refers to other variables.
Note that before Go 1.5 this option took two separate arguments.

这里的importpath.name=value可以理解为包名.变量名,比如上面的main.AAA就表示main包下的AAA变量。


编译时加入git信息等到程序中

在上面已经叙述了如何加入自定义信息到程序中,所以只要在编译时先获取到相关git信息,然后通过ldflags来加入到程序中即可。

test.go代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

var (
Version string
GoVersion string
GitUrl string
GitBranch string
GitCommit string
GitLatestTag string
BuildTime string
)

func main() {
fmt.Println("Version =", Version)
fmt.Println("GoVersion =", GoVersion)
fmt.Println("GitUrl =", GitUrl)
fmt.Println("GitBranch =", GitBranch)
fmt.Println("GitCommit =", GitCommit)
fmt.Println("GitLatestTag =", GitLatestTag)
fmt.Println("BuildTime =", BuildTime)
}

编译时使用如下命令:

1
2
3
4
go build -ldflags "-X 'main.Version="v0.1.1"' -X 'main.GoVersion=`go version`' \
-X 'main.GitUrl=`git remote -v`' -X 'main.GitBranch=`git rev-parse --abbrev-ref HEAD`' \
-X 'main.GitCommit=`git rev-parse HEAD`' -X 'main.GitLatestTag=`git describe --tags --abbrev=0`' \
-X 'main.BuildTime=`date +"%Y-%m-%d %H:%M:%S %Z"`'"

其中:

  • go version:获取到编译使用的go版本信息。
  • git remote -v:获取到当前远程仓库的地址。
  • git rev-parse --abbrev-ref HEAD:获取到当前分支名。
  • git rev-parse HEAD:获取到当前代码的commit号。
  • git describe --tags --abbrev=0:获取到最近的tag。
  • date +"%Y-%m-%d %H:%M:%S %Z":获取当前编译时间。

运行程序,打印结果如下:

1
2
3
4
5
6
7
8
9
10
$ ./test

Version = v0.1.1
GoVersion = go version go1.15.5 linux/amd64
GitUrl = origin https://xxx.git (fetch)
origin https://xxx.git (push)
GitBranch = master
GitCommit = 71c8xxxxxxxx21b
GitLatestTag = v0.1.1
BuildTime = 2021-07-15 09:07:19 CST

这样就达到了目的。


使用Makefile来进行编译

注意到刚才编译时的命令:

1
2
3
4
go build -ldflags "-X 'main.Version="v0.1.1"' -X 'main.GoVersion=`go version`' \
-X 'main.GitUrl=`git remote -v`' -X 'main.GitBranch=`git rev-parse --abbrev-ref HEAD`' \
-X 'main.GitCommit=`git rev-parse HEAD`' -X 'main.GitLatestTag=`git describe --tags --abbrev=0`' \
-X 'main.BuildTime=`date +"%Y-%m-%d %H:%M:%S %Z"`'"

如果每次编译时,都要手动输入这么一大串……不可能的事情

可以使用Makefile来简化这个过程,在项目中新建Makefile文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VERSION="v0.1.1"
GO_VERSION=`go version`
GIT_URL=`git remote -v`
GIT_BRANCH=`git rev-parse --abbrev-ref HEAD`
GIT_COMMIT=`git rev-parse HEAD`
GIT_LATEST_TAG=`git describe --tags --abbrev=0`
BUILD_TIME=`date +"%Y-%m-%d %H:%M:%S %Z"`

LDFLAGS="-X 'main.Version=${VERSION}' -X 'main.GoVersion=${GO_VERSION}' -X 'main.GitUrl=${GIT_URL}' -X 'main.GitBranch=${GIT_BRANCH}' -X 'main.GitCommit=${GIT_COMMIT}' -X 'main.GitLatestTag=${GIT_LATEST_TAG}' -X 'main.BuildTime=${BUILD_TIME}'"

all: build

build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags ${LDFLAGS}

这里CGO_ENABLED=0 GOOS=linux GOARCH=amd64用于指定代码运行平台(交叉编译),其中:

  • CGO_ENABLED=0:表示不启用cgo。
  • GOOS=linux:表示为linux平台编译代码。
  • GOARCH=amd64:表示代码执行架构为amd64。

然后编译时,直接使用make即可:

1
make

其中all后面跟着的子模块就代表make时会执行的子模块,比如all: build1 build2那么make时就会执行build1和build2两个模块。也可以使用make build,这样就只会执行build子模块。

注意这里-X 'main.GoVersion=${GO_VERSION}'中还要加两个引号,是因为go version执行的结果中会包含空格,如果不加引号make会报错,索性这里全部加上了引号。


顺便在程序启动时打印一个字符画

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"github.com/common-nighthawk/go-figure"
)

func main() {

myFigure := figure.NewFigure("MyProject", "", true)

fmt.Println(myFigure.String())
}

效果如下:

1
2
3
4
5
6
 __  __           ____                     _                 _
| \/ | _ _ | _ \ _ __ ___ (_) ___ ___ | |_
| |\/| | | | | | | |_) | | '__| / _ \ | | / _ \ / __| | __|
| | | | | |_| | | __/ | | | (_) | | | | __/ | (__ | |_
|_| |_| \__, | |_| |_| \___/ _/ | \___| \___| \__|
|___/ |__/

程序启动时的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
=====================================================================
__ __ ____ _ _
| \/ | _ _ | _ \ _ __ ___ (_) ___ ___ | |_
| |\/| | | | | | | |_) | | '__| / _ \ | | / _ \ / __| | __|
| | | | | |_| | | __/ | | | (_) | | | | __/ | (__ | |_
|_| |_| \__, | |_| |_| \___/ _/ | \___| \___| \__|
|___/ |__/
=====================================================================
=====================================================================
Version: v0.1.1
Go Version: go version go1.15.5 linux/amd64
Git URL: origin https://xxx.git (fetch)
origin https://xxx.git (push)
Git Branch: master
Git Commit: 71c8xxxxxxxx21b
Git Latest Tag: v0.1.1
Build Time: 2021-07-15 09:07:19 CST

=====================================================================

部分问题

使用goland在编写Makefile时会出现格式错误:

由于Makefile规定缩进必需是tab,但是通常情况下我们把golang的tab改成了4个空格,这就导致用goland写的Makefile有格式错误。

一个简单的解决方法,下载Makefile插件即可:

image

插件安装之后重启goland,然后在编辑Makefile时,tab就会强制变为“tab”而不是4个空格。


make时出现version "go1.14" does not match go tool version "go1.15.5"

出现这个错误,可能是因为当前环境中的go版本与系统的go版本不一致造成的:

1
2
3
4
5
6
7
$ type go

go is /usr/bin/go

$ /usr/bin/go version

go version go1.15.5 linux/amd64

可以看到系统的go版本是1.15.5的,与当前环境的go1.14不一致。解决这个问题,只要让版本一致,或者删掉其中一个go环境就好了。

我这里直接把当前环境的go也升级到了1.15.5,就不再出现这个问题了。