了解Go编译处理(二)—— go build
前言
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进行初始化及检查,最后对输出编译包的路径进行检查。
大致处理步骤:
- 进行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为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的步骤如下:
- 不能从源码重新编译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.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的过程需要经过以下阶段:
- 根据路径获取直接参与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开发相关内容,望大家感兴趣的支持一下,在此特别感谢。
了解Go编译处理(二)—— go build相关推荐
- 全志uboot修改_全志SDK编译问题解决二:build uboot only
SDK在lichee部分编译内核会遇到错误: ./build.sh -p a13_nuclear -m uboot编译无法通过,提示找不到工具链 解决办法是在buildroot/scripts/com ...
- Ardupilot源码编译(二)
最新参考:Ardupilot开发环境搭建(Ubuntu18.04,20190407) 目前Ardupilot的master版本需要用waf编译 waf 是一个帮助构建和编译系统的框架,采用pyth ...
- Android10源码编译报错ninja: build stopped: subcommand failed处理
1.背景说明 虚拟机:wsl 2.0 Ubuntu18.04 虚拟机配置:8G内存,1T存储 软件版本:Android10源码,无任何修改 报错内容:编译至97%时,ninja编译中断,ninja: ...
- 【错误记录】Android Studio 编译报错 ( Installed Build Tools revision 31.0.0 is corrupted )
文章目录 一.报错信息 二.解决方案 一.报错信息 Executing tasks: [:dex_demo:assembleDebug, :app:assembleDebug] in project ...
- 【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)
作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csd ...
- Android makefile编译流程(二)
★★★ 友情链接 : 个人博客导读首页-点击此处 ★★★ 在build/core/main.mk中找到第一个目标,其makefile树关系如下: 然后我们逐一分析这些目标. ============= ...
- ios 编译时报 Could not build module xxx 的解决方法尝试
文章概要 说明 尝试一 尝试二 说明 使用 #import <xx/xx.h> 在代码中来导入某个.framework库,编译时却收到了如下错误: Could not build modu ...
- DPDK源码编译(二十八)
1.安装DPDK和源代码 首先,解压DPDK源码包 unzip DPDK-<version>.zip cd DPDK-<version>ls app/ config/ exam ...
- Docker源码编译(二)
这是根据自己项目修改而记录的过程,对他人不具有参考性 一.按照以下教程,搭建docker编译环境:Docker源码编译和开发环境搭建 二.由于我对docker源码的修改导入了mysql数据库的包,如下 ...
- 跟vczh看实例学编译原理——二:实现Tinymoe的词法分析
文章中引用的代码均来自https://github.com/vczh/tinymoe. 实现Tinymoe的第一步自然是一个词法分析器.词法分析其所作的事情很简单,就是把一份代码分割成若干个token ...
最新文章
- 让android的TextView可以滚动
- 真实案例引起的对系统健壮性的思考
- linux which
- 这两天做项目出现的几个问题
- Django05: 请求生命周期流程图/路由层
- python 方程组 整数解_用Python语言求解线性整数方程组
- 五、Linux实用指令
- Linux(ubuntu)——FTP服务器
- labelme进行mask图像标注
- 交互系统的构建之(二)Linux下鼠标和键盘的模拟控制
- Angular 学习笔记 Material
- chrome如何调试html,如何用firefox或chrome浏览器调试js和jquery程序
- Vue错误 Module not found:Error:Can‘t resolve ‘vue/types/umd‘ in ......
- 股票大作手杰西·利弗莫尔语录集锦
- gif动图怎么制作?gif动图制作教程大全
- 因果分析:原理、方法论、应用
- 怎样去识别是否双线主机服务器的方法
- 查看云服务器信息,查看云服务器信息
- [算法总结] LCA倍增法 dfs
- 如何1分钟实现身份实名认证功能?
热门文章
- 80后员工的12大缺点,管理他们需要18般兵器
- VideoPlayer 视频播放
- ubuntu通过qemu安装银河麒麟V10_Arm_v8 (kylin_v10_飞腾/鲲鹏版)
- 将其文件夹添加到MATL AB路径
- 目标检测之模型篇(3)【DMPNet】
- 百度网盘上传文件超过4G,只需一个工具即可免费上传
- 开源Linux、Windows服务器数据备份工具选型分析(一) UrBackup
- 打印N行的菱形图案(实心空心)
- Android java.lang.IllegalArgumentException(...contains a path separator)
- deadlock mysql_循环update导致的mysql deadlock分析