go基础库之go版本获取

golang 基础库之golang版本,介绍了如何获取Golang的运行时版本,以及通过源码解析版本是如何获取的

获取Golang版本

Golang 版本

1.12.1

实现

package main

import (
	"log"
	"runtime"
)

const info = `
    程序 %s 运行...
    由 Go:%s 版本编译
`

func main() {
	log.Printf(info, "Example", runtime.Version())
}

原理

runtime包中包含了许多有用的功能,要找出Go的运行时版本,我们就可以使用Version函数。它可以是提交时的哈希值或者是构建时的日期,也可以是“go1.3”之类的发布标记。

package runtime

import "runtime/internal/sys"
// Version returns the Go tree's version string.
// It is either the commit hash and date at the time of the build or,
// when possible, a release tag like "go1.3".
func Version() string {
	return sys.TheVersion
}

Version函数事实上只是返回了runtime/internal/sysTheVersion常量。常量位于$GOROOT/src/runtime/internal/sys/zversion.go中。

// Code generated by go tool dist; DO NOT EDIT.

package sys

const TheVersion = `go1.12.1`
const Goexperiment = ``
const StackGuardMultiplierDefault = 1

通过该文件的注释我们可以了解到,该文件是由go tool dist生成,然后我们可以在$GOROOT/src/cmd/dist/buildruntime.go 这个文件中找到生成该文件的方法mkzversion。进而我们找到了常量TheVersion的赋值方法findgoversion

package main
// mkzversion writes zversion.go:
//
//	package sys
//
//	const TheVersion = <version>
//	const Goexperiment = <goexperiment>
//	const StackGuardMultiplier = <multiplier value>
//
func mkzversion(dir, file string) {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "// Code generated by go tool dist; DO NOT EDIT.\n")
	fmt.Fprintln(&buf)
	fmt.Fprintf(&buf, "package sys\n")
	fmt.Fprintln(&buf)
	fmt.Fprintf(&buf, "const TheVersion = `%s`\n", findgoversion())
	fmt.Fprintf(&buf, "const Goexperiment = `%s`\n", os.Getenv("GOEXPERIMENT"))
	fmt.Fprintf(&buf, "const StackGuardMultiplierDefault = %d\n", stackGuardMultiplierDefault())

	writefile(buf.String(), file, writeSkipSame)
}

findgoversion方法所在文件是$GOROOT/src/cmd/dist/build.go

package main

// findgoversion determines the Go version to use in the version string.
func findgoversion() string {
	// The $GOROOT/VERSION file takes priority, for distributions
	// without the source repo.
	path := pathf("%s/VERSION", goroot)
	if isfile(path) {
		b := chomp(readfile(path))
		// Commands such as "dist version > VERSION" will cause
		// the shell to create an empty VERSION file and set dist's
		// stdout to its fd. dist in turn looks at VERSION and uses
		// its content if available, which is empty at this point.
		// Only use the VERSION file if it is non-empty.
		if b != "" {
			// Some builders cross-compile the toolchain on linux-amd64
			// and then copy the toolchain to the target builder (say, linux-arm)
			// for use there. But on non-release (devel) branches, the compiler
			// used on linux-amd64 will be an amd64 binary, and the compiler
			// shipped to linux-arm will be an arm binary, so they will have different
			// content IDs (they are binaries for different architectures) and so the
			// packages compiled by the running-on-amd64 compiler will appear
			// stale relative to the running-on-arm compiler. Avoid this by setting
			// the version string to something that doesn't begin with devel.
			// Then the version string will be used in place of the content ID,
			// and the packages will look up-to-date.
			// TODO(rsc): Really the builders could be writing out a better VERSION file instead,
			// but it is easier to change cmd/dist than to try to make changes to
			// the builder while Brad is away.
			if strings.HasPrefix(b, "devel") {
				if hostType := os.Getenv("META_BUILDLET_HOST_TYPE"); strings.Contains(hostType, "-cross") {
					fmt.Fprintf(os.Stderr, "warning: changing VERSION from %q to %q\n", b, "builder "+hostType)
					b = "builder " + hostType
				}
			}
			return b
		}
	}

	// The $GOROOT/VERSION.cache file is a cache to avoid invoking
	// git every time we run this command. Unlike VERSION, it gets
	// deleted by the clean command.
	path = pathf("%s/VERSION.cache", goroot)
	if isfile(path) {
		return chomp(readfile(path))
	}

	// Show a nicer error message if this isn't a Git repo.
	if !isGitRepo() {
		fatalf("FAILED: not a Git repo; must put a VERSION file in $GOROOT")
	}

	// Otherwise, use Git.
	// What is the current branch?
	branch := chomp(run(goroot, CheckExit, "git", "rev-parse", "--abbrev-ref", "HEAD"))

	// What are the tags along the current branch?
	tag := "devel"
	precise := false

	// If we're on a release branch, use the closest matching tag
	// that is on the release branch (and not on the master branch).
	if strings.HasPrefix(branch, "release-branch.") {
		tag, precise = branchtag(branch)
	}

	if !precise {
		// Tag does not point at HEAD; add hash and date to version.
		tag += chomp(run(goroot, CheckExit, "git", "log", "-n", "1", "--format=format: +%h %cd", "HEAD"))
	}

	// Cache version.
	writefile(tag, path, 0)

	return tag
}

注释有很多但我们只需要理解版本的获取流程即可,首先$GOROOT/VERSION文件是优先级最高的,如果该文件不存在或者为空,则使用$GOROOT/VERSION.cache文件。如果$GOROOT/VERSION.cache也没有,则工具开始尝试使用Git信息来解析版本,但在这种情况下,Go的源码必须是通过git的方式下载的。