大家好,我是polarisxu。在项目中,尤其是开源项目中,会特别注意项目的版本号。有些项目会把版本号写入源码,每次升级都会修改源码号。但这不是一个特别好的方法。本文通过学习如何处理Go语言源码来掌握它,并将其应用到自己的项目中。本文基于Go1.17,不同版本的实现细节可能不同funcmain(){fmt.Println("GoVersion:",runtime.Version())}02如何实现查看runtime.Version源码://buildVersionistheGotree'sversionstringatbuildtime.////如果任何GOEXPERIMENT设置为非默认值,它将包括//"X:".////Thisissetbythelinker.////Thisisaccessedby"goversion".varbuildVersionstring//VersionreturnstheGotree'sversionstring.//Itiseitherthecommithasanddateatthetimeofthebuildor,//whenpossible,releasetaglike"go1.3"ersion(funcstringVersion){returnbuildVersion}\根据注释提示,在Go仓库源码中找到src/cmd/link,就是实现的去链接器。在internal/ld/main.go文件中发现以下代码:buildVersion:=buildcfg.Versionifgoexperiment:=buildcfg.GOEXPERIMENT();goexperiment!=""{buildVersion+="X:"+goexperiment}addstrdata1(ctxt,"runtime.buildVersion="+buildVersion)从buildcfg.Version中获取buildVersion值,如果设置了GOEXPERIMENT(环境变量值),则使用该值。关注buildcfg.Version是如何获取的:var(defaultGOROOTstring//setbylinkerGOROOT=envOr("GOROOT",defaultGOROOT)GOARCH=envOr("GOARCH",defaultGOARCH)GOOS=envOr("GOOS",defaultGOOS)GO386=envOr("GO386",defaultGO386)GOARM=goarm()GOMIPS=gomips()GOMIPS64=gomips64()GOPPC64=goppc64()GOWASM=gowasm()GO_LDSO=defaultGO_LDSOVersion=version)很奇怪,Version的值直接由版本赋值。但是版本的值是什么?在src/cmd/dist/buildruntime.go文件中,有一个数mkbuildcfg,用于生成buildcfg://mkbuildcfgwritesinternal/buildcfg/zbootstrap.go:////packagebuildcfg////constdefaultGOROOT=//constdefaultGO386=//...//constdefaultGOOS=runtime.GOOS//constdefaultGOARCH=runtime.GOARCH////Theuseofruntime.GOOSandruntime.GOARCHmakesurethat//across-compiledcompilerexpectstocompileforitsowntarget//system.Thatis,ifonaMacyoudo:////GOOS=linuxGOARCH=ppc64gobuildcmd/compile////theresultingcompilerwilldefaulttogeneratinglinux/ppc64objectfiles.//Thisismorefulthanhavingitdefaulttogeneratingobjectsforthe//originaltarget(inthisexample,aMac).funcmkbuildcfg(filestring){varbufbytes.Bufferfmt.Fprintf(&buf,"/tooldist生成;DONOTEDIT.\n")fmt.Fprintln(&buf)fmt.Fprintf(&buf,"packagebuildcfg\n")fmt.Fprintln(&buf)fmt.Fprintf(&buf,"import\"runtime\"\n")fmt.Fprintln(&buf)fmt.Fprintf(&buf,"constdefaultGO386=`%s`\n",go386)fmt.Fprintf(&buf,"constdefaultGOARM=`%s`\n",goarm)fmt.Fprintf(&buf,"constdefaultGOMIPS=`%s`\n",gomips)fmt.Fprintf(&buf,"constdefaultGOMIPS64=`%s`\n",gomips64)fmt.Fprintf(&buf,"constdefaultGOPPC64=`%s`\n",goppc64)fmt.Fprintf(&buf,"constdefaultGOEXPERIMENT=`%s`\n",goexperiment)fmt.Fprintf(&buf,"constdefaultGO_EXTLINK_ENABLED=`%s`\n",goextlinkenabled)fmt.Fprintf(&buf,"constdefaultGO_LDSO=`%s`\n",defaultldso)fmt.Fprintf(&buf,"constversion=`%s`\n",findgoversion())fmt.Fprintf(&buf,"constdefaultGOOS=runtime.GOOS\n")fmt.Fprintf(&buf,"constdefaultGOARCH=runtime.GOARCH\n")writefile(buf.String(),file,writeSkipSame)}其中version的值是通过findgoversion()获取的,定义在src/cmd/dist/build.go中:(部分细节略)//findgoversion确定要在版本字符串中使用的政府版本。funcfindgoversion()string{//$GOROOT/VERSIONfiletakespriority,fordistributions//withoutthesourcerepo.path:=pathf("%s/VERSION",goroot)ifisfile(path){...}//$GOROOT/VERSION.cachefileisacachetoavoidinvoking//giteverytimewerunthiscommand.UnlikeVERSION,itgets//deletedbythecleancommand.path=pathf("%s/VERSION.cache",goroot)ifisfile(path){returnchomp(readfile(path))}//Showanicererrormessageifthisisn'taGitrepo.if!isGitRepo(){fatalf("FAILED:notaGitrepo;mustputaVERSIONfilein$GOROOT")}//否则,使用Git。//当前分支是什么?branch:=chomp(run(goroot,CheckExit,"git","re??v-parse","--abbrev-ref","HEAD"))...//Cacheversion.writefile(tag,path,0)returntag}按照以下顺序获取Version(如果前面的获取不到,则依次执行后续的获取步骤)获取来自$GOROOT/VERSION,在这个文件中包含了版本信息。您可以在您的Go安装目录中查看该文件的信息。从$GOROOT/VERSION.cache中获取Git仓库生成的版本信息,并生成缓存,方便后面直接读取$GOROOT/VERSION。.cache获取03自己的项目通过前面的分析,总结一下Go版本是如何写入Go源码的:正式版是通过项目根目录下的一个文件获取的,比如$GOROOT/版本;非官方版本,通过Git获取版本信息;为了避免在编译过程中重复进行Git相关的操作,可以生成缓存;通过环境变量控制版本信息;最后,您可以通过API将版本信息公开给用户。对于我们自己的GoFor项目,通过Git获取版本信息可以通过shell脚本来实现。编译Go项目时,通过-X传入版本信息。现在我们通过脚本来实现这个功能。工程代码如下://main.gopackagemainimport("fmt")varVersionstringfuncmain(){fmt.Println("Version:",Version)}现在写一个shell脚本通过这个脚本编译上面的代码:#!/bin/shversion=""if[-f"VERSION"];thenversion=`catVERSION`fiif[[-z$version]];thenif[-d".git"];thenversion=`gitsymbolic-refHEAD|cut-b12-`-`gitrev-parseHEAD`elseversion="unknown"fifigobuild-ldflags"-Xmain.Version=$version"main.go如果有VERSION文件,读取该文件的值作为版本信息;如果version值为空,则判断当前项目是否为Git项目。如果是,获取版本信息,格式为:master-commithash;否则,将版本信息设置为未知;通过gobuild的ldflags将版本信息传递给main.Version;这样,项目中的Version就会被设置为正确的值。04小结本文通过对Go源码中版本信息的学习和研究,掌握了优秀开源项目设置版本信息的做法。最后,它演示了如何在您自己的项目中使用此技能。本文不演示环境变量,一般用的比较少。