12使用 Go 管理版本

簡介

如果你曾經(jīng)運行過 docker version,
就會發(fā)現(xiàn)它提供了很多信息:

PS C:\Users\tzh> docker version
Client: Docker Engine - Community
 Version:           19.03.4
 API version:       1.40
 Go version:        go1.12.10
 Git commit:        9013bf5
 Built:             Thu Oct 17 23:44:48 2019
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.4
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.10
  Git commit:       9013bf5
  Built:            Thu Oct 17 23:50:38 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

對于編譯好的二進制文件而言, 獲取版本信息是非常重要的.
盡可能地提供詳細信息, 有利于后期的維護和排錯.

如何實現(xiàn)

對于版本信息等, 有兩種方式,
一種從外部獲取, 比如配置文件等,
另一種從源代碼中獲取, 將配置信息寫死在源代碼中.

這兩種都不太好, 比如編譯時間就不太好確定.
最好是能在 go build 時確定這些信息.

幸好, go build 提供了一個選項叫做 -ldflags '[pattern=]arg list'.

-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.

這使得我們可以在編譯生成二進制文件時, 指定某些變量的值.

比如我們有個文件是 company/buildinfo 包的一部分.

package buildinfo

var BuildTime string

運行 go build -ldflags="-X 'company/buildinfo.BuildTime=$(date)'" 會記錄編譯時間,
BuildTime 的值設(shè)置為編譯時的時間, 即從 $(date) 中獲取的時間.

參考:

實踐

新增 pkg/version 包, 用于獲取版本信息.

package version

// 這些值應該是從外部傳入的
var (
    gitTag       string = ""
    gitCommit    string = "$Format:%H$"          // sha1 from git, output of $(git rev-parse HEAD)
    gitTreeState string = "not a git tree"       // state of git tree, either "clean" or "dirty"
    buildDate    string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
package version

import (
    "fmt"
    "runtime"
)

// 構(gòu)建時的版本信息
type VersionInfo struct {
    GitTag       string `json:"git_tag"`
    GitCommit    string `json:"git_commit"`
    GitTreeState string `json:"git_tree_state"`
    BuildDate    string `json:"build_date"`
    GoVersion    string `json:"go_version"`
    Compiler     string `json:"compiler"`
    Platform     string `json:"platform"`
}

func (info VersionInfo) String() string {
    return info.GitTag
}

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

主要定義了一個結(jié)構(gòu)體, 保持版本信息.

有些信息可以通過 runtime 獲取, 有些是編譯時傳進來的.

這里沒有明確的版本號, 而是使用 git tag 作為版本標簽.

最后, 定義一個命令 version.

package cmd

import (
    "encoding/json"
    "fmt"

    "github.com/spf13/cobra"
    "tzh.com/web/pkg/version"
)

var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Print the version info of server",
    Long:  "Print the version info of server",
    Run: func(cmd *cobra.Command, args []string) {
        printVersion()
    },
}

func printVersion() {
    info := version.Get()
    infoj, err := json.MarshalIndent(&info, "", " ") // 加一點縮進
    if err != nil {
        fmt.Printf("遇到了錯誤: %v\n", err)
    }
    fmt.Println(string(infoj))
}

別忘了使用 AddCommand 添加子命令.

// 初始化, 設(shè)置 flag 等
func init() {
    cobra.OnInitialize(initConfig)
    rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./conf/config.yaml", "config file (default: ./conf/config.yaml)")
    rootCmd.AddCommand(versionCmd)
}

由此, 代碼基本已經(jīng)改完了, 還剩下最后一點, 修改 Makefile 文件,
以便簡化操作過程.

修改 Makefile

SHELL := /bin/bash
BASEDIR = $(shell pwd)

# build with version infos
versionDir = "tzh.com/web/pkg/version"
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)
buildDate = $(shell TZ=UTC date +%FT%T%z)
gitCommit = $(shell git log --pretty=format:'%H' -n 1)
gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)

ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}"

all: gotool build
build:
    go build -ldflags ${ldflags} ./
run:
    go run -ldflags ${ldflags} ./
docker:
    go run -ldflags ${ldflags} ./ -c ./conf/config_docker.yaml

首行定義了運行的 shell, 默認是 /bin/sh, 這里改成了更常用的 /bin/bash.

然后, 就是定義了一大堆需要的參數(shù).
在運行 go build 的時候添加了參數(shù) -ldflags ${ldflags}.

如此, 以后只要使用 make build 就能生成具有版本信息的二進制文件了.

編譯好之后, 可以運行 ./web version 查看具體的版本信息.

總結(jié)

通過為編譯時添加額外信息, 可以生成更具交互性的二進制文件.
同時, 也能體會到 Makefile 帶來的便捷.

當前部分的代碼

作為版本 v0.12.0

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容