github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/cmd/go/internal/work/gc.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package work
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strings"
    18  
    19  	"cmd/go/internal/base"
    20  	"cmd/go/internal/cfg"
    21  	"cmd/go/internal/load"
    22  	"cmd/go/internal/str"
    23  	"crypto/sha1"
    24  )
    25  
    26  // The Go toolchain.
    27  
    28  type gcToolchain struct{}
    29  
    30  func (gcToolchain) compiler() string {
    31  	return base.Tool("compile")
    32  }
    33  
    34  func (gcToolchain) linker() string {
    35  	return base.Tool("link")
    36  }
    37  
    38  func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg []byte, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) {
    39  	p := a.Package
    40  	objdir := a.Objdir
    41  	if archive != "" {
    42  		ofile = archive
    43  	} else {
    44  		out := "_go_.o"
    45  		ofile = objdir + out
    46  	}
    47  
    48  	pkgpath := p.ImportPath
    49  	if cfg.BuildBuildmode == "plugin" {
    50  		pkgpath = pluginPath(a)
    51  	} else if p.Name == "main" {
    52  		pkgpath = "main"
    53  	}
    54  	gcargs := []string{"-p", pkgpath}
    55  	if p.Standard {
    56  		gcargs = append(gcargs, "-std")
    57  	}
    58  	compilingRuntime := p.Standard && (p.ImportPath == "runtime" || strings.HasPrefix(p.ImportPath, "runtime/internal"))
    59  	if compilingRuntime {
    60  		// runtime compiles with a special gc flag to emit
    61  		// additional reflect type data.
    62  		gcargs = append(gcargs, "-+")
    63  	}
    64  
    65  	// If we're giving the compiler the entire package (no C etc files), tell it that,
    66  	// so that it can give good error messages about forward declarations.
    67  	// Exceptions: a few standard packages have forward declarations for
    68  	// pieces supplied behind-the-scenes by package runtime.
    69  	extFiles := len(p.CgoFiles) + len(p.CFiles) + len(p.CXXFiles) + len(p.MFiles) + len(p.FFiles) + len(p.SFiles) + len(p.SysoFiles) + len(p.SwigFiles) + len(p.SwigCXXFiles)
    70  	if p.Standard {
    71  		switch p.ImportPath {
    72  		case "bytes", "internal/poll", "net", "os", "runtime/pprof", "sync", "syscall", "time":
    73  			extFiles++
    74  		}
    75  	}
    76  	if extFiles == 0 {
    77  		gcargs = append(gcargs, "-complete")
    78  	}
    79  	if cfg.BuildContext.InstallSuffix != "" {
    80  		gcargs = append(gcargs, "-installsuffix", cfg.BuildContext.InstallSuffix)
    81  	}
    82  	if a.buildID != "" {
    83  		gcargs = append(gcargs, "-buildid", a.buildID)
    84  	}
    85  	platform := cfg.Goos + "/" + cfg.Goarch
    86  	if p.Internal.OmitDebug || platform == "nacl/amd64p32" || platform == "darwin/arm" || platform == "darwin/arm64" || cfg.Goos == "plan9" {
    87  		gcargs = append(gcargs, "-dwarf=false")
    88  	}
    89  	if strings.HasPrefix(runtimeVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") {
    90  		gcargs = append(gcargs, "-goversion", runtimeVersion)
    91  	}
    92  
    93  	gcflags := str.StringList(forcedGcflags, p.Internal.Gcflags)
    94  	if compilingRuntime {
    95  		// Remove -N, if present.
    96  		// It is not possible to build the runtime with no optimizations,
    97  		// because the compiler cannot eliminate enough write barriers.
    98  		for i := 0; i < len(gcflags); i++ {
    99  			if gcflags[i] == "-N" {
   100  				copy(gcflags[i:], gcflags[i+1:])
   101  				gcflags = gcflags[:len(gcflags)-1]
   102  				i--
   103  			}
   104  		}
   105  	}
   106  
   107  	args := []interface{}{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", trimDir(a.Objdir), gcflags, gcargs, "-D", p.Internal.LocalPrefix}
   108  	if importcfg != nil {
   109  		if err := b.writeFile(objdir+"importcfg", importcfg); err != nil {
   110  			return "", nil, err
   111  		}
   112  		args = append(args, "-importcfg", objdir+"importcfg")
   113  	}
   114  	if ofile == archive {
   115  		args = append(args, "-pack")
   116  	}
   117  	if asmhdr {
   118  		args = append(args, "-asmhdr", objdir+"go_asm.h")
   119  	}
   120  
   121  	// Add -c=N to use concurrent backend compilation, if possible.
   122  	if c := gcBackendConcurrency(gcflags); c > 1 {
   123  		args = append(args, fmt.Sprintf("-c=%d", c))
   124  	}
   125  
   126  	for _, f := range gofiles {
   127  		args = append(args, mkAbs(p.Dir, f))
   128  	}
   129  
   130  	output, err = b.runOut(p.Dir, p.ImportPath, nil, args...)
   131  	return ofile, output, err
   132  }
   133  
   134  // gcBackendConcurrency returns the backend compiler concurrency level for a package compilation.
   135  func gcBackendConcurrency(gcflags []string) int {
   136  	// First, check whether we can use -c at all for this compilation.
   137  	canDashC := concurrentGCBackendCompilationEnabledByDefault
   138  
   139  	switch e := os.Getenv("GO19CONCURRENTCOMPILATION"); e {
   140  	case "0":
   141  		canDashC = false
   142  	case "1":
   143  		canDashC = true
   144  	case "":
   145  		// Not set. Use default.
   146  	default:
   147  		log.Fatalf("GO19CONCURRENTCOMPILATION must be 0, 1, or unset, got %q", e)
   148  	}
   149  
   150  	if os.Getenv("GOEXPERIMENT") != "" {
   151  		// Concurrent compilation is presumed incompatible with GOEXPERIMENTs.
   152  		canDashC = false
   153  	}
   154  
   155  CheckFlags:
   156  	for _, flag := range gcflags {
   157  		// Concurrent compilation is presumed incompatible with any gcflags,
   158  		// except for a small whitelist of commonly used flags.
   159  		// If the user knows better, they can manually add their own -c to the gcflags.
   160  		switch flag {
   161  		case "-N", "-l", "-S", "-B", "-C", "-I":
   162  			// OK
   163  		default:
   164  			canDashC = false
   165  			break CheckFlags
   166  		}
   167  	}
   168  
   169  	if !canDashC {
   170  		return 1
   171  	}
   172  
   173  	// Decide how many concurrent backend compilations to allow.
   174  	//
   175  	// If we allow too many, in theory we might end up with p concurrent processes,
   176  	// each with c concurrent backend compiles, all fighting over the same resources.
   177  	// However, in practice, that seems not to happen too much.
   178  	// Most build graphs are surprisingly serial, so p==1 for much of the build.
   179  	// Furthermore, concurrent backend compilation is only enabled for a part
   180  	// of the overall compiler execution, so c==1 for much of the build.
   181  	// So don't worry too much about that interaction for now.
   182  	//
   183  	// However, in practice, setting c above 4 tends not to help very much.
   184  	// See the analysis in CL 41192.
   185  	//
   186  	// TODO(josharian): attempt to detect whether this particular compilation
   187  	// is likely to be a bottleneck, e.g. when:
   188  	//   - it has no successor packages to compile (usually package main)
   189  	//   - all paths through the build graph pass through it
   190  	//   - critical path scheduling says it is high priority
   191  	// and in such a case, set c to runtime.NumCPU.
   192  	// We do this now when p==1.
   193  	if cfg.BuildP == 1 {
   194  		// No process parallelism. Max out c.
   195  		return runtime.NumCPU()
   196  	}
   197  	// Some process parallelism. Set c to min(4, numcpu).
   198  	c := 4
   199  	if ncpu := runtime.NumCPU(); ncpu < c {
   200  		c = ncpu
   201  	}
   202  	return c
   203  }
   204  
   205  func trimDir(dir string) string {
   206  	if len(dir) > 1 && dir[len(dir)-1] == filepath.Separator {
   207  		dir = dir[:len(dir)-1]
   208  	}
   209  	return dir
   210  }
   211  
   212  func (gcToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) {
   213  	p := a.Package
   214  	// Add -I pkg/GOOS_GOARCH so #include "textflag.h" works in .s files.
   215  	inc := filepath.Join(cfg.GOROOT, "pkg", "include")
   216  	args := []interface{}{cfg.BuildToolexec, base.Tool("asm"), "-trimpath", trimDir(a.Objdir), "-I", a.Objdir, "-I", inc, "-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch, forcedAsmflags, p.Internal.Asmflags}
   217  	if p.ImportPath == "runtime" && cfg.Goarch == "386" {
   218  		for _, arg := range forcedAsmflags {
   219  			if arg == "-dynlink" {
   220  				args = append(args, "-D=GOBUILDMODE_shared=1")
   221  			}
   222  		}
   223  	}
   224  	var ofiles []string
   225  	for _, sfile := range sfiles {
   226  		ofile := a.Objdir + sfile[:len(sfile)-len(".s")] + ".o"
   227  		ofiles = append(ofiles, ofile)
   228  		a := append(args, "-o", ofile, mkAbs(p.Dir, sfile))
   229  		if err := b.run(p.Dir, p.ImportPath, nil, a...); err != nil {
   230  			return nil, err
   231  		}
   232  	}
   233  	return ofiles, nil
   234  }
   235  
   236  // toolVerify checks that the command line args writes the same output file
   237  // if run using newTool instead.
   238  // Unused now but kept around for future use.
   239  func toolVerify(b *Builder, p *load.Package, newTool string, ofile string, args []interface{}) error {
   240  	newArgs := make([]interface{}, len(args))
   241  	copy(newArgs, args)
   242  	newArgs[1] = base.Tool(newTool)
   243  	newArgs[3] = ofile + ".new" // x.6 becomes x.6.new
   244  	if err := b.run(p.Dir, p.ImportPath, nil, newArgs...); err != nil {
   245  		return err
   246  	}
   247  	data1, err := ioutil.ReadFile(ofile)
   248  	if err != nil {
   249  		return err
   250  	}
   251  	data2, err := ioutil.ReadFile(ofile + ".new")
   252  	if err != nil {
   253  		return err
   254  	}
   255  	if !bytes.Equal(data1, data2) {
   256  		return fmt.Errorf("%s and %s produced different output files:\n%s\n%s", filepath.Base(args[1].(string)), newTool, strings.Join(str.StringList(args...), " "), strings.Join(str.StringList(newArgs...), " "))
   257  	}
   258  	os.Remove(ofile + ".new")
   259  	return nil
   260  }
   261  
   262  func (gcToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error {
   263  	var absOfiles []string
   264  	for _, f := range ofiles {
   265  		absOfiles = append(absOfiles, mkAbs(a.Objdir, f))
   266  	}
   267  	absAfile := mkAbs(a.Objdir, afile)
   268  
   269  	// The archive file should have been created by the compiler.
   270  	// Since it used to not work that way, verify.
   271  	if !cfg.BuildN {
   272  		if _, err := os.Stat(absAfile); err != nil {
   273  			base.Fatalf("os.Stat of archive file failed: %v", err)
   274  		}
   275  	}
   276  
   277  	p := a.Package
   278  	if cfg.BuildN || cfg.BuildX {
   279  		cmdline := str.StringList(base.Tool("pack"), "r", absAfile, absOfiles)
   280  		b.Showcmd(p.Dir, "%s # internal", joinUnambiguously(cmdline))
   281  	}
   282  	if cfg.BuildN {
   283  		return nil
   284  	}
   285  	if err := packInternal(b, absAfile, absOfiles); err != nil {
   286  		b.showOutput(p.Dir, p.ImportPath, err.Error()+"\n")
   287  		return errPrintedOutput
   288  	}
   289  	return nil
   290  }
   291  
   292  func packInternal(b *Builder, afile string, ofiles []string) error {
   293  	dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	defer dst.Close() // only for error returns or panics
   298  	w := bufio.NewWriter(dst)
   299  
   300  	for _, ofile := range ofiles {
   301  		src, err := os.Open(ofile)
   302  		if err != nil {
   303  			return err
   304  		}
   305  		fi, err := src.Stat()
   306  		if err != nil {
   307  			src.Close()
   308  			return err
   309  		}
   310  		// Note: Not using %-16.16s format because we care
   311  		// about bytes, not runes.
   312  		name := fi.Name()
   313  		if len(name) > 16 {
   314  			name = name[:16]
   315  		} else {
   316  			name += strings.Repeat(" ", 16-len(name))
   317  		}
   318  		size := fi.Size()
   319  		fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\n",
   320  			name, 0, 0, 0, 0644, size)
   321  		n, err := io.Copy(w, src)
   322  		src.Close()
   323  		if err == nil && n < size {
   324  			err = io.ErrUnexpectedEOF
   325  		} else if err == nil && n > size {
   326  			err = fmt.Errorf("file larger than size reported by stat")
   327  		}
   328  		if err != nil {
   329  			return fmt.Errorf("copying %s to %s: %v", ofile, afile, err)
   330  		}
   331  		if size&1 != 0 {
   332  			w.WriteByte(0)
   333  		}
   334  	}
   335  
   336  	if err := w.Flush(); err != nil {
   337  		return err
   338  	}
   339  	return dst.Close()
   340  }
   341  
   342  // setextld sets the appropriate linker flags for the specified compiler.
   343  func setextld(ldflags []string, compiler []string) []string {
   344  	for _, f := range ldflags {
   345  		if f == "-extld" || strings.HasPrefix(f, "-extld=") {
   346  			// don't override -extld if supplied
   347  			return ldflags
   348  		}
   349  	}
   350  	ldflags = append(ldflags, "-extld="+compiler[0])
   351  	if len(compiler) > 1 {
   352  		extldflags := false
   353  		add := strings.Join(compiler[1:], " ")
   354  		for i, f := range ldflags {
   355  			if f == "-extldflags" && i+1 < len(ldflags) {
   356  				ldflags[i+1] = add + " " + ldflags[i+1]
   357  				extldflags = true
   358  				break
   359  			} else if strings.HasPrefix(f, "-extldflags=") {
   360  				ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
   361  				extldflags = true
   362  				break
   363  			}
   364  		}
   365  		if !extldflags {
   366  			ldflags = append(ldflags, "-extldflags="+add)
   367  		}
   368  	}
   369  	return ldflags
   370  }
   371  
   372  // pluginPath computes the package path for a plugin main package.
   373  //
   374  // This is typically the import path of the main package p, unless the
   375  // plugin is being built directly from source files. In that case we
   376  // combine the package build ID with the contents of the main package
   377  // source files. This allows us to identify two different plugins
   378  // built from two source files with the same name.
   379  func pluginPath(a *Action) string {
   380  	p := a.Package
   381  	if p.ImportPath != "command-line-arguments" {
   382  		return p.ImportPath
   383  	}
   384  	h := sha1.New()
   385  	fmt.Fprintf(h, "build ID: %s\n", a.buildID)
   386  	for _, file := range str.StringList(p.GoFiles, p.CgoFiles, p.SFiles) {
   387  		data, err := ioutil.ReadFile(filepath.Join(p.Dir, file))
   388  		if err != nil {
   389  			base.Fatalf("go: %s", err)
   390  		}
   391  		h.Write(data)
   392  	}
   393  	return fmt.Sprintf("plugin/unnamed-%x", h.Sum(nil))
   394  }
   395  
   396  func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
   397  	cxx := len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
   398  	for _, a := range root.Deps {
   399  		if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
   400  			cxx = true
   401  		}
   402  	}
   403  	var ldflags []string
   404  	if cfg.BuildContext.InstallSuffix != "" {
   405  		ldflags = append(ldflags, "-installsuffix", cfg.BuildContext.InstallSuffix)
   406  	}
   407  	if root.Package.Internal.OmitDebug {
   408  		ldflags = append(ldflags, "-s", "-w")
   409  	}
   410  	if cfg.BuildBuildmode == "plugin" {
   411  		ldflags = append(ldflags, "-pluginpath", pluginPath(root))
   412  	}
   413  
   414  	// TODO(rsc): This is probably wrong - see golang.org/issue/22155.
   415  	if cfg.GOROOT != runtime.GOROOT() {
   416  		ldflags = append(ldflags, "-X=runtime/internal/sys.DefaultGoroot="+cfg.GOROOT)
   417  	}
   418  
   419  	// Store BuildID inside toolchain binaries as a unique identifier of the
   420  	// tool being run, for use by content-based staleness determination.
   421  	if root.Package.Goroot && strings.HasPrefix(root.Package.ImportPath, "cmd/") {
   422  		ldflags = append(ldflags, "-X=cmd/internal/objabi.buildID="+root.buildID)
   423  	}
   424  
   425  	// If the user has not specified the -extld option, then specify the
   426  	// appropriate linker. In case of C++ code, use the compiler named
   427  	// by the CXX environment variable or defaultCXX if CXX is not set.
   428  	// Else, use the CC environment variable and defaultCC as fallback.
   429  	var compiler []string
   430  	if cxx {
   431  		compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
   432  	} else {
   433  		compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
   434  	}
   435  	ldflags = append(ldflags, "-buildmode="+ldBuildmode)
   436  	if root.buildID != "" {
   437  		ldflags = append(ldflags, "-buildid="+root.buildID)
   438  	}
   439  	ldflags = append(ldflags, forcedLdflags...)
   440  	ldflags = append(ldflags, root.Package.Internal.Ldflags...)
   441  	ldflags = setextld(ldflags, compiler)
   442  
   443  	// On OS X when using external linking to build a shared library,
   444  	// the argument passed here to -o ends up recorded in the final
   445  	// shared library in the LC_ID_DYLIB load command.
   446  	// To avoid putting the temporary output directory name there
   447  	// (and making the resulting shared library useless),
   448  	// run the link in the output directory so that -o can name
   449  	// just the final path element.
   450  	// On Windows, DLL file name is recorded in PE file
   451  	// export section, so do like on OS X.
   452  	dir := "."
   453  	if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" {
   454  		dir, out = filepath.Split(out)
   455  	}
   456  
   457  	return b.run(dir, root.Package.ImportPath, nil, cfg.BuildToolexec, base.Tool("link"), "-o", out, "-importcfg", importcfg, ldflags, mainpkg)
   458  }
   459  
   460  func (gcToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error {
   461  	ldflags := []string{"-installsuffix", cfg.BuildContext.InstallSuffix}
   462  	ldflags = append(ldflags, "-buildmode=shared")
   463  	ldflags = append(ldflags, forcedLdflags...)
   464  	ldflags = append(ldflags, root.Package.Internal.Ldflags...)
   465  	cxx := false
   466  	for _, a := range allactions {
   467  		if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
   468  			cxx = true
   469  		}
   470  	}
   471  	// If the user has not specified the -extld option, then specify the
   472  	// appropriate linker. In case of C++ code, use the compiler named
   473  	// by the CXX environment variable or defaultCXX if CXX is not set.
   474  	// Else, use the CC environment variable and defaultCC as fallback.
   475  	var compiler []string
   476  	if cxx {
   477  		compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
   478  	} else {
   479  		compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
   480  	}
   481  	ldflags = setextld(ldflags, compiler)
   482  	for _, d := range toplevelactions {
   483  		if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries
   484  			continue
   485  		}
   486  		ldflags = append(ldflags, d.Package.ImportPath+"="+d.Target)
   487  	}
   488  	return b.run(".", out, nil, cfg.BuildToolexec, base.Tool("link"), "-o", out, "-importcfg", importcfg, ldflags)
   489  }
   490  
   491  func (gcToolchain) cc(b *Builder, a *Action, ofile, cfile string) error {
   492  	return fmt.Errorf("%s: C source files not supported without cgo", mkAbs(a.Package.Dir, cfile))
   493  }