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