Golang中管理程序的版本信息

栏目: IT技术 · 发布时间: 4年前

内容简介:我们都知道在一些Golang写的程序中,默认会有比如我们常用的Golang开发的程序是这样输出版本相关信息的:从上面的版本输出记录中我们其实可以看出,版本信息中不仅记录了基本的构建环境和版本信息,同时还记录了commitId,所以这些信息应该是在build时动态传入到程序中的,大概猜测一下应该就是提前在程序的version相关代码中预留好版本相关的变量,然后在构建的时候对变量进行赋值,以此来实现变量的动态注入。

我们都知道在一些Golang写的程序中,默认会有 version-v 相关的参数来输出软件版本信息,这些版本信息里可能包含软件版本,git中的commit记录,构建时间、构建环境等信息,那么这些信息都是如何在Golang程序中进行维护和管理的呢?请看:point_down:.

示例

比如我们常用的Golang开发的程序是这样输出版本相关信息的:

# k8s的客户端程序版本
$ kubectl version -o json --client
{
  "clientVersion": {
    "major": "1",
    "minor": "10",
    "gitVersion": "v1.10.11",
    "gitCommit": "637c7e288581ee40ab4ca210618a89a555b6e7e9",
    "gitTreeState": "clean",
    "buildDate": "2018-11-26T14:38:32Z",
    "goVersion": "go1.9.3",
    "compiler": "gc",
    "platform": "darwin/amd64"
  }
}

# docker的客户端程序版本
$ docker version
Client: Docker Engine - Community
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        6247962
 Built:             Sun Feb 10 04:12:39 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

# minio的客户端程序版本
$ minio --version
minio version RELEASE.2020-04-10T03-34-42Z

从上面的版本输出记录中我们其实可以看出,版本信息中不仅记录了基本的构建环境和版本信息,同时还记录了commitId,所以这些信息应该是在build时动态传入到程序中的,大概猜测一下应该就是提前在程序的version相关代码中预留好版本相关的变量,然后在构建的时候对变量进行赋值,以此来实现变量的动态注入。

小试牛刀

在大概猜测后,查看到相关文档中有说明可以在构建时使用参数 -ldflags -X importpath.name=value 来动态注入变量。在官方稳定 link 中有详细说明:

-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。而且仅当字符串变量在源码中没有初始化或初始化成常量字符串表达式时才有效。如果初始化变量调用了函数或者引用了其他变量, -X 参数也不会工作。

所以,总结起来就是,可以使用该参数给源码中动态传入一些简单的字符串变量。

接下来,我们简单试用一下:

$ cat main.go
cat main.go
package main
import "fmt"
var (
    version     string
    buildTime   string
    osArch      string
)
func main() {
    fmt.Printf("Version: %s\nBuilt: %s\nOS/Arch: %s\n",version,buildTime,osArch)
}

# 直接运行
$ go run main.go
Version:
Built:
OS/Arch:

# 使用-ldflags -X importpath.name参数注入变量
$ go run  -ldflags "-X 'main.version=0.1' -X 'main.buildTime=2020-06-26' -X 'main.osArch=darwin/amd64'" main.go
Version: 0.1
Built: 2020-06-26
OS/Arch: darwin/amd64

# 使用参数编译到二进制程序中
$ go build  -ldflags "-X 'main.version=0.1' -X 'main.buildTime=2020-06-26' -X 'main.osArch=darwin/amd64'" main.Golang
$ ./main
Version: 0.1
Built: 2020-06-26
OS/Arch: darwin/amd64

如上示例中简单演示了,如何在构建时,给程序注入一些版本相关的信息,这样我们在每次发版时,就可以根据当前的版本、环境、构建等信息为程序注入一个详细的版本信息了。

项目中使用

上面只是一个简单的示例用来演示如何利用 -ldflags -X importpath.name=value 参数来给程序动态注入一些变量值,在实际项目中可能需要维护一个相对全面和通用的版本信息,接下来一起看看项目中大概会如何使用。

# 首先,在项目中维护一个version.go的文件,用于定义版本相关的信息
$ cat utils/version.go
package utils

import (
    "fmt"
    "runtime"
)

var (
    version      string
    gitBranch    string
    gitTag       string
    gitCommit    string
    gitTreeState string
    buildDate    string
)

type Info struct {
    Version      string `json:"version"`
    GitBranch    string `json:"gitBranch"`
    GitTag       string `json:"gitTag"`
    GitCommit    string `json:"gitCommit"`
    GitTreeState string `json:"gitTreeState"`
    BuildDate    string `json:"buildDate"`
    GoVersion    string `json:"goVersion"`
    Compiler     string `json:"compiler"`
    Platform     string `json:"platform"`
}

func (info Info) String() string {
    return info.GitCommit
}

func GetVersion() Info {
    return Info{
        Version:      version,
        GitBranch:    gitBranch,
        GitTag:       gitTag,
        GitCommit:    gitCommit,
        GitTreeState: gitTreeState,
        BuildDate:    buildDate,
        GoVersion:    runtime.Version(),
        Compiler:     runtime.Compiler,
        Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
    }
}

# 然后可以在main程序中,对version子命令的输出调用utils.Get()函数
$ cat main.go
package main
import (
    "fmt"
    "os"

    "version/utils"
)

func main() {

    args := os.Args
    if len(args) >= 2 && args[1] == "version" {
        v := utils.GetVersion()
        fmt.Printf("Version: %s\nGitBranch: %s\nCommitId: %s\nBuild Date: %s\nGo Version: %s\nOS/Arch: %s\n", v.Version, v.GitBranch, v.GitCommit, v.BuildDate, v.GoVersion, v.Platform)
    } else {
        fmt.Printf("Version(hard code): %s\n","0.1")
    }
}

$ go build   -ldflags "-X 'version/utils.version=0.1' -X 'version/utils.gitBranch=test' -X 'version/utils.gitTag=test' -X 'version/utils.gitCommit=test' -X 'version/utils.buildDate=2020-06-26' -X 'version/utils.osArch=darwin/amd64'" main.go

$ ./main
Version(hard code): 0.1


$ ./main version
Version: 0.1
GitBranch: test
CommitId: test
Build Date: 2020-06-26
Go Version: go1.14
OS/Arch: darwin/amd64

此时,我们已经可以对程序中的版本信息进行动态维护和管理了,但是每次手动维护版本信息还是比较麻烦,因此常见的做法是使用脚本或者 Makefile 来对构建参数进行统一管理.

我们在项目中再增加 Makefile 来管理参数.

$ cat Makefile
.PHONY: list vet fmt default clean
all: list vet fmt default clean
BINARY="version"
VERSION=0.0.1
BUILD=`date +%F`
SHELL := /bin/bash
BASEDIR = $(shell pwd)

# build with verison infos
versionDir="version/utils"
gitTag=$(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi)
gitBranch=$(shell git rev-parse --abbrev-ref HEAD)
buildDate=$(shell TZ=Asia/Shanghai date +%FT%T%z)
gitCommit=$(shell git rev-parse --short HEAD)
gitTreeState=$(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)

ldflags="-s -w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState} -X ${versionDir}.version=${VERSION} -X ${versionDir}.gitBranch=${gitBranch}"


default:
    @echo "build the ${BINARY}"
    @GOOS=linux GOARCH=amd64 go build -ldflags ${ldflags} -o  build/${BINARY}.linux  -tags=jsoniter
    @go build -ldflags ${ldflags} -o  build/${BINARY}.mac  -tags=jsoniter
    @echo "build done."



# 构建测试
$ make default
build the version
build done.

$ ./build/version.mac
Version(hard code): 0.1

$ ./build/version.mac version
Version: 0.0.1
GitBranch: master
CommitId: c1702a8
Build Date: 2020-06-27T13:32:42+0800
Go Version: go1.14
OS/Arch: darwin/amd64

细心的小伙伴们有没有发现,此时我们的版本输出信息和前面 docker 以及 kubectl 程序的版本信息有那么一丢丢相似了,其实目前大多数使用Golang开源的程序基本上也都是采用这种方式来管理程序的版本信息的,比如 kubectl 中的version命令:

kubectl-version

kubernetes-apimachinery

上面的示例代码,我放到了 go-version 中了,有需要的小伙伴可以去看看。

Golang中管理程序的版本信息

公众号

欢迎关注我们的微信公众号,每天学习 Go 知识

Golang中管理程序的版本信息

以上所述就是小编给大家介绍的《Golang中管理程序的版本信息》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

你不是个玩意儿

你不是个玩意儿

杰伦·拉尼尔 / 葛仲君 / 中信出版社 / 2011-8 / 35.00元

“你不是个玩意儿。” 这句话当然不是骂人,这是一个宣言。人当然不是玩意儿,不是机器,而是人。 在网络化程度越来越高的今天,我们每个人似乎都有足够的理由,无限欣喜地拥抱互联网。然而,你有没有想过互联网那些不完美的设计却是某种潜在的威胁…… 为什么如此多的暴民在社交网站上争吵不休,很多骂人的脏话我们在现实的人际交往中可能从来不会使用,但在匿名网络环境中却漫天飞舞? 互联网的本质......一起来看看 《你不是个玩意儿》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试