14

git工作流下golang项目的version信息该如何处理

 3 years ago
source link: https://zhangguanzhang.github.io/2020/04/27/go-version-ifno/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

来探讨下git工作流下golang项目的version信息该如何处理比较符合标准

之前自己写的几个简单的小工具都是没有把version信息注入到编译出来的文件里,发布版本多了后也无法溯源了.想着有必要看下这方面的知识了.

观察现有的一些

之前我博客docker panic那次事故就是根据docker info里的containerd的commit id找到了相关的代码来回溯.我们先看看各个项目的version信息

  • docker:

    $ docker version
    Client: Docker Engine - Community
     Version:           19.03.2
     API version:       1.40
     Go version:        go1.12.8
     Git commit:        6a30dfc
     Built:             Thu Aug 29 05:28:55 2019
     OS/Arch:           linux/amd64
     Experimental:      false
    
    Server: Docker Engine - Community
     Engine:
      Version:          19.03.2
      API version:      1.40 (minimum version 1.12)
      Go version:       go1.12.8
      Git commit:       6a30dfc
      Built:            Thu Aug 29 05:27:34 2019
      OS/Arch:          linux/amd64
      Experimental:     false
     containerd:
      Version:          1.2.10
      GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
     runc:
      Version:          1.0.0-rc8+dev
      GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
     docker-init:
      Version:          0.18.0
      GitCommit:        fec3683
    
  • etcd:

    $ etcd --version
    etcd Version: 3.3.13
    Git SHA: 98d3084
    Go Version: go1.10.8
    Go OS/Arch: linux/amd64
    
  • k8s:

    $ kubectl version -o json
    {
      "clientVersion": {
        "major": "1",
        "minor": "13",
        "gitVersion": "v1.13.12",
        "gitCommit": "a8b52209ee172232b6db7a6e0ce2adc77458829f",
        "gitTreeState": "clean",
        "buildDate": "2019-10-15T12:12:15Z",
        "goVersion": "go1.11.13",
        "compiler": "gc",
        "platform": "linux/amd64"
      },
      "serverVersion": {
        "major": "1",
        "minor": "13",
        "gitVersion": "v1.13.12",
        "gitCommit": "a8b52209ee172232b6db7a6e0ce2adc77458829f",
        "gitTreeState": "clean",
        "buildDate": "2019-10-15T12:04:30Z",
        "goVersion": "go1.11.13",
        "compiler": "gc",
        "platform": "linux/amd64"
      }
    }
    

如何注入

对比下都有如下信息:

  • version
  • go version
  • arch info and os
  • git commit id
  • build date

github上一些star比较多的项目的 makefile 都是类似的如下内容

BUILD_VERSION   := $(shell cat version)
BUILD_TIME      := $(shell date "+%F %T")
COMMIT_SHA1     := $(shell git rev-parse HEAD)

all:
	gox -osarch="darwin/amd64 linux/386 linux/amd64" \
        -output="dist/{{.Dir}}_{{.OS}}_{{.Arch}}" \
        -tags="containers_image_openpgp" \
        -ldflags   "-X 'github.com/xxxx/xxxxx/cmd.version=${BUILD_VERSION}' \
					-X 'github.com/xxxx/xxxx/cmd.buildTime=${BUILD_TIME}' \
					-X 'github.com/xxxx/xxxx/cmd.commit=${COMMIT_SHA1}'"

搜索了下资料是通过 go build -ldflags 注入变量的,例如下面:

cat>test.go<<EOF
package main

var a string
func main(){
  print(a)
}
EOF

go build -ldflags '-X main.a=tttt' test.go
./test # 下面是输出
tttt

包名.变量名 形式注入到编译过程里,可以覆盖掉原有变量的值,变量首字母可以不用大写也会注入.知道了实现方法,来思考下如何优雅.

市面上很多都是单独整了个version文件,直接从里面cat的.实际都是master代码测试完美了后发布tag,tag触发release.可以根据tag号做版本号来外部注入.总的来说就是k8s这方面最规范.去看它的源码.

从k8s源码学习

查看了下源码结构找到了核心部分 staging/src/k8s.io/component-base/version/version.go

func Get() apimachineryversion.Info {
	// These variables typically come from -ldflags settings and in
	// their absence fallback to the settings in ./base.go
	return apimachineryversion.Info{
		Major:        gitMajor,
		Minor:        gitMinor,
		GitVersion:   gitVersion,
		GitCommit:    gitCommit,
		GitTreeState: gitTreeState,
		BuildDate:    buildDate,
		GoVersion:    runtime.Version(),
		Compiler:     runtime.Compiler,
		Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
	}
}

以及文件 staging/src/k8s.io/component-base/version/base.go 里等待被注入的变量.查看了下makefile的逻辑,复杂的逻辑和变量处理是仍shell脚本里处理的,因为makefile并不是一个功能强的脚本语言.

脚本 hack/lib/init.sh 比较先执行,里面有执行

# The root of the build/dist directory
KUBE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
...
source "${KUBE_ROOT}/hack/lib/version.sh"

version脚本里逻辑可能对于非运维看比较麻烦,简单说下上面version信息:

  • Major 就是大版本号,例如 1.18.2 就是 1
  • Minor 就是小版本号,例如 1.18.2 就是 18
  • GitVersion 就是release的版本,如果你是master代码下载编译则是最新的 $release-dirty 字样
  • GitCommit 是取编译时候的git commit id
  • GitTreeState 是你代码有没有commit,修改了代码没有commit则是dirty,你提issue社区人员看到后不会过多理你来浪费时间
  • 其余几个没啥可说的

个人实现

我简单说下现在我推荐的逻辑判断:

  • 获取项目的目录,也就是上面的 KUBE_ROOT 设置下命令别名git,让git只对项目目录生效:
    git=(git --work-tree "${KUBE_ROOT}") #后面使用git命令用:"${git[@]}"
    
  • 获取构建时间
    BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
    
  • 获取当前HEAD的commit id
    HEAD=$("${git[@]}" rev-parse "HEAD^{commit}")
    
  • tag名不为空的时候(指定编译之前的tag版本传入tag变量),或者ci是tag触发的.判断下该tag名存在否
    if [ -n "$TAG" ]; then
      TAG_COMMITID=$("${git[@]}" rev-parse $TAG 2>/dev/null)
      if [ "$?" -ne 0 ];then
          echo no such tag: $TAG
          exit 1
      fi
    
  • 否则tag为空下也就是master的代码,获取下离master最近的也就是最新的的tag号:
    else #默认取最新的tag
      TAG_COMMITID=$("${git[@]}" rev-list --tags --max-count=1)
      TAG=$("${git[@]}" describe --tags ${TAG_COMMITID})
    fi
    
  • 取tag号
    "${git[@]}" checkout $TAG 2>/dev/null
    BUILD_VERSION=${TAG}
    
  • 设置git tree state,没提交代码则是dirty
    if [ -z "$("${git[@]}" status --porcelain 2>/dev/null)" ];then
      GIT_TREE_STATE='clean'
    else
      GIT_TREE_STATE='dirty'
    fi
    
  • 对比tag的commit id是否和head一致,这步是针对master的ci触发的version设置为上一个 $tag-dirty 的值
    if [ "${HEAD}" != "${TAG_COMMITID}" ];then
      #tag的基础上改动,所以tag版本号-dirty
      BUILD_VERSION+="-dirty"
      COMMIT_ID=${HEAD}
    else
      COMMIT_ID=${TAG_COMMITID}
    fi
    
  • 最后git切回去,因为前面都是取变量的值,不会动代码
    "${git[@]}" checkout $HEAD 2>/dev/null
    

整个细节性都在我github上

https://github.com/zhangguanzhang/gonelist

入口脚本是 https://github.com/zhangguanzhang/gonelist/blob/master/build/build.sh

# 使用master的代码构建
bash build/build.sh build

# 自己编译指定tag版本的话
export TAG_NUM=xxx # 如果是tag推送,上面的逻辑处理就是tag的值不用声明,自己编译最新的tag release也不用声明变量
bash build/build.sh release

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK