美文网首页田字簿
给Go程序加入编译版本时间等信息

给Go程序加入编译版本时间等信息

作者: 就想叫yoko | 来源:发表于2019-10-27 10:20 被阅读0次

    先看效果

    $./myapp -v
    GitCommitLog=d97d098e5bb4ad38a2a7968f273a256e10a0108f mod bininfo comment
    GitStatus=cleanly
    BuildTime=2019.10.26.194341
    GoVersion=go version go1.13 darwin/amd64
    runtime=darwin/amd64
    

    myapp 是一个演示用的 demo 程序,输入 -v 参数运行时,打印出程序的一些信息。以上信息对应的说明如下:

    # GitCommitLog
      d97d098e5bb4ad38a2a7968f273a256e10a0108f: 源码最近一次 commit 的 sha 值
      mod bininfo comment: 源码最近一次 commit 的描述信息
    # GitStatus
      cleanly: 表示本地代码相对于最近一次 commit,并没有任何修改
      如果本地代码有修改,此处会显示修改过的文件
    # BuildTime
      2019.10.26.194341: 程序的编译时间为2019年10月26号19点43分41秒
    # GoVersion
      go version go1.13 darwin/amd64: 程序编译使用的 Go 版本为1.13,darwin 即 macos
    # runtime
      程序运行时的平台,因为 Go 的跨平台编译做的比较好,为了避免混淆,我
      们在 GoVersion 打印了编译平台的同时,把运行平台也打印出来
    

    ok,下面就来介绍是如何实现的。

    依赖的知识点

    Go 语言编译时,可以通过 go build -ldflags 的方式向程序中指定的包中的变量传递值。

    拿下面这个十来行的程序做个演示:

    package main
    
    import "fmt"
    
    var Foo string
    
    func main() {
        if Foo == "" {
            fmt.Println("Foo is empty.")
        } else {
            fmt.Printf("Foo=%s\n", Foo)
        }
    }
    

    如果直接使用 go build 编译,运行的结果是 Foo is empty.

    如果使用 go build -ldflags "-X 'main.Foo=test'" 编译,则运行的结果为 Foo=test。它的格式为 -X '<包名>.<变量名>=<值>'

    编译期将感兴趣的信息传入程序中

    通过上面这种手法,我们可以编写一个用于编译 Go 程序的 shell 脚本,在脚本中获取一些编译时期的信息,传递到程序中。

    比如:

    # 获取源码最近一次 git commit log,包含 commit sha 值,以及 commit message
    GitCommitLog=`git log --pretty=oneline -n 1`
    # 检查源码在最近一次 git commit 基础上,是否有本地修改,且未提交的文件
    GitStatus=`git status -s`
    # 获取当前时间
    BuildTime=`date +'%Y.%m.%d.%H%M%S'`
    # 获取Go的版本
    BuildGoVersion=`go version`
    
    # 之后将上面这些变量传递到 go build -ldflags 中,编译 Go 程序。
    # 完整的 shell 脚本地址后文有。
    

    更进一步

    其实前面也提到, Go 不仅仅支持在编译时向 main 包中的变量传递值,也支持向非 main 包传递。

    基于以上前提,为了以后写不同应用程序时,减少模板代码的拷贝,我专门写了一个 package (package bininfo github地址),代码如下:

    package bininfo
    
    import (
        "fmt"
        "runtime"
        "strings"
    )
    
    var (
        // 初始化为 unknown,如果编译时没有传入这些值,则为 unknown
        GitCommitLog   = "unknown"
        GitStatus      = "unknown"
        BuildTime      = "unknown"
        BuildGoVersion = "unknown"
    )
    
    // 返回单行格式
    func StringifySingleLine() string {
        return fmt.Sprintf("GitCommitLog=%s. GitStatus=%s. BuildTime=%s. GoVersion=%s. runtime=%s/%s.",
            GitCommitLog, GitStatus, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
    }
    
    // 返回多行格式
    func StringifyMultiLine() string {
        return fmt.Sprintf("GitCommitLog=%s\nGitStatus=%s\nBuildTime=%s\nGoVersion=%s\nruntime=%s/%s\n",
            GitCommitLog, GitStatus, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
    }
    
    // 对一些值做美化处理
    func beauty() {
        if GitStatus == "" {
            // GitStatus 为空时,说明本地源码与最近的 commit 记录一致,无修改
            // 为它赋一个特殊值
            GitStatus = "cleanly"
        } else {
            // 将多行结果合并为一行
            GitStatus = strings.Replace(strings.Replace(GitStatus, "\r\n", " |", -1), "\n", " |", -1)
        }
    }
    
    func init() {
        beauty()
    }
    

    然后我们用一个 demo 程序 myapp.go 来演示如何使用,代码如下:

    package main
    
    import (
        "flag"
        "fmt"
        "os"
    
        "github.com/q191201771/naza/pkg/bininfo"
    )
    
    func main() {
        v := flag.Bool("v", false, "show bin info")
        flag.Parse()
        if *v {
            _, _ = fmt.Fprint(os.Stderr, bininfo.StringifyMultiLine())
            os.Exit(1)
        }
    
        fmt.Println("my app running...")
        fmt.Println("bye...")
    }
    

    最后,是我们的 build.sh 脚本,源码如下:

    #!/usr/bin/env bash
    
    set -x
    
    # 获取源码最近一次 git commit log,包含 commit sha 值,以及 commit message
    GitCommitLog=`git log --pretty=oneline -n 1`
    # 将 log 原始字符串中的单引号替换成双引号
    GitCommitLog=${GitCommitLog//\'/\"}
    # 检查源码在git commit 基础上,是否有本地修改,且未提交的内容
    GitStatus=`git status -s`
    # 获取当前时间
    BuildTime=`date +'%Y.%m.%d.%H%M%S'`
    # 获取 Go 的版本
    BuildGoVersion=`go version`
    
    # 将以上变量序列化至 LDFlags 变量中
    LDFlags=" \
        -X 'github.com/q191201771/naza/pkg/bininfo.GitCommitLog=${GitCommitLog}' \
        -X 'github.com/q191201771/naza/pkg/bininfo.GitStatus=${GitStatus}' \
        -X 'github.com/q191201771/naza/pkg/bininfo.BuildTime=${BuildTime}' \
        -X 'github.com/q191201771/naza/pkg/bininfo.BuildGoVersion=${BuildGoVersion}' \
    "
    
    ROOT_DIR=`pwd`
    
    # 如果可执行程序输出目录不存在,则创建
    if [ ! -d ${ROOT_DIR}/bin ]; then
      mkdir bin
    fi
    
    # 编译多个可执行程序
    cd ${ROOT_DIR}/demo/add_blog_license && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/add_blog_license &&
    cd ${ROOT_DIR}/demo/add_go_license && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/add_go_license &&
    cd ${ROOT_DIR}/demo/taskpool && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/taskpool &&
    cd ${ROOT_DIR}/demo/slicebytepool && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/slicebytepool &&
    cd ${ROOT_DIR}/demo/myapp && go build -ldflags "$LDFlags" -o ${ROOT_DIR}/bin/myapp &&
    ls -lrt ${ROOT_DIR}/bin &&
    cd ${ROOT_DIR} && ./bin/myapp -v &&
    echo 'build done.'
    

    写在最后

    本文中的 package bininfo,编译脚本,示例代码都在我的 github 项目 naza (https://github.com/q191201771/naza) 中。

    这个仓库包含了我平时学习 Go 练手写的一些基础库代码。有些已经在我的线上服务中使用了。后续我还会写一些文章介绍这个仓库中的其他包。

    感谢阅读,如果觉得文章还不错的话,顺手给我的 github 项目 来个 star 就更好啦。 :)

    原文链接: https://pengrl.com/p/37397/
    原文出处: yoko blog (https://pengrl.com)
    原文作者: yoko
    版权声明: 本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。

    相关文章

      网友评论

        本文标题:给Go程序加入编译版本时间等信息

        本文链接:https://www.haomeiwen.com/subject/ktypvctx.html