目录
  • 问题
  • 思路
  • 实现步骤
    • 1. 在代码中定义版本信息的变量
    • 2. 在代码中使用这些变量
    • 3. 在编译前获取版本信息
    • 4. 在编译时注入版本信息
    • 5. 验证效果
  • 总结

    问题

    一般而言,稍微做得好一点的开源软件,其二进制文件都会带上版本信息,比如Docker,你可以通过以下命令来查看:

    $ docker --version
    Docker version 20.10.17, build 100c701
    $ docker version
    ...
    //输出太长,这里就不展示了

    有些Web应用还提供了/version接口,你可以通过接口来获取:

    $ curl http://127.0.0.1:8080/version | jq
    {
      "Build Time": "Fri, 16 Jun 2023 16:19:23 +0800",
      "Git Commit": "b554659",
      "Go Version": "go1.19",
      "OS/Arch": "darwin/amd64",
      "version": "0.9.2"
    }

    然而很多公司并没有这方面的强制性规范,多数开发人员也没有这样的意识或习惯,所以大部分项目并没有实现这个功能。如果二进制文件不带上版本信息,你可能要借助其它系统来查询或追溯,这无疑是一件难受的事,对于代码调试和问题定位很不友好。

    那么在Golang中,如何实现这个功能呢?

    思路

    版本信息有很多,比如代码版本号、Git提交号、编译时间、Go版本号等,如何让二进制文件带上这些信息呢?

    硬编码在代码里肯定不行。因为Git提交号是将代码提交到代码仓库时才产生,而Go版本号和编译时间只有在编译时才能拿到。

    比较合理的做法是在编译时注入。Go编译工具提供了-ldflags选项,通过-X参数可以注入包变量的值,我们只需要在代码中将版本信息定义成包变量,然后在编译时完成注入即可。这个过程可以实现为自动化完成,下面以make工具为例说明。

    实现步骤

    1. 在代码中定义版本信息的变量

    将代码版本号、Git提交号、编译时间定义为包变量:

    package config
    var (
    	Version   string  //代码版本号
    	GitCommit string  //Git提交号
    	BuildTime string  //编译时间
    )

    Go版本号等信息可以借助runtime包获取:

    runtime.Version()  //Go版本
    runtime.GOOS       //操作系统
    runtime.GOARCH     //平台架构

    2. 在代码中使用这些变量

    1)实现–version参数或version子命令

    以Cobra命令行框架为例,为rootCmd实现–version参数:

    rootCmd.Version = config.Version
    rootCmd.SetVersionTemplate(fmt.Sprintf(`{{with .Name}}{{printf "%%s version information: " .}}{{end}}
        {{printf "Version:    %%s" .Version}}
        Git Commit: %s
        Build Time: %s
        Go version: %s
        OS/Arch:    %s/%s
    `, config.GitCommit, config.BuildTime, runtime.Version(), runtime.GOOS, runtime.GOARCH))

    2)实现/version接口

    以Gin框架为例,将/version路由到如下的Version函数:

    func Version(ctx *gin.Context) {
        ctx.Writer.Header().Set("Content-Type", "application/json")
        ctx.Writer.WriteHeader(http.StatusOK)
        json.NewEncoder(ctx.Writer).Encode(map[string]string{
            "version":    config.Version,
            "Git Commit": config.GitCommit,
            "Build Time": config.BuildTime,
            "Go Version": runtime.Version(),
            "OS/Arch":    runtime.GOOS + "/" + runtime.GOARCH,
        })
    }

    3. 在编译前获取版本信息

    在Makefile中,用git命令获取代码版本号、Git提交号,用date命令获取当前时间

    VERSION    = $(shell git describe --tags --always)
    GIT_COMMIT = $(shell git rev-parse --short HEAD)
    BUILD_TIME = $(shell date -R)

    4. 在编译时注入版本信息

    在Makefile中,通过-ldflags选项为go build命令传入编译参数,实现版本信息的注入

    define LDFLAGS
    "-X 'github.com/myname/myapp/config.Version=${VERSION}' \
    
    -X 'github.com/myname/myapp/config.GitCommit=${GIT_COMMIT}' \
    
    -X 'github.com/myname/myapp/config.BuildTime=${BUILD_TIME}'"
    endef
    build:
        go build -ldflags ${LDFLAGS} -o myapp_${VERSION} .

    5. 验证效果

    编译出二进制,即可验证–version参数;用二进制启动服务进程,即可验证/version接口。

    $ make build
    ...
    //编译过程,省略输出
    $ myapp --version
    myapp version information: 
        Version:    0.9.2
        Git Commit: b554659
        Build Time: Sat, 17 Jun 2023 11:30:44 +0800
        Go version: go1.20.5
        OS/Arch:    darwin/arm64
    $ myapp
    ...
    //启动进程,省略输出
    $ curl http://127.0.0.1:8080/version | jq
    {
      "Build Time": "Sat, 17 Jun 2023 11:30:44 +0800",
      "Git Commit": "b554659",
      "Go Version": "go1.20.5",
      "OS/Arch": "darwin/arm64",
      "version": "0.9.2"
    }

    总结

    二进制文件直接带上版本信息,能为代码调试和问题定位带来很大的方便。本文介绍了通过-ldflags选项实现在编译时注入版本信息的具体做法。

    这是一个比较通用的方法,很多开源软件比如 Docker 也是这样做的。

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。