Golang的命令go类似于一个工具箱,当我们执行go build时,go会进行包依赖分析,然后创建若干子进程去执行compile命令编译源码。go的入口函数是cmd/go/main.go:main。下面以go build为例简略分析这个过程。下文对代码做了非常多的精简,逻辑也做了简化。
一 main流程
main.go:main函数的的流程非常简单,如图所示。
go在解析完参数后,会查找对应的command。所有支持的command在main.go:init中初始化,并由internal目录下的各个包实现相对应的command。
func
如internal/work实现build命令,build.go中定义的CmdBuild如下所示。
var
确定command后,main会调用command定义的Run函数,运行command。
二 运行command
func
build.go初始化了CmdBuild的Run为runBuild函数。runBuild函数包含如下两个步骤
- 包依赖关系分析
- 根据依赖关系构造编译规则
- 调用compile编译源码
三 依赖分析
依赖分析主要是由internal/load和src/go/build两个包实现,涉及到三个函数:
- Import:分析go源文件,确定依赖关系。这一步涉及到语法解析。
- load:根据包依赖关系,递归导入所依赖的包。
- loadImport:先import,再load。
函数的调用关系如下图所示:
3.1 Import函数分析:
3.1.1 首先查找包。下图所示代码在GOPATH中查找包。
for
3.1.2 然后确定src/pkg/bin等目录
Found
3.1.3 最后解析源文件,确定依赖关系
for
3.2 load函数分析
3.2.1 首先填充package信息,包括名称,路径等
p
3.2.2 然后递归地进行依赖分析
importPaths包括依赖包的导入路径,p.Internal.Imports包括依赖包的Package对象。LoadImport函数内部会调用loadImport函数。
imports
总结下:经过以上的分析后,最终可以得到如下所示的依赖关系
四 根据依赖关系构建编程规则
main包由LinkAction函数处理。下面的代码片段显示会根据main包的依赖关系再调用CompileAction函数构建编译规则。
a
下图的CompileAction片段显示根据p.Internal.Imports中存在的依赖关系对象进行递归构建。
a
五 调用compile编译源码
work包根据指定的编译规则进行编译
5.1 确定源代码,并调用gc函数
下面的代码片段显示从package对象中提出go, cxx等源文件名
gofiles
5.2 gc函数会准备好相应的参数,并调用runOut函数
args
5.3 runOut函数依赖exec包创建子进程,执行compile命令
cmdline
那么最后子进行执行compile命令类似于下面这样的代码:
link命令类似于下面这样的代码: