前言

博主在查找一些Go内置的关键字(如make、append等)的具体实现源码时,发现网上的部分说明只直接提到了源码位于哪个package等,并未提及缘由。对应package内的源码中的func又是怎么被调用?这些都是让人疑惑的地方。
在初步研究后,博主准备通过一系列的文章,大致说明下这些内置关键字的处理过程及调用的查找快速查找方式,希望能对大家查找源码实现有所帮助。

Go是编译型语言,Go程序需要经过编译生成可执行文件才能运行,编译的命令是go buildgo是Go语言自带的强大工具,包含多个command,如go get命令拉取或更新代码,go run运行代码。在了解编译命令go build之前,先了解下go

使用方式

可以直接在终端中运行go,即可看到如下的使用提示。

Go is a tool for managing Go source code.Usage:go <command> [arguments]The commands are:bug         start a bug reportbuild       compile packages and dependenciesclean       remove object files and cached filesdoc         show documentation for package or symbolenv         print Go environment informationfix         update packages to use new APIsfmt         gofmt (reformat) package sourcesgenerate    generate Go files by processing sourceget         add dependencies to current module and install theminstall     compile and install packages and dependencieslist        list packages or modulesmod         module maintenancerun         compile and run Go programtest        test packagestool        run specified go toolversion     print Go versionvet         report likely mistakes in packagesUse "go help <command>" for more information about a command.Additional help topics:buildmode   build modesc           calling between Go and Ccache       build and test cachingenvironment environment variablesfiletype    file typesgo.mod      the go.mod filegopath      GOPATH environment variablegopath-get  legacy GOPATH go getgoproxy     module proxy protocolimportpath  import path syntaxmodules     modules, module versions, and moremodule-get  module-aware go getmodule-auth module authentication using go.summodule-private module configuration for non-public modulespackages    package lists and patternstestflag    testing flagstestfunc    testing functionsUse "go help <topic>" for more information about that topic.

从提示中可以看出,go工具的使用方式如下:

go <command> [arguments]

对于具体command的说明可以运行go help <command>获取。

go tool溯源

go tool本身是由go语言实现的,源码位于/cmd/go package。

这里说下查找源码的简单小方法:当无法直接通过调用间的跳转找到源码时,可以直接通过全局搜索(范围选择所有位置)的方式来找相关的源码。如:我们要找go命令的源码,我们知道go命令的参数解析都是经过flag实现的。直接运行go命令,可以看到相关的help,搜索任一命令对应的解释,如:build的compile packages and dependencies,经过简单的排查即可找到对应的源码。

go tool main

go tool对应的源码入口在/cmd/go/main.go文件中,命令的入口为main func。main.go中还包含2个init的func,在了解main func前,先看下init func。

init func

func init() {base.Go.Commands = []*base.Command{//以下为具体的命令bug.CmdBug,//bugwork.CmdBuild,//buildclean.CmdClean,//cleandoc.CmdDoc,//docenvcmd.CmdEnv,//envfix.CmdFix,//fixfmtcmd.CmdFmt,//fmtgenerate.CmdGenerate,//generatemodget.CmdGet,//getwork.CmdInstall,//installlist.CmdList,//listmodcmd.CmdMod,//modrun.CmdRun,//runtest.CmdTest,//testtool.CmdTool,//toolversion.CmdVersion,//versionvet.CmdVet,//vet//以下为命令的具体的参数help.HelpBuildmode,help.HelpC,help.HelpCache,help.HelpEnvironment,help.HelpFileType,modload.HelpGoMod,help.HelpGopath,get.HelpGopathGet,modfetch.HelpGoproxy,help.HelpImportPath,modload.HelpModules,modget.HelpModuleGet,modfetch.HelpModuleAuth,modfetch.HelpModulePrivate,help.HelpPackages,test.HelpTestflag,test.HelpTestfunc,}
}type Command struct {// Run runs the command.// The args are the arguments after the command name.Run func(cmd *Command, args []string)// UsageLine is the one-line usage message.// The words between "go" and the first flag or argument in the line are taken to be the command name.UsageLine string// Short is the short description shown in the 'go help' output.Short string// Long is the long message shown in the 'go help <this-command>' output.Long string// Flag is a set of flags specific to this command.Flag flag.FlagSet// CustomFlags indicates that the command will do its own// flag parsing.CustomFlags bool// Commands lists the available commands and help topics.// The order here is the order in which they are printed by 'go help'.// Note that subcommands are in general best avoided.Commands []*Command
}var Go = &Command{UsageLine: "go",Long:      `Go is a tool for managing Go source code.`,// Commands initialized in package main
}

我们知道,同一个文件中出现多个init func时,会按照出现的顺序依次执行。

先看第一个init。base.Go是Command的具体实例,内里包含的UsageLine与Long正对应我们运行go命令获取的前2行。整个func就是是对base.Go初始化过程,封装了各个命令的对应处理至Commands参数中。

注意:每个命令的处理也有相关的init处理,根据依赖关系,这些init func运行在main的init前。如build对应的work.CmdBuild,其init中就指定了CmdBuild的Run func(此处仅粗略提及整个处理过程,具体过程在后续的文章中会详细探讨)。

var CmdBuild = &base.Command{UsageLine: "go build [-o output] [-i] [build flags] [packages]",Short:     "compile packages and dependencies",Long: `
Build compiles the packages named by the import paths,
along with their dependencies, but it does not install the results.
...`,
}func init() {...CmdBuild.Run = runBuild...
}

第二个init封装了默认的Usage,mainUsage中是对base.Go的格式化说明。

func init() {base.Usage = mainUsage
}func mainUsage() {help.PrintUsage(os.Stderr, base.Go)os.Exit(2)
}func PrintUsage(w io.Writer, cmd *base.Command) {bw := bufio.NewWriter(w)tmpl(bw, usageTemplate, cmd)bw.Flush()
}var usageTemplate = `{{.Long | trim}}Usage:{{.UsageLine}} <command> [arguments]The commands are:
{{range .Commands}}{{if or (.Runnable) .Commands}}{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}Use "go help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
{{if eq (.UsageLine) "go"}}
Additional help topics:
{{range .Commands}}{{if and (not .Runnable) (not .Commands)}}{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}Use "go help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
{{end}}
`

main func

main func是程序运行的入口,看下其处理的逻辑。

func main() {_ = go11tagflag.Usage = base.Usageflag.Parse()log.SetFlags(0)args := flag.Args()if len(args) < 1 {base.Usage()}if args[0] == "get" || args[0] == "help" {if !modload.WillBeEnabled() {// Replace module-aware get with GOPATH get if appropriate.*modget.CmdGet = *get.CmdGet}}cfg.CmdName = args[0] // for error messagesif args[0] == "help" {help.Help(os.Stdout, args[1:])return}// Diagnose common mistake: GOPATH==GOROOT.// This setting is equivalent to not setting GOPATH at all,// which is not what most people want when they do it.if gopath := cfg.BuildContext.GOPATH; filepath.Clean(gopath) == filepath.Clean(runtime.GOROOT()) {fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath)} else {for _, p := range filepath.SplitList(gopath) {// Some GOPATHs have empty directory elements - ignore them.// See issue 21928 for details.if p == "" {continue}// Note: using HasPrefix instead of Contains because a ~ can appear// in the middle of directory elements, such as /tmp/git-1.8.2~rc3// or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell.if strings.HasPrefix(p, "~") {fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p)os.Exit(2)}if !filepath.IsAbs(p) {if cfg.Getenv("GOPATH") == "" {// We inferred $GOPATH from $HOME and did a bad job at it.// Instead of dying, uninfer it.cfg.BuildContext.GOPATH = ""} else {fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p)os.Exit(2)}}}}if fi, err := os.Stat(cfg.GOROOT); err != nil || !fi.IsDir() {fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", cfg.GOROOT)os.Exit(2)}// Set environment (GOOS, GOARCH, etc) explicitly.// In theory all the commands we invoke should have// the same default computation of these as we do,// but in practice there might be skew// This makes sure we all agree.cfg.OrigEnv = os.Environ()cfg.CmdEnv = envcmd.MkEnv()for _, env := range cfg.CmdEnv {if os.Getenv(env.Name) != env.Value {os.Setenv(env.Name, env.Value)}}BigCmdLoop:for bigCmd := base.Go; ; {for _, cmd := range bigCmd.Commands {if cmd.Name() != args[0] {continue}if len(cmd.Commands) > 0 {bigCmd = cmdargs = args[1:]if len(args) == 0 {help.PrintUsage(os.Stderr, bigCmd)base.SetExitStatus(2)base.Exit()}if args[0] == "help" {// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.help.Help(os.Stdout, append(strings.Split(cfg.CmdName, " "), args[1:]...))return}cfg.CmdName += " " + args[0]continue BigCmdLoop}if !cmd.Runnable() {continue}cmd.Flag.Usage = func() { cmd.Usage() }if cmd.CustomFlags {args = args[1:]} else {base.SetFromGOFLAGS(cmd.Flag)cmd.Flag.Parse(args[1:])args = cmd.Flag.Args()}cmd.Run(cmd, args)base.Exit()return}helpArg := ""if i := strings.LastIndex(cfg.CmdName, " "); i >= 0 {helpArg = " " + cfg.CmdName[:i]}fmt.Fprintf(os.Stderr, "go %s: unknown command\nRun 'go help%s' for usage.\n", cfg.CmdName, helpArg)base.SetExitStatus(2)base.Exit()}
}

main的处理逻辑大致如下:

  1. 没有参数时,直接打印mainUsage,退出
  2. 对于get、help命令,如果没开启mod,则mod get替换为get
  3. 第一个参数为help时,根据后续命令处理
    • 如果没有命令,直接打印mainUsage
    • 后续仅一个命令且为documentation,打印所有命令的documentation。
    • 后续有多个命令时,确认后续命名是否是前一个子命令。若不是,则打印出错处;若一直是,则打印对应的说明。
  4. 检查GOPATH,若GOPATH与GOROOT设置在同一文件夹,则警告
  5. 检查GOROOT
  6. 获取环境变量,对于错误设置的环境变量值,会主动修正至正确值,减少错误带来的影响
  7. 循环查找对应的命令
    • 目标命令存在子命令

      • 若传入命令不足,则打印说明并退出;
      • 若后续命令为help,则打印对应的说明
      • 正常,则依次拼凑命令
    • 若命令不可执行,则跳过
    • 参数解析(若是需要自行解析,由对应的命令进行解析,否则统一解析参数),执行命令Run,执行结束后退出
  8. 若命令不存在,打印unknown command错误并结束运行

总体来说,go会先检查GOROOT、GOPATH等环境变量是否符合要求,然后获取调用参数。查询参数中是否出现init中封装在Commands中处理的具体Command,如果有的话,则进行subCommand的匹配,一直到完全匹配为止,中间有任何匹配不上处,均会报错退出。注意:go针对help及非help命令做了处理,两者最大的不同处是,匹配后help返回的使用提示,其他命令则执行命令的操作。

总结

本文主要是介绍go工具入口的处理逻辑,go工具的源码位于/cmd/go package,其本身只负责部分flag的解析,command的匹配,具体的执行由其internal package下对应command的Run func执行。稍后的文章中会说明go build的处理过程。

公众号

鄙人刚刚开通了公众号,专注于分享Go开发相关内容,望大家感兴趣的支持一下,在此特别感谢。

了解Go编译处理(一)—— go tool相关推荐

  1. 编译原理2---A Power Tool 正则表达式

    个人理解 正则表达式,重在表达,计算机领域有很多字符或字符串需要被表达(或者说描述),如果能有一种特殊语言能够通用地描述一种形式的字符串,那么计算机在查找,匹配,处理等操作时会变得更加方便. 使用re ...

  2. 【Android 逆向】修改运行中的 Android 进程的内存数据 ( Android 系统中调试器进程内存流程 | 编译内存调试动态库以及调试程序 )

    文章目录 一.Android 系统中调试器进程内存流程 二.编译内存调试动态库以及调试程序 三.博客资源 一.Android 系统中调试器进程内存流程 修改游戏运行中的内存 , 游戏运行之后 , 游戏 ...

  3. Linux编译和下载嵌入式实验,嵌入式实验6交叉编译及Linux简单程序设计实验

    <嵌入式实验6交叉编译及Linux简单程序设计实验>由会员分享,可在线阅读,更多相关<嵌入式实验6交叉编译及Linux简单程序设计实验(7页珍藏版)>请在人人文库网上搜索. 1 ...

  4. 【ceph】cmake管理Ceph编译+Ceph工程目录+cmake 实战学习

    前言 Ceph cmake 工程 cmake生成的目录 cmake工程添加新模块(CMakeLists.txt) 添加动态库依赖 cmake导入外部链接库 *.cmake文件 cmake生成编译DEB ...

  5. OnlyOffice验证(四)MoblieWeb编译

    OnlyOffice验证(四)MoblieWeb编译   按照官方build tool的说明编译会发现.移动端预览没有编译出来,但是社区版确实还有这个功能. web预览 mobile web预览    ...

  6. reactos 编译,安装篇

    在这里我们将具体谈一下reactos的编译和安装. 首先,要去www.reactos.org上下载最新的代码和编译工具: 目前reactos 版本为0.3.10,编译工具版本为:1.4.4 一般可在桌 ...

  7. exe一机一码加密工具_Python代码加密混淆

    我多想再见你 哪怕匆匆一眼就别离 python作为一种解释型语言,源代码加密本身比较困难.但有时候我们在发布一款python产品时又必须考虑到代码的加密性,以避免源代码泄露.为此,我查阅了一些资料,研 ...

  8. 现在的编译器还需要手动展开循环吗_一例 Go 编译器代码优化 bug 定位和修复解析...

    缘起 某日,一位友人在群里招呼我,"看到有人给 Go 提了个编译器的 bug,挺有意思,感觉还挺严重的,要不要来看看?"于是我打开了 issue 40367 .彼时,最新一条评论是 ...

  9. Valgrind使用说明

    from:http://bbs.rosoo.net/thread-726-1-1.html Valgrind是运行在Linux上一套基于仿真技术的程序调试和分析工具,是公认的最接近Purify的产品, ...

  10. XCTF-Reverse:python-trade

    测试文件:https://adworld.xctf.org.cn/media/task/attachments/69c8f29912ae4f679d92a6cd36c33196.pyc pyc反编译在 ...

最新文章

  1. Spring Boot集成Swagger导入YApi@无界编程
  2. 018 jquery中的事件
  3. 牛客 - 车辆调度(dfs)
  4. C语言中的静态函数的作用
  5. JQuery.autocomplete扩展功能:实现多列自动提示
  6. 指定一个actor对pawn不可见
  7. LUNA16_Challange数据预处理2
  8. 【数据结构笔记33】C实现:希尔排序、增量序列
  9. php7 opcode,php7最小化安装 vld扩展安装并查看php代码的opcode ast测试
  10. 初识Jasima-调度仿真系列教程预告
  11. (十五)洞悉linux下的Netfilteriptables:开发自己的hook函数【实战】
  12. 加密的m3u8、ts文件合并
  13. 数学建模英文论文写作
  14. 520送什么礼物好呢、适合送女友的礼物推荐
  15. 新华财经•专访 | 来自大洋彼岸对区块链大势的深度解读
  16. CTFshow-菜狗杯-web签到
  17. python中len是什么函数_总结Python中len()函数的使用实例
  18. 电路原理 | 电路基本定理
  19. 如何写出和阿里大佬一样高效优雅的打码
  20. androidPN java.lang.NoClassDefFoundError: org.androidpn.client.解决方法

热门文章

  1. 机器人手眼标定原理介绍(含详细推导过程)使用Tsai-Lenz算法
  2. 详解KITTI数据集
  3. 多米诺喷码机维修大全之----缺字、字体不成形、字体跑点以及歪
  4. python 拼音输入法_用Python从0开始实现一个中文拼音输入法的思路详解
  5. SAR成像(一):线性调频信号(LFM)和脉冲压缩
  6. 超火的ipad procreate必备神仙笔刷资源打包下载
  7. 中国第二代支付清算体系-总结
  8. Fragstats 提示错误与警告
  9. 百度文库和豆丁网的在线文档阅读功能
  10. 独家 | 精彩!这27本书籍,每位数据科学家都应该阅读(附说明图表)