安利一個 Go 開發技巧

大家好,我是明哥。

今天來安利一個非常好用的開發技巧,我們經常在使用一些工具時,查看工具的版本的時候,時常能看到版本信息非常多,連 git 的 commit id 都有。

~ ➤ docker version
Client:
 Cloud integration: v1.0.22
 Version:           20.10.11
 API version:       1.41
 Go version:        go1.16.10
 Git commit:        dea9396
 Built:             Thu Nov 18 00:36:09 2021
 OS/Arch:           darwin/arm64
 Context:           default
 Experimental:      true

最值得關注的是很多信息在每次構建時都會發生變化,如果這些信息是寫死在代碼中的變量裏的,那意味着每次構建都要修改代碼,一般情況下都不允許隨意代碼,構建時的代碼應與 git 版本分支上保持一致。

1. 實現動態信息注入


那 Go 程序又是如何實現這種個性化信息的動態注入呢?

在 go build 命令裏有一個 -ldflags 參數,該參數可以接收 -X importpath.name=value 形式的值,該值就是實現信息動態注入的核心入口。

以下面一段例子來演示

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:

此時,我給 run 或者 build 加上如下的 -ldflags 參數,Go 的編譯器就能接收到並賦值給我們指定的變量

~ ➤ go run  -ldflags "-X 'main.version=0.1' -X 'main.buildTime=2022-03-25' -X 'main.osArch=darwin/amd64'" main.go
Version: 0.1
Built: 2022-03-25
OS/Arch: darwin/amd64

我們只要編譯一次,後續執行二進制文件就不用再指定這麼長的一長參數了

~ ➤ go build  -ldflags "-X 'main.version=0.1' -X 'main.buildTime=2022-03-25' -X 'main.osArch=darwin/amd64'" main.go
~ ➤
~ ➤ ./main
Version: 0.1
Built: 2022-03-25
OS/Arch: darwin/amd64

2. 實際開發項目


上面爲了方便學習,主程序直接將版本信息直接打印出來了,實際上應該指定 version 參數再打印。

有了前面的基礎知識,下邊就演示一下正常開發中如何來注入版本信息

首先,初始化項目

go mod init github.com/iswbm/demo

然後創建 main.go

package main

import (
    "fmt"
    "os"
        "github.com/iswbm/demo/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")
    }
}

再創建  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),
    }
}

最後,使用如下命令去編譯

go build -ldflags "-X 'github.com/iswbm/demo/utils.version=0.1' -X 'github.com/iswbm/demo/utils.gitBranch=test' -X 'github.com/iswbm/demo/utils.gitTag=test' -X 'github.com/iswbm/demo/utils.gitCommit=test' -X 'github.com/iswbm/demo/utils.buildDate=2022-03-25' -X 'github.com/iswbm/demo/utils.osArch=darwin/amd64'"

編譯好後,可以運行一下看效果

3. 使用 Makefile


在上面可以看到,在編譯的時候,需要指定一大串的參數,相信你也已經崩潰了吧?

更合理的做法,是將這些參數 Makefile 來管理維護,在 Makefile 中可以用 shell 命令去獲取一些 git 的信息,比如下面這樣子

# gitTag
gitTag=$(git log --pretty=format:'%h' -n 1)

# commitID
gitCommit=$(git rev-parse --short HEAD)

# gitBranch
gitBranch=$(git rev-parse --abbrev-ref HEAD)

我先在該項目下初始化 Git 倉庫

# 初始化
git init .

# 添加所有文件到暫存區
git add -A

# 提交 commit
git commit -m "init repo"

然後編寫出如下的 Makefile 到項目的根目錄

BINARY="demo"
VERSION=0.0.1
BUILD=`date +%F`
SHELL := /bin/bash

versionDir="github.com/iswbm/demo/utils"
gitTag=$(shell git log --pretty=format:'%h' -n 1)
gitBranch=$(shell git rev-parse --abbrev-ref HEAD)
buildDate=$(shell TZ=Asia/Shanghai date +%FT%T%z)
gitCommit=$(shell git rev-parse --short HEAD)

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

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 命令,編譯出 mac 和 linux 兩個版本的二進制執行文件

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pXunL6fXXLEcYVLokDOKUw