包:根据包的ImportPath确定引用名称
如果是顶级路径:log, fmt等,则直接使用其本身作为包名
非顶级路径:如果没有使用go模块,则直接使用路径分割的最后一个,如果;如果使用go模块(> go1.11), 则如果最后一个是v2,v3,… v10 (v+有效数字, 除了v0,v1,v05), 则判定是版本,使用倒数第二个路径元素作为引入名称。
加载模块
loader.resolveMissingImports
buildList
一个全局对象,包含所有需要构建的模块
queryImport
queryImport
func queryImport(ctx context.Context, path string) (module.Version, error) {
}
Init
go mod initfindModuleRootGO111MODULEonoffautoauto
$GOPATH/go.mod
cfg.ModulesEnabled = true
findModuleRoot
LoadPackages
LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string)
Lookup
检查模块冲突
buildList
cmd/go/internal/cfg
-a-o
cmd/go/internal/work
go build
-work-debug-actiongraph=graph.jsongraph.json
go build
work.runBuild()
-> BuildInit()
-> modload.Init()
load.PackagesForBuild(args)
-> load.PackagesAndErros(args)
-> 将命令行的patterns转换成初始的packages
-> 预加载通过Context.Import和Context.ImportDir来获取所有的Import依赖(递归进行,go协程,最大并发数=CPU数,见下面的介绍)
work.CompileAction(mainPackage) // 加载main包,构建依赖树,从依赖为0的包开始编译,直到最终编译完主包(main)
load.PackagesForBuild(args)
并发加载pkg信息的过程
首先准备一个大小为N的并发控制管道,每次获得一个管道占用的机会,就启动一个协程加载包信息,加载完成后释放管道。然后对引用的所有包,递归执行这个过程。
由于所有子包的加载都在协程中进行,所以假如所有的初始包都能获得管道占用的机会,那么这个过程就不再阻塞,但是此时依赖包、依赖包的依赖包可能都还没有加载完成,因为协程还在递归产生更多协程。
加载的包将会保存在缓存中。
如何检测import环?
LoadImportPackage.Internal.Imports
Package.Internal.ImportsPackage.Internal.Imports
CompileAction
假定有包的依赖关系:
main
|-->a
| |-->b
|
|-->c
|-->a
Action结构如下:
type Action struct {
Mode string // description of action operation
Package *load.Package // the package this action works on
Deps []*Action // actions that must happen before this one
Func func(*Builder, context.Context, *Action) error // the action itself (nil = no-op)
Args []string // additional args for runProgram
triggers []*Action // inverse of deps
// Generated files, directories.
Objdir string // directory for intermediate objects
Target string // goal of the action: the created package or executable
built string // the actual created package or executable
actionID cache.ActionID // cache ID of action input
buildID string // build ID of action output
// Execution state.
pending int // number of deps yet to complete
priority int // relative execution priority
Failed bool // whether the action failed
//...
}
Depstriggers
work.Builder.Do()Action
-debug-actiongraph=graph.json
unsafebuiltin
构建过程:首先根据Imports从main节点构造出Action的依赖树,然后深度遍历转换成列表,使用一个同样大小的channel来记录待完成的任务,根据Deps构造triggers逆反关系,依赖数量使用pending记录。
Actiontriggerspendingpending==0main.pending==0
核心编译过程
cmd/go/internal/workBuilder.buildgo tool compile
每个包的Action都会使用所有的编译参数来生成一个唯一的Hash, 包括本次编译涉及的所有源文件内容和标志等。
go tool compileobjdirobjdir/_pkg_.agoccasmc.o
go tool packobjdir/_pkg_.apack.a.ogo tool packar
Build-Cache
-workBuild-CacheBuild-Cacheobjdir/_pkg_.a
编译缓存
Action.Hashgo-build~/Library/Caches/go-build/4b/4bb6ff70820dec2d60de038c0e2a6f264995f0f233d945672f7f2dd1778646bb-d
Build-Cachearmainlink
go compile
go build
-work
go build somefile.go
go build *.go*.gogo build "*.go"
go buildgo build init.go main.gocfg.BuildO = initgo build main.go init.gocfg.BuildO = main
go build
go build .
go build some
./../
allallallGOPATH
...
- 名称为testdata
- 含有go.mod(也就是说不会递归导入模块)
- 不含有go文件的空目录(因为不是一个go包,go包至少含有一个go文件)
单个包和多个包构建的区别
go build somego build-ocfg.BuildO.alink
mainlinkWorkWORK-work
linkbuildbuildBuild-Cachelinklinkbuild
build actionlink actionbuild actionbuild actionBuild-Cachebuild action
go build -o dir/
-o/main
cmd/go/internal/imports
构建标签(buildtags)
loadTags(), 首先会把 GOOS,GOARCH, Compiler(值:“gc”, “gccgo”) 作为tag传入,根据环境变量注入"cgo"标签,然后是命令行指定的标签
BuildID
BuildIDActionID + "/" + ActionIDAction.TargetBuildIDActionActionID + "/"
go/build
Context
BuildContext 定义了默认的Context
Context结构,定义了构建的上下文,build tags, 并提供了几个可以定制的函数,如果没有赋值使用默认实现。
// A Context specifies the supporting context for a build.
type Context struct {
GOARCH string // target architecture
GOOS string // target operating system
GOROOT string // Go root
GOPATH string // Go path
// Dir is the caller's working directory, or the empty string to use
// the current directory of the running process. In module mode, this is used
// to locate the main module.
//
// If Dir is non-empty, directories passed to Import and ImportDir must
// be absolute.
Dir string
CgoEnabled bool // whether cgo files are included
UseAllFiles bool // use files regardless of +build lines, file names
Compiler string // compiler to assume when computing target paths
// The build and release tags specify build constraints
// that should be considered satisfied when processing +build lines.
// Clients creating a new context may customize BuildTags, which
// defaults to empty, but it is usually an error to customize ReleaseTags,
// which defaults to the list of Go releases the current release is compatible with.
// BuildTags is not set for the Default build Context.
// In addition to the BuildTags and ReleaseTags, build constraints
// consider the values of GOARCH and GOOS as satisfied tags.
// The last element in ReleaseTags is assumed to be the current release.
BuildTags []string
ReleaseTags []string
// The install suffix specifies a suffix to use in the name of the installation
// directory. By default it is empty, but custom builds that need to keep
// their outputs separate can set InstallSuffix to do so. For example, when
// using the race detector, the go command uses InstallSuffix = "race", so
// that on a Linux/386 system, packages are written to a directory named
// "linux_386_race" instead of the usual "linux_386".
InstallSuffix string
// By default, Import uses the operating system's file system calls
// to read directories and files. To read from other sources,
// callers can set the following functions. They all have default
// behaviors that use the local file system, so clients need only set
// the functions whose behaviors they wish to change.
// JoinPath joins the sequence of path fragments into a single path.
// If JoinPath is nil, Import uses filepath.Join.
JoinPath func(elem ...string) string
// SplitPathList splits the path list into a slice of individual paths.
// If SplitPathList is nil, Import uses filepath.SplitList.
SplitPathList func(list string) []string
// IsAbsPath reports whether path is an absolute path.
// If IsAbsPath is nil, Import uses filepath.IsAbs.
IsAbsPath func(path string) bool
// IsDir reports whether the path names a directory.
// If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
IsDir func(path string) bool
// HasSubdir reports whether dir is lexically a subdirectory of
// root, perhaps multiple levels below. It does not try to check
// whether dir exists.
// If so, HasSubdir sets rel to a slash-separated path that
// can be joined to root to produce a path equivalent to dir.
// If HasSubdir is nil, Import uses an implementation built on
// filepath.EvalSymlinks.
HasSubdir func(root, dir string) (rel string, ok bool)
// ReadDir returns a slice of fs.FileInfo, sorted by Name,
// describing the content of the named directory.
// If ReadDir is nil, Import uses ioutil.ReadDir.
ReadDir func(dir string) ([]fs.FileInfo, error)
// OpenFile opens a file (not a directory) for reading.
// If OpenFile is nil, Import uses os.Open.
OpenFile func(path string) (io.ReadCloser, error)
}
Context.Import
Context.Importgo list -e -- path-e
go tool compile
支持的命令行标志
-+-std-d strgo tool compile -d help-packfile.afile.o-trimpath-p pkg
go build
go tool compile -o $WORK/b001/_pkg_.a -trimpath $WORK/b001=> -p main -lang=go1.13 -complete -
buildid 9_0557aJwaAvYDCxLHSA/9_0557aJwaAvYDCxLHSA -D -importcfg $WORK/b001/importcfg -pack -c=4 ./init.go ./main.go $WORK/b001/_gomod_.go
初始化工作
gc.Maingo.builtinunsaferuntime
localpkg = types.NewPkg("", "")
localpkg.Prefix = "\"\""
// We won't know localpkg's height until after import
// processing. In the mean time, set to MaxPkgHeight to ensure
// height comparisons at least work until then.
localpkg.Height = types.MaxPkgHeight
// pseudo-package, for scoping
builtinpkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin?
builtinpkg.Prefix = "go.builtin" // not go%2ebuiltin
// pseudo-package, accessed by import "unsafe"
unsafepkg = types.NewPkg("unsafe", "unsafe")
// Pseudo-package that contains the compiler's builtin
// declarations for package runtime. These are declared in a
// separate package to avoid conflicts with package runtime's
// actual declarations, which may differ intentionally but
// insignificantly.
Runtimepkg = types.NewPkg("go.runtime", "runtime")
Runtimepkg.Prefix = "runtime"
// pseudo-packages used in symbol tables
itabpkg = types.NewPkg("go.itab", "go.itab")
itabpkg.Prefix = "go.itab" // not go%2eitab
itablinkpkg = types.NewPkg("go.itablink", "go.itablink")
itablinkpkg.Prefix = "go.itablink" // not go%2eitablink
trackpkg = types.NewPkg("go.track", "go.track")
trackpkg.Prefix = "go.track" // not go%2etrack
// pseudo-package used for map zero values
mappkg = types.NewPkg("go.map", "go.map")
mappkg.Prefix = "go.map"
// pseudo-package used for methods with anonymous receivers
gopkg = types.NewPkg("go", "")
-importcfg
读取包名到已编译的文件对象的映射,后续的编译只需要使用这个map来确定编译好的文件。
该文件内容格式:
packagefile name=path
...
importmap alias=name
....
initUniverse
intdoubleerrorbyteruneunsafe.Pointer
builtinPointerunsafe
因此实际上这些包是不会专门去编译生成的。
parseFiles(files)
*syntax.File*Nodedeclsxtop
import语句.a.o
resolve
intiota*NoderesolveOp==ONONAME
const (
C0 int = iota // int和iota是未解析的
C1
)
var a NONE // NONE是未解析的
typecheck
调用类型检查
resolveOp==ONONAME:: undefined:
deadcode
无用代码是什么意思呢?举个例子:
a && b 如果a=true, 则简化为b
如果a=false, 则简化为a
a || b 如果a=true,则简化为a
如果a=false,则简化为b
if a {
left
}else{
right
}
如果a=true则简化为left,a=false则简化为right
if a {
...
panic or return
}
...
如果a=true,且if或else的语句以panic或return结束, 则if后面的所有语句都可以消除
fninit(nodes)
init
遍历语法树,构建出变量初始化依赖图。如果存在环状依赖,则报错
var g = b
var b = d
var d = g
cycleinit.go:5:5: typechecking loop involving g = b
cycleinit.go:5:5 g
cycleinit.go:8:5 d = g
cycleinit.go:8:5 d
cycleinit.go:6:5 b = d
cycleinit.go:6:5 b
cycleinit.go:5:5 g = b
capturevars(fn)
变量捕获分析(值捕获或引用捕获)
typecheckinl
内联
escapes(xtop)
逃逸分析
transformclosure
闭包转换
initssaconfigcompileFunctions
ssa编译和pass优化
typecheck(external)
外部依赖分析
GOSSAFUNCGOSSADIR
GOSSAFUNC
GOSSADIR$GOSSADIR/$importPath/$funcName.html
compile
go tool compile -o tmp/moduleworld.a -p main -lang=go1.13 -complete *.go
syntax.ParseFile
ParseFilego tool compile