前言

Go是编译型语言,Go程序需要经过编译生成可执行文件才能运行,实现编译的命令就是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溯源

这里说下查找源码的简单小方法:当无法直接通过调用间的跳转找到源码时,可以直接通过全局搜索的方式来找相关的源码。如:我们要找go命令的源码,我们知道go命令的参数解析都是经过flag实现的。直接运行go命令,可以看到相关的help,搜索任一命令对应的解释,如: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对应的Command为work.CmdBuild。

CmdBuild

work.CmdBuild的具体源码位于/cmd/go/internal/work/build.go中,此文件中也包含了install命令对应的CmdInstall

声明

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。

第一个init func中指定了CmdBuild的运行fun,添加了对参数-i-o的解析(build.go中同时也包含了install命令的处理)。

func init() {// break init cycleCmdBuild.Run = runBuildCmdInstall.Run = runInstallCmdBuild.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 Contextc.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 copyenv := os.Getenv("CGO_ENABLED")if env == "" {env = defaultCGO_ENABLED}switch env {case "1":c.CgoEnabled = truecase "0":c.CgoEnabled = falsedefault:// cgo must be explicitly enabled for cross compilation buildsif 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 = valuecfg.BuildToolchainCompiler = BuildToolchain.compilercfg.BuildToolchainLinker = BuildToolchain.linkercfg.BuildContext.Compiler = valuereturn nil
}

此init func获取了默认的GOARCH、GOOS、GOROOT、GOPATH、Compiler,默认的Compiler为"gc",所以默认BuildToolchain为gcToolchain{},后续将由gcToolchain{}负责具体的编译调用。

runBuild

具体的执行func。

func runBuild(cmd *base.Command, args []string) {BuildInit()//对mod、instrument、buildMode进行初始化及检查,最后对输出编译包的路径进行检查。var b Builderb.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 optionsswitch 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 := ModeBuildif 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为buildfor _, p := range pkgs {if p.Name != "main" {//build的package需从main package开始continue}//目标packag的参数设置p.Target = filepath.Join(cfg.BuildO, p.DefaultExecName())p.Target += cfg.ExeSuffixp.Stale = truep.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 {//没有packagebase.Fatalf("no packages to build")}p := pkgs[0]p.Target = cfg.BuildOp.Stale = true // must build - not up to datep.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进行初始化及检查,最后对输出编译包的路径进行检查。

大致处理步骤:

  1. 进行build前的初始化操作
  2. 检查所有参与build的package(若不指定则以运行命令的当前文件夹为指定package)
  3. 确认输出路径及文件后缀
  4. 健全检查,避免错误使用
  5. 忽略仅测试的package
  6. 判断输出路径
  • 若不为空

    • 若输出路径存在且是文件夹,

      • 指定Action Mode,仅从main package开始Action关联,Action中包含对每个package build的处理
    • 若输出路径不存在或不是文件夹,仅能build一个package,否则报错退出
  • 若为空
    • 对所有package进行Action关联
  1. 执行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为linka.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.ExeSuffixa.built = a.Targetb.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 != 0mode &^= ModeVetOnlyif 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为buildObjdir:  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 = nilreturn 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.Targeta.Func = nilreturn a}}return a})// Find the build action; the cache entry may have been replaced// by the install action during (*Builder).installAction.buildAction := aswitch buildAction.Mode {case "build", "built-in package", "gccgo stdlib":// okcase "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转化为slicefor 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.gobase.Fatalf("go: refusing to write action graph to %v\n", file)}js := actionGraphJSON(root)//转化为jsonif 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 errorif 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.BuildPif 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

针对单个package的编译处理,此处仅关注.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.Packagebit := func(x uint32, b bool) uint32 {if b {return x}return 0}cachedBuild := false//正常编译时need为needBuild,即1need := bit(needBuild, !b.IsCmdList && a.needBuild || b.NeedExport) |bit(needCgoHdr, b.needCgoHdr(a)) |bit(needVet, a.needVet) |bit(needCompiledGoFiles, b.NeedCompiledGoFiles)if !p.BinaryOnly {//可以重编译的packageif 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 = truea.output = []byte{} // start saving output in case we miss any cache resultsneed &^= needBuildif 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 = truep.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 stringvar coverFile stringvar key stringif strings.HasSuffix(file, ".cgo1.go") {// cgo files have absolute pathsbase := filepath.Base(file)sourceFile = filecoverFile = objdir + basekey = strings.TrimSuffix(base, ".cgo1.go") + ".go"} else {sourceFile = filepath.Join(a.Package.Dir, file)coverFile = objdir + filekey = 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 []stringgccfiles = append(gccfiles, cfiles...)cfiles = nilif 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-.gosrcfiles = 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文件中获取ABIsymabis, 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.Bufferfmt.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.Packageif 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.Goarchfor _, 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." + extif 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." + extif 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." + extif 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/2601objects = 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的buildIDif err := b.updateBuildID(a, objpkg, true); err != nil {return err}//保存编译的路径a.built = objpkgreturn nil
}

build的步骤如下:

  1. 不能从源码重新编译package报错处理。
  2. 根据buidlID确认没有变更的(意味着package源文件没有发生变化),可以直接使用原编译缓存则直接使用;需要重新编译,则使用缓存源文件进行编译。
  3. 若无编译缓存,有缓存源文件,如需要,使用缓存源文件进行编译。
  4. 确认中间生成文件路径
  5. needCgoHdr、needVet
  6. 确认最终生成文件的路径
  7. 根据文件类型分类
  8. swig文件处理
  9. cover mode的处理,需要使用cover工具
  10. cgo处理,需要使用cgo工具
  11. 缓存所有源文件
  12. needVet、needCompiledGoFiles的处理
  13. 获取ABI,需要用到asm工具
  14. import处理
  15. 编译go文件,需要使用compile工具
  16. 复制.h文件
  17. 编译c文件
  18. 组装.s文件,需要使用asm工具
  19. 添加系统组件
  20. 打包至最终生成文件路径,需要使用pack工具
  21. 更新最终生成文件的buildID,需要使用buildid工具
  22. 保存最终生成文件路径

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.Packageobjdir := 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.Buffercmd := exec.Command(cmdline[0], cmdline[1:]...)cmd.Stdout = &bufcmd.Stderr = &bufcleanup := passLongArgsInResponseFiles(cmd)defer cleanup()cmd.Dir = dircmd.Env = base.EnvForDir(cmd.Dir, os.Environ())cmd.Env = append(cmd.Env, env...)start := time.Now()err := cmd.Run()...return buf.Bytes(), err
}

gc中是关于具体参数的拼接,并不直接编译,而是通过compile工具,传入拼接后的参数,调用compile的命令执行完成编译。

总结

go命令源码位于/cmd/go/ package中,这里负责着get、build、install等相关命令的处理。

go build的源码位于/cmd/go/internal/work/build.go中,从代码中可以看出,build的过程需要经过以下阶段:

  1. 根据路径获取直接参与build的package(不会循环查找子目录的package),忽略仅测试的package
  2. 对于单个main package的未设置输出路径的设置默认路径
  3. 若有输出路径,则仅从main package开始进行Action的关联,Action中包含对每个package build的处理
  4. 若无输出路径,仅支持对单个package的处理,此此package开始关联
  5. 执行Action,获取action list,添加优先级,按照优先级存入栈中
  6. 从栈中取出action,并发执行每一个action的Func,对应Builder的build func,等待所有action的操作执行结束
  • 如果缓存可用则使用缓存(直接使用缓存的编译文件或根据缓存的源文件重新编译)
  • 否则,根据文件类型分类,依次对swig、c、c++进行相关的build处理,缓存所有源文件
  • 处理.s、import config,编译go代码,拷贝.h,组装.s文件,添加系统组件,
  • 打包更新BuildID
  1. 所有action执行完毕后,即可获取对应的执行文件

build的源码中并不负责代码的编译,而是交给专门的编译工具尽心编译,完整的build过程使用到以下工具:

cover-cgo-compile-asm-pack-buildid-link

部分工具因文件类型的不一或编译参数的设置,可能不会调用。其中关于go代码的编译是在compile中完成的,后续的文章中会对compile进行研究。

公众号

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

了解Go编译处理(二)—— go build相关推荐

  1. 全志uboot修改_全志SDK编译问题解决二:build uboot only

    SDK在lichee部分编译内核会遇到错误: ./build.sh -p a13_nuclear -m uboot编译无法通过,提示找不到工具链 解决办法是在buildroot/scripts/com ...

  2. Ardupilot源码编译(二)

    最新参考:Ardupilot开发环境搭建(Ubuntu18.04,20190407)   目前Ardupilot的master版本需要用waf编译 waf 是一个帮助构建和编译系统的框架,采用pyth ...

  3. Android10源码编译报错ninja: build stopped: subcommand failed处理

    1.背景说明 虚拟机:wsl 2.0 Ubuntu18.04 虚拟机配置:8G内存,1T存储 软件版本:Android10源码,无任何修改 报错内容:编译至97%时,ninja编译中断,ninja: ...

  4. 【错误记录】Android Studio 编译报错 ( Installed Build Tools revision 31.0.0 is corrupted )

    文章目录 一.报错信息 二.解决方案 一.报错信息 Executing tasks: [:dex_demo:assembleDebug, :app:assembleDebug] in project ...

  5. 【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)

    作者 : 韩曙亮  博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csd ...

  6. Android makefile编译流程(二)

    ★★★ 友情链接 : 个人博客导读首页-点击此处 ★★★ 在build/core/main.mk中找到第一个目标,其makefile树关系如下: 然后我们逐一分析这些目标. ============= ...

  7. ios 编译时报 Could not build module xxx 的解决方法尝试

    文章概要 说明 尝试一 尝试二 说明 使用 #import <xx/xx.h> 在代码中来导入某个.framework库,编译时却收到了如下错误: Could not build modu ...

  8. DPDK源码编译(二十八)

    1.安装DPDK和源代码 首先,解压DPDK源码包 unzip DPDK-<version>.zip cd DPDK-<version>ls app/ config/ exam ...

  9. Docker源码编译(二)

    这是根据自己项目修改而记录的过程,对他人不具有参考性 一.按照以下教程,搭建docker编译环境:Docker源码编译和开发环境搭建 二.由于我对docker源码的修改导入了mysql数据库的包,如下 ...

  10. 跟vczh看实例学编译原理——二:实现Tinymoe的词法分析

    文章中引用的代码均来自https://github.com/vczh/tinymoe. 实现Tinymoe的第一步自然是一个词法分析器.词法分析其所作的事情很简单,就是把一份代码分割成若干个token ...

最新文章

  1. 让android的TextView可以滚动
  2. 真实案例引起的对系统健壮性的思考
  3. linux which
  4. 这两天做项目出现的几个问题
  5. Django05: 请求生命周期流程图/路由层
  6. python 方程组 整数解_用Python语言求解线性整数方程组
  7. 五、Linux实用指令
  8. Linux(ubuntu)——FTP服务器
  9. labelme进行mask图像标注
  10. 交互系统的构建之(二)Linux下鼠标和键盘的模拟控制
  11. Angular 学习笔记 Material
  12. chrome如何调试html,如何用firefox或chrome浏览器调试js和jquery程序
  13. Vue错误 Module not found:Error:Can‘t resolve ‘vue/types/umd‘ in ......
  14. 股票大作手杰西·利弗莫尔语录集锦
  15. gif动图怎么制作?gif动图制作教程大全
  16. 因果分析:原理、方法论、应用
  17. 怎样去识别是否双线主机服务器的方法
  18. 查看云服务器信息,查看云服务器信息
  19. [算法总结] LCA倍增法 dfs
  20. 如何1分钟实现身份实名认证功能?

热门文章

  1. 80后员工的12大缺点,管理他们需要18般兵器
  2. VideoPlayer 视频播放
  3. ubuntu通过qemu安装银河麒麟V10_Arm_v8 (kylin_v10_飞腾/鲲鹏版)
  4. 将其文件夹添加到MATL AB路径
  5. 目标检测之模型篇(3)【DMPNet】
  6. 百度网盘上传文件超过4G,只需一个工具即可免费上传
  7. 开源Linux、Windows服务器数据备份工具选型分析(一) UrBackup
  8. 打印N行的菱形图案(实心空心)
  9. Android java.lang.IllegalArgumentException(...contains a path separator)
  10. deadlock mysql_循环update导致的mysql deadlock分析