前言
go build
go build使用
对指定文件或文件夹编译时的命令如下:
go build [-o output] [-i] [build flags] [packages]
Go支持交叉编译,可以跨平台编译,如在mac平台编译在linux平台运行的包。
如需要交叉编译,可以在go build前添加目标平台、平台架构环境变量参数,如下命令:
GOOS=linux GOARCH=amd64 go build [-o output] [-i] [build flags] [packages]
GOOS、GOARCH是通过改变环境变量,实现指定平台相关信息的,这些环境变量会在正式编译期获取。
go build溯源
compile packages and dependencies
go tool
go命令对应的源码入口在/cmd/go/main.go文件中,命令的入口为main func,相关的说明在了解Go编译处理(一)—— go tool一文中已做说明,本文不再赘述。
func init() {
base.Go.Commands = []*base.Command{
...
work.CmdBuild,
...
}
}
build
CmdBuild
work.CmdBuildinstallCmdInstall
声明
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,
See also: go install, go get, go clean.
`,
}
const concurrentGCBackendCompilationEnabledByDefault = true
CmdBuild是base.Command的实例,包含了对usage的说明。
init
build.go中存在两个init func。
-i-o
func init() {
// break init cycle
CmdBuild.Run = runBuild
CmdInstall.Run = runInstall
CmdBuild.Flag.BoolVar(&cfg.BuildI, "i", false, "")
CmdBuild.Flag.StringVar(&cfg.BuildO, "o", "", "output file or directory")
CmdInstall.Flag.BoolVar(&cfg.BuildI, "i", false, "")
AddBuildFlags(CmdBuild, DefaultBuildFlags)
AddBuildFlags(CmdInstall, DefaultBuildFlags)
}
第二个init func需要重点说明下。
const Compiler = "gc"
func init() {
switch build.Default.Compiler {
case "gc", "gccgo":
buildCompiler{}.Set(build.Default.Compiler)
}
}
var Default Context = defaultContext()
func defaultContext() Context {
var c Context
c.GOARCH = envOr("GOARCH", runtime.GOARCH)
c.GOOS = envOr("GOOS", runtime.GOOS)
c.GOROOT = pathpkg.Clean(runtime.GOROOT())
c.GOPATH = envOr("GOPATH", defaultGOPATH())
c.Compiler = runtime.Compiler
// Each major Go release in the Go 1.x series adds a new
// "go1.x" release tag. That is, the go1.x tag is present in
// all releases >= Go 1.x. Code that requires Go 1.x or later
// should say "+build go1.x", and code that should only be
// built before Go 1.x (perhaps it is the stub to use in that
// case) should say "+build !go1.x".
// The last element in ReleaseTags is the current release.
for i := 1; i <= goversion.Version; i++ {
c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i))
}
defaultReleaseTags = append([]string{}, c.ReleaseTags...) // our own private copy
env := os.Getenv("CGO_ENABLED")
if env == "" {
env = defaultCGO_ENABLED
}
switch env {
case "1":
c.CgoEnabled = true
case "0":
c.CgoEnabled = false
default:
// cgo must be explicitly enabled for cross compilation builds
if runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS {
c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH]
break
}
c.CgoEnabled = false
}
return c
}
type buildCompiler struct{}
func (c buildCompiler) Set(value string) error {
switch value {
case "gc":
BuildToolchain = gcToolchain{}
case "gccgo":
BuildToolchain = gccgoToolchain{}
default:
return fmt.Errorf("unknown compiler %q", value)
}
cfg.BuildToolchainName = value
cfg.BuildToolchainCompiler = BuildToolchain.compiler
cfg.BuildToolchainLinker = BuildToolchain.linker
cfg.BuildContext.Compiler = value
return nil
}
"gc"
runBuild
具体的执行func。
func runBuild(cmd *base.Command, args []string) {
BuildInit()//对mod、instrument、buildMode进行初始化及检查,最后对输出编译包的路径进行检查。
var b Builder
b.Init()//对于编译缓存参数的初始化,对工作目录、GOOS-ARCH及tag进行检查。
pkgs := load.PackagesForBuild(args)//检查所有的package,若有错误则不能编译
explicitO := len(cfg.BuildO) > 0 //指定输出路径
if len(pkgs) == 1 && pkgs[0].Name == "main" && cfg.BuildO == "" {
cfg.BuildO = pkgs[0].DefaultExecName()//默认执行文件名
cfg.BuildO += cfg.ExeSuffix//执行文件后缀
}
// sanity check some often mis-used options
switch cfg.BuildContext.Compiler {//健全检查一些经常误用的选项
case "gccgo":
if load.BuildGcflags.Present() {
fmt.Println("go build: when using gccgo toolchain, please pass compiler flags using -gccgoflags, not -gcflags")
}
if load.BuildLdflags.Present() {
fmt.Println("go build: when using gccgo toolchain, please pass linker flags using -gccgoflags, not -ldflags")
}
case "gc":
if load.BuildGccgoflags.Present() {
fmt.Println("go build: when using gc toolchain, please pass compile flags using -gcflags, and linker flags using -ldflags")
}
}
depMode := ModeBuild
if cfg.BuildI {//对install的处理
depMode = ModeInstall
}
pkgs = omitTestOnly(pkgsFilter(load.Packages(args)))//忽略仅测试的package
// Special case -o /dev/null by not writing at all.
if cfg.BuildO == os.DevNull {//指针特殊输出路径的处理
cfg.BuildO = ""
}
if cfg.BuildO != "" {//针对输出路径的处理
// If the -o name exists and is a directory, then
// write all main packages to that directory.
// Otherwise require only a single package be built.
if fi, err := os.Stat(cfg.BuildO); err == nil && fi.IsDir() {//输出路径已存在且是文件夹
if !explicitO {
base.Fatalf("go build: build output %q already exists and is a directory", cfg.BuildO)
}
a := &Action{Mode: "go build"}//指定Action的Mode为build
for _, p := range pkgs {
if p.Name != "main" {//build的package需从main package开始
continue
}
//目标packag的参数设置
p.Target = filepath.Join(cfg.BuildO, p.DefaultExecName())
p.Target += cfg.ExeSuffix
p.Stale = true
p.StaleReason = "build -o flag in use"
a.Deps = append(a.Deps, b.AutoAction(ModeInstall, depMode, p))//记录针对所有package的编译操作
}
if len(a.Deps) == 0 {//没有main则报错
base.Fatalf("go build: no main packages to build")
}
//根据action进行操作
b.Do(a)
return
}
if len(pkgs) > 1 {//输出目录不存在或非文件夹
base.Fatalf("go build: cannot write multiple packages to non-directory %s", cfg.BuildO)
} else if len(pkgs) == 0 {//没有package
base.Fatalf("no packages to build")
}
p := pkgs[0]
p.Target = cfg.BuildO
p.Stale = true // must build - not up to date
p.StaleReason = "build -o flag in use"
a := b.AutoAction(ModeInstall, depMode, p)
b.Do(a)
return
}
a := &Action{Mode: "go build"}
for _, p := range pkgs {
a.Deps = append(a.Deps, b.AutoAction(ModeBuild, depMode, p))
}
if cfg.BuildBuildmode == "shared" {
a = b.buildmodeShared(ModeBuild, depMode, args, pkgs, a)
}
b.Do(a)
}
BuildInit主要是对mod、instrument、buildMode进行初始化及检查,最后对输出编译包的路径进行检查。
大致处理步骤:
- 进行build前的初始化操作
- 检查所有参与build的package(若不指定则以运行命令的当前文件夹为指定package)
- 确认输出路径及文件后缀
- 健全检查,避免错误使用
- 忽略仅测试的package
- 判断输出路径
- 若不为空
- 若输出路径存在且是文件夹,
- 指定Action Mode,仅从main package开始Action关联,Action中包含对每个package build的处理
- 若输出路径不存在或不是文件夹,仅能build一个package,否则报错退出
- 若输出路径存在且是文件夹,
- 若为空
- 对所有package进行Action关联
- 执行Action
AutoAction
// AutoAction returns the "right" action for go build or go install of p.
func (b *Builder) AutoAction(mode, depMode BuildMode, p *load.Package) *Action {
if p.Name == "main" {
return b.LinkAction(mode, depMode, p)//LinkAction也会调用CompileAction
}
return b.CompileAction(mode, depMode, p)
}
func (b *Builder) LinkAction(mode, depMode BuildMode, p *load.Package) *Action {
// Construct link action.
a := b.cacheAction("link", p, func() *Action {
a := &Action{
Mode: "link",
Package: p,
}
a1 := b.CompileAction(ModeBuild, depMode, p)//与非
a.Func = (*Builder).link//绑定action的执行func为link
a.Deps = []*Action{a1}
a.Objdir = a1.Objdir
// An executable file. (This is the name of a temporary file.)
// Because we run the temporary file in 'go run' and 'go test',
// the name will show up in ps listings. If the caller has specified
// a name, use that instead of a.out. The binary is generated
// in an otherwise empty subdirectory named exe to avoid
// naming conflicts. The only possible conflict is if we were
// to create a top-level package named exe.
name := "a.out"
if p.Internal.ExeName != "" {
name = p.Internal.ExeName
} else if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" && p.Target != "" {
// On OS X, the linker output name gets recorded in the
// shared library's LC_ID_DYLIB load command.
// The code invoking the linker knows to pass only the final
// path element. Arrange that the path element matches what
// we'll install it as; otherwise the library is only loadable as "a.out".
// On Windows, DLL file name is recorded in PE file
// export section, so do like on OS X.
_, name = filepath.Split(p.Target)
}
a.Target = a.Objdir + filepath.Join("exe", name) + cfg.ExeSuffix
a.built = a.Target
b.addTransitiveLinkDeps(a, a1, "")
// Sequence the build of the main package (a1) strictly after the build
// of all other dependencies that go into the link. It is likely to be after
// them anyway, but just make sure. This is required by the build ID-based
// shortcut in (*Builder).useCache(a1), which will call b.linkActionID(a).
// In order for that linkActionID call to compute the right action ID, all the
// dependencies of a (except a1) must have completed building and have
// recorded their build IDs.
a1.Deps = append(a1.Deps, &Action{Mode: "nop", Deps: a.Deps[1:]})
return a
})
if mode == ModeInstall || mode == ModeBuggyInstall {
a = b.installAction(a, mode)
}
return a
}
main package负责链接各非main package。
CompileAction
func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Action {
vetOnly := mode&ModeVetOnly != 0
mode &^= ModeVetOnly
if mode != ModeBuild && (p.Internal.Local || p.Module != nil) && p.Target == "" {
// Imported via local path or using modules. No permanent target.
mode = ModeBuild//导入本地路径或modules的处理
}
if mode != ModeBuild && p.Name == "main" {
// We never install the .a file for a main package.
mode = ModeBuild//对main package的处理
}
// Construct package build action.
a := b.cacheAction("build", p, func() *Action {
a := &Action{
Mode: "build",
Package: p,
Func: (*Builder).build,//绑定action的执行func为build
Objdir: b.NewObjdir(),
}
if p.Error == nil || !p.Error.IsImportCycle {
for _, p1 := range p.Internal.Imports {//依赖包的处理
a.Deps = append(a.Deps, b.CompileAction(depMode, depMode, p1))
}
}
if p.Standard {//go标准包的处理
switch p.ImportPath {
case "builtin", "unsafe"://特殊包的处理,builtin、unsafe不编译
// Fake packages - nothing to build.
a.Mode = "built-in package"
a.Func = nil
return a
}
// gccgo standard library is "fake" too.
if cfg.BuildToolchainName == "gccgo" {//gccgo模式下的特殊处理
// the target name is needed for cgo.
a.Mode = "gccgo stdlib"
a.Target = p.Target
a.Func = nil
return a
}
}
return a
})
// Find the build action; the cache entry may have been replaced
// by the install action during (*Builder).installAction.
buildAction := a
switch buildAction.Mode {
case "build", "built-in package", "gccgo stdlib":
// ok
case "build-install":
buildAction = a.Deps[0]
default:
panic("lost build action: " + buildAction.Mode)
}
buildAction.needBuild = buildAction.needBuild || !vetOnly
// Construct install action.
if mode == ModeInstall || mode == ModeBuggyInstall {//对于install的处理
a = b.installAction(a, mode)
}
return a
}
CompileAction获取当前package p的Action,Action封装了处理的相关信息。若p是标准包,则其处理func为nil,即不处理。若封装后的Action,其处理会在Do中进行。
Do
Action的执行过程,注释已添加至代码中。
func (b *Builder) Do(root *Action) {
if !b.IsCmdList {
// If we're doing real work, take time at the end to trim the cache.
c := cache.Default()
defer c.Trim()
}
all := actionList(root)//将root的deps转化为slice
for i, a := range all {
a.priority = i//设置优先级为顺序
}
// Write action graph, without timing information, in case we fail and exit early.
writeActionGraph := func() {
if file := cfg.DebugActiongraph; file != "" {
if strings.HasSuffix(file, ".go") {
// Do not overwrite Go source code in:
// go build -debug-actiongraph x.go
base.Fatalf("go: refusing to write action graph to %v\n", file)
}
js := actionGraphJSON(root)//转化为json
if err := ioutil.WriteFile(file, []byte(js), 0666); err != nil {
fmt.Fprintf(os.Stderr, "go: writing action graph: %v\n", err)
base.SetExitStatus(1)
}
}
}
writeActionGraph()//如果开启了DebugActiongraph,则记录action graph到文件中,此时不带时间信息
b.readySema = make(chan bool, len(all))
// Initialize per-action execution state.
// 初始化每个action的执行状态
for _, a := range all {
for _, a1 := range a.Deps {
a1.triggers = append(a1.triggers, a)
}
a.pending = len(a.Deps)
if a.pending == 0 {
b.ready.push(a)//以栈的形式存入,形成编译依赖链
b.readySema <- true
}
}
// Handle runs a single action and takes care of triggering
// any actions that are runnable as a result.
handle := func(a *Action) {//针对单个action的处理
if a.json != nil {//针对DebugActiongraph的处理,添加时间
a.json.TimeStart = time.Now()
}
var err error
if a.Func != nil && (!a.Failed || a.IgnoreFail) {
err = a.Func(b, a)//执行action绑定的操作
}
if a.json != nil {
a.json.TimeDone = time.Now()
}
// The actions run in parallel but all the updates to the
// shared work state are serialized through b.exec.
b.exec.Lock()
defer b.exec.Unlock()
if err != nil {//发生错误
if err == errPrintedOutput {
base.SetExitStatus(2)
} else {
base.Errorf("%s", err)
}
a.Failed = true
}
for _, a0 := range a.triggers {
if a.Failed {
a0.Failed = true
}
if a0.pending--; a0.pending == 0 {
b.ready.push(a0)
b.readySema <- true//完成
}
}
if a == root {//完全完成
close(b.readySema)
}
}
var wg sync.WaitGroup
// Kick off goroutines according to parallelism.
// If we are using the -n flag (just printing commands)
// drop the parallelism to 1, both to make the output
// deterministic and because there is no real work anyway.
par := cfg.BuildP
if cfg.BuildN {
par = 1
}
for i := 0; i < par; i++ {
wg.Add(1)
go func() {//并发执行
defer wg.Done()
for {
select {
case _, ok := <-b.readySema:
if !ok {//完成结束
return
}
// Receiving a value from b.readySema entitles
// us to take from the ready queue.
b.exec.Lock()
a := b.ready.pop()//移出最后的action,优先级最大
b.exec.Unlock()
handle(a)//执行对单个action的处理
case <-base.Interrupted://命令强制结束
base.SetExitStatus(1)
return
}
}
}()
}
wg.Wait()
// Write action graph again, this time with timing information.
writeActionGraph()//如果开启了DebugActiongraph,则记录action graph到文件中,此时带时间信息,记录了action的开始及完成时间
}
Do中是以并发执行这样可以提高执行的效率。
build
.go文件
// build is the action for building a single package.
// Note that any new influence on this logic must be reported in b.buildActionID above as well.
func (b *Builder) build(a *Action) (err error) {
p := a.Package
bit := func(x uint32, b bool) uint32 {
if b {
return x
}
return 0
}
cachedBuild := false
//正常编译时need为needBuild,即1
need := bit(needBuild, !b.IsCmdList && a.needBuild || b.NeedExport) |
bit(needCgoHdr, b.needCgoHdr(a)) |
bit(needVet, a.needVet) |
bit(needCompiledGoFiles, b.NeedCompiledGoFiles)
if !p.BinaryOnly {//可以重编译的package
if b.useCache(a, b.buildActionID(a), p.Target) {//如果存在缓存可用
// We found the main output in the cache.
// If we don't need any other outputs, we can stop.
// Otherwise, we need to write files to a.Objdir (needVet, needCgoHdr).
// Remember that we might have them in cache
// and check again after we create a.Objdir.
cachedBuild = true
a.output = []byte{} // start saving output in case we miss any cache results
need &^= needBuild
if b.NeedExport {
p.Export = a.built
}
if need&needCompiledGoFiles != 0 {
if err := b.loadCachedSrcFiles(a); err == nil {//加载缓存
need &^= needCompiledGoFiles
}
}
}
// Source files might be cached, even if the full action is not
// (e.g., go list -compiled -find).
// 源文件缓存的处理
if !cachedBuild && need&needCompiledGoFiles != 0 {
if err := b.loadCachedSrcFiles(a); err == nil {
need &^= needCompiledGoFiles
}
}
if need == 0 {
return nil
}
defer b.flushOutput(a)//将缓存的结果写入b中
}
defer func() {//针对错误的处理
if err != nil && err != errPrintedOutput {
err = fmt.Errorf("go build %s: %v", a.Package.ImportPath, err)
}
if err != nil && b.IsCmdList && b.NeedError && p.Error == nil {
p.Error = &load.PackageError{Err: err}
}
}()
if cfg.BuildN {
// In -n mode, print a banner between packages.
// The banner is five lines so that when changes to
// different sections of the bootstrap script have to
// be merged, the banners give patch something
// to use to find its context.
b.Print("\n#\n# " + a.Package.ImportPath + "\n#\n\n")
}
if cfg.BuildV {
b.Print(a.Package.ImportPath + "\n")
}
//不能重编译的package报错
if a.Package.BinaryOnly {
p.Stale = true
p.StaleReason = "binary-only packages are no longer supported"
if b.IsCmdList {
return nil
}
return errors.New("binary-only packages are no longer supported")
}
//创建中间编译文件的目录
if err := b.Mkdir(a.Objdir); err != nil {
return err
}
objdir := a.Objdir
// Load cached cgo header, but only if we're skipping the main build (cachedBuild==true).
if cachedBuild && need&needCgoHdr != 0 {
if err := b.loadCachedCgoHdr(a); err == nil {
need &^= needCgoHdr
}
}
// Load cached vet config, but only if that's all we have left
// (need == needVet, not testing just the one bit).
// If we are going to do a full build anyway,
// we're going to regenerate the files below anyway.
if need == needVet {//针对vet的处理
if err := b.loadCachedVet(a); err == nil {
need &^= needVet
}
}
if need == 0 {
return nil
}
// make target directory
// 创建package编译的目标文件的目录
dir, _ := filepath.Split(a.Target)
if dir != "" {
if err := b.Mkdir(dir); err != nil {
return err
}
}
gofiles := str.StringList(a.Package.GoFiles)//.go文件
cgofiles := str.StringList(a.Package.CgoFiles)//导入C的.go文件
cfiles := str.StringList(a.Package.CFiles)//.c文件
sfiles := str.StringList(a.Package.SFiles)//.s文件
cxxfiles := str.StringList(a.Package.CXXFiles)//.cc, .cpp and .cxx文件
var objects, cgoObjects, pcCFLAGS, pcLDFLAGS []string
//对包含c或swig的处理
if a.Package.UsesCgo() || a.Package.UsesSwig() {
if pcCFLAGS, pcLDFLAGS, err = b.getPkgConfigFlags(a.Package); err != nil {
return
}
}
// Run SWIG on each .swig and .swigcxx file.
// Each run will generate two files, a .go file and a .c or .cxx file.
// The .go file will use import "C" and is to be processed by cgo.
if a.Package.UsesSwig() {
outGo, outC, outCXX, err := b.swig(a, a.Package, objdir, pcCFLAGS)
if err != nil {
return err
}
cgofiles = append(cgofiles, outGo...)
cfiles = append(cfiles, outC...)
cxxfiles = append(cxxfiles, outCXX...)
}
// If we're doing coverage, preprocess the .go files and put them in the work directory
// cover模式
if a.Package.Internal.CoverMode != "" {
for i, file := range str.StringList(gofiles, cgofiles) {
var sourceFile string
var coverFile string
var key string
if strings.HasSuffix(file, ".cgo1.go") {
// cgo files have absolute paths
base := filepath.Base(file)
sourceFile = file
coverFile = objdir + base
key = strings.TrimSuffix(base, ".cgo1.go") + ".go"
} else {
sourceFile = filepath.Join(a.Package.Dir, file)
coverFile = objdir + file
key = file
}
coverFile = strings.TrimSuffix(coverFile, ".go") + ".cover.go"
cover := a.Package.Internal.CoverVars[key]
if cover == nil || base.IsTestFile(file) {
// Not covering this file.
continue
}
if err := b.cover(a, coverFile, sourceFile, cover.Var); err != nil {
return err
}
if i < len(gofiles) {
gofiles[i] = coverFile
} else {
cgofiles[i-len(gofiles)] = coverFile
}
}
}
// Run cgo.
if a.Package.UsesCgo() || a.Package.UsesSwig() {
// In a package using cgo, cgo compiles the C, C++ and assembly files with gcc.
// There is one exception: runtime/cgo's job is to bridge the
// cgo and non-cgo worlds, so it necessarily has files in both.
// In that case gcc only gets the gcc_* files.
var gccfiles []string
gccfiles = append(gccfiles, cfiles...)
cfiles = nil
if a.Package.Standard && a.Package.ImportPath == "runtime/cgo" {
filter := func(files, nongcc, gcc []string) ([]string, []string) {
for _, f := range files {
if strings.HasPrefix(f, "gcc_") {
gcc = append(gcc, f)
} else {
nongcc = append(nongcc, f)
}
}
return nongcc, gcc
}
sfiles, gccfiles = filter(sfiles, sfiles[:0], gccfiles)
} else {
for _, sfile := range sfiles {
data, err := ioutil.ReadFile(filepath.Join(a.Package.Dir, sfile))
if err == nil {
if bytes.HasPrefix(data, []byte("TEXT")) || bytes.Contains(data, []byte("\nTEXT")) ||
bytes.HasPrefix(data, []byte("DATA")) || bytes.Contains(data, []byte("\nDATA")) ||
bytes.HasPrefix(data, []byte("GLOBL")) || bytes.Contains(data, []byte("\nGLOBL")) {
return fmt.Errorf("package using cgo has Go assembly file %s", sfile)
}
}
}
gccfiles = append(gccfiles, sfiles...)
sfiles = nil
}
outGo, outObj, err := b.cgo(a, base.Tool("cgo"), objdir, pcCFLAGS, pcLDFLAGS, mkAbsFiles(a.Package.Dir, cgofiles), gccfiles, cxxfiles, a.Package.MFiles, a.Package.FFiles)
if err != nil {
return err
}
if cfg.BuildToolchainName == "gccgo" {
cgoObjects = append(cgoObjects, a.Objdir+"_cgo_flags")
}
cgoObjects = append(cgoObjects, outObj...)
gofiles = append(gofiles, outGo...)
switch cfg.BuildBuildmode {
case "c-archive", "c-shared":
b.cacheCgoHdr(a)
}
}
var srcfiles []string // .go and non-.go
srcfiles = append(srcfiles, gofiles...)
srcfiles = append(srcfiles, sfiles...)
srcfiles = append(srcfiles, cfiles...)
srcfiles = append(srcfiles, cxxfiles...)
b.cacheSrcFiles(a, srcfiles)
// Running cgo generated the cgo header.
need &^= needCgoHdr
// Sanity check only, since Package.load already checked as well.
if len(gofiles) == 0 {
return &load.NoGoError{Package: a.Package}
}
// Prepare Go vet config if needed.
if need&needVet != 0 {
buildVetConfig(a, srcfiles)
need &^= needVet
}
if need&needCompiledGoFiles != 0 {
if err := b.loadCachedSrcFiles(a); err != nil {
return fmt.Errorf("loading compiled Go files from cache: %w", err)
}
need &^= needCompiledGoFiles
}
if need == 0 {
// Nothing left to do.
return nil
}
// Collect symbol ABI requirements from assembly.
// 从.s文件中获取ABI
symabis, err := BuildToolchain.symabis(b, a, sfiles)
if err != nil {
return err
}
// Prepare Go import config.
// We start it off with a comment so it can't be empty, so icfg.Bytes() below is never nil.
// It should never be empty anyway, but there have been bugs in the past that resulted
// in empty configs, which then unfortunately turn into "no config passed to compiler",
// and the compiler falls back to looking in pkg itself, which mostly works,
// except when it doesn't.
var icfg bytes.Buffer
fmt.Fprintf(&icfg, "# import config\n")
for i, raw := range a.Package.Internal.RawImports {
final := a.Package.Imports[i]
if final != raw {
fmt.Fprintf(&icfg, "importmap %s=%s\n", raw, final)
}
}
for _, a1 := range a.Deps {
p1 := a1.Package
if p1 == nil || p1.ImportPath == "" || a1.built == "" {
continue
}
fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, a1.built)
}
if p.Internal.BuildInfo != "" && cfg.ModulesEnabled {
if err := b.writeFile(objdir+"_gomod_.go", load.ModInfoProg(p.Internal.BuildInfo, cfg.BuildToolchainName == "gccgo")); err != nil {
return err
}
gofiles = append(gofiles, objdir+"_gomod_.go")
}
// Compile Go.
// 编译go文件,
objpkg := objdir + "_pkg_.a"
// 具体go的编译
ofile, out, err := BuildToolchain.gc(b, a, objpkg, icfg.Bytes(), symabis, len(sfiles) > 0, gofiles)
if len(out) > 0 {
output := b.processOutput(out)
if p.Module != nil && !allowedVersion(p.Module.GoVersion) {
output += "note: module requires Go " + p.Module.GoVersion + "\n"
}
b.showOutput(a, a.Package.Dir, a.Package.Desc(), output)
if err != nil {
return errPrintedOutput
}
}
if err != nil {
if p.Module != nil && !allowedVersion(p.Module.GoVersion) {
b.showOutput(a, a.Package.Dir, a.Package.Desc(), "note: module requires Go "+p.Module.GoVersion)
}
return err
}
if ofile != objpkg {
objects = append(objects, ofile)
}
// Copy .h files named for goos or goarch or goos_goarch
// to names using GOOS and GOARCH.
// For example, defs_linux_amd64.h becomes defs_GOOS_GOARCH.h.
//拷贝.h文件
_goos_goarch := "_" + cfg.Goos + "_" + cfg.Goarch
_goos := "_" + cfg.Goos
_goarch := "_" + cfg.Goarch
for _, file := range a.Package.HFiles {
name, ext := fileExtSplit(file)
switch {
case strings.HasSuffix(name, _goos_goarch):
targ := file[:len(name)-len(_goos_goarch)] + "_GOOS_GOARCH." + ext
if err := b.copyFile(objdir+targ, filepath.Join(a.Package.Dir, file), 0666, true); err != nil {
return err
}
case strings.HasSuffix(name, _goarch):
targ := file[:len(name)-len(_goarch)] + "_GOARCH." + ext
if err := b.copyFile(objdir+targ, filepath.Join(a.Package.Dir, file), 0666, true); err != nil {
return err
}
case strings.HasSuffix(name, _goos):
targ := file[:len(name)-len(_goos)] + "_GOOS." + ext
if err := b.copyFile(objdir+targ, filepath.Join(a.Package.Dir, file), 0666, true); err != nil {
return err
}
}
}
//编译C文件,必须使用CGO模式
for _, file := range cfiles {
out := file[:len(file)-len(".c")] + ".o"
if err := BuildToolchain.cc(b, a, objdir+out, file); err != nil {
return err
}
objects = append(objects, out)
}
// Assemble .s files.
if len(sfiles) > 0 {
ofiles, err := BuildToolchain.asm(b, a, sfiles)
if err != nil {
return err
}
objects = append(objects, ofiles...)
}
// For gccgo on ELF systems, we write the build ID as an assembler file.
// This lets us set the SHF_EXCLUDE flag.
// This is read by readGccgoArchive in cmd/internal/buildid/buildid.go.
if a.buildID != "" && cfg.BuildToolchainName == "gccgo" {
switch cfg.Goos {
case "aix", "android", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris":
asmfile, err := b.gccgoBuildIDFile(a)
if err != nil {
return err
}
ofiles, err := BuildToolchain.asm(b, a, []string{asmfile})
if err != nil {
return err
}
objects = append(objects, ofiles...)
}
}
// NOTE(rsc): On Windows, it is critically important that the
// gcc-compiled objects (cgoObjects) be listed after the ordinary
// objects in the archive. I do not know why this is.
// https://golang.org/issue/2601
objects = append(objects, cgoObjects...)
// Add system object files.
for _, syso := range a.Package.SysoFiles {
objects = append(objects, filepath.Join(a.Package.Dir, syso))
}
// Pack into archive in objdir directory.
// If the Go compiler wrote an archive, we only need to add the
// object files for non-Go sources to the archive.
// If the Go compiler wrote an archive and the package is entirely
// Go sources, there is no pack to execute at all.
if len(objects) > 0 {//打包编译的文件至objpkg中
if err := BuildToolchain.pack(b, a, objpkg, objects); err != nil {
return err
}
}
//更新objpkg的buildID
if err := b.updateBuildID(a, objpkg, true); err != nil {
return err
}
//保存编译的路径
a.built = objpkg
return nil
}
build的步骤如下:
- 不能从源码重新编译package报错处理。
- 根据buidlID确认没有变更的(意味着package源文件没有发生变化),可以直接使用原编译缓存则直接使用;需要重新编译,则使用缓存源文件进行编译。
- 若无编译缓存,有缓存源文件,如需要,使用缓存源文件进行编译。
- 确认中间生成文件路径
- needCgoHdr、needVet
- 确认最终生成文件的路径
- 根据文件类型分类
- swig文件处理
- cover mode的处理,需要使用cover工具
- cgo处理,需要使用cgo工具
- 缓存所有源文件
- needVet、needCompiledGoFiles的处理
- 获取ABI,需要用到asm工具
- import处理
- 编译go文件,需要使用compile工具
- 复制.h文件
- 编译c文件
- 组装.s文件,需要使用asm工具
- 添加系统组件
- 打包至最终生成文件路径,需要使用pack工具
- 更新最终生成文件的buildID,需要使用buildid工具
- 保存最终生成文件路径
gc
此处的gc的意思是go compile。
func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) {
p := a.Package
objdir := a.Objdir
// 目标路径不存在则使用默认地址
if archive != "" {
ofile = archive
} else {
out := "_go_.o"
ofile = objdir + out
}
...
// 拼接调用compile工具的参数,compile位于$GOROOT/pkg/tool/$GOOS_$GOARCH下
args := []interface{}{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", a.trimpath(), gcflags, gcargs, "-D", p.Internal.LocalPrefix}
...
output, err = b.runOut(a, p.Dir, nil, args...)
return ofile, output, err
}
//base.Tool("compile")是获取compile的地址
func (b *Builder) runOut(a *Action, dir string, env []string, cmdargs ...interface{}) ([]byte, error) {
cmdline := str.StringList(cmdargs...)
...
var buf bytes.Buffer
cmd := exec.Command(cmdline[0], cmdline[1:]...)
cmd.Stdout = &buf
cmd.Stderr = &buf
cleanup := passLongArgsInResponseFiles(cmd)
defer cleanup()
cmd.Dir = dir
cmd.Env = base.EnvForDir(cmd.Dir, os.Environ())
cmd.Env = append(cmd.Env, env...)
start := time.Now()
err := cmd.Run()
...
return buf.Bytes(), err
}
compile
总结
go
go build
- 根据路径获取直接参与build的package(不会循环查找子目录的package),忽略仅测试的package
- 对于单个main package的未设置输出路径的设置默认路径
- 若有输出路径,则仅从main package开始进行Action的关联,Action中包含对每个package build的处理
- 若无输出路径,仅支持对单个package的处理,此此package开始关联
- 执行Action,获取action list,添加优先级,按照优先级存入栈中
- 从栈中取出action,并发执行每一个action的Func,对应Builder的build func,等待所有action的操作执行结束
- 如果缓存可用则使用缓存(直接使用缓存的编译文件或根据缓存的源文件重新编译)
- 否则,根据文件类型分类,依次对swig、c、c++进行相关的build处理,缓存所有源文件
- 处理.s、import config,编译go代码,拷贝.h,组装.s文件,添加系统组件,
- 打包更新BuildID
- 所有action执行完毕后,即可获取对应的执行文件
build的源码中并不负责代码的编译,而是交给专门的编译工具尽心编译,完整的build过程使用到以下工具:
cover-cgo-compile-asm-pack-buildid-link
部分工具因文件类型的不一或编译参数的设置,可能不会调用。其中关于go代码的编译是在compile中完成的,后续的文章中会对compile进行研究。
公众号
鄙人刚刚开通了公众号,专注于分享Go开发相关内容,望大家感兴趣的支持一下,在此特别感谢。