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	"fmt"
9	"io/ioutil"
10	"os"
11	"os/exec"
12	"path/filepath"
13	"strings"
14
15	"cmd/go/internal/base"
16	"cmd/go/internal/cfg"
17	"cmd/go/internal/load"
18	"cmd/go/internal/str"
19)
20
21// The Gccgo toolchain.
22
23type gccgoToolchain struct{}
24
25var GccgoName, GccgoBin string
26var gccgoErr error
27
28func init() {
29	GccgoName = os.Getenv("GCCGO")
30	if GccgoName == "" {
31		GccgoName = cfg.DefaultGCCGO(cfg.Goos, cfg.Goarch)
32	}
33	GccgoBin, gccgoErr = exec.LookPath(GccgoName)
34}
35
36func (gccgoToolchain) compiler() string {
37	checkGccgoBin()
38	return GccgoBin
39}
40
41func (gccgoToolchain) linker() string {
42	checkGccgoBin()
43	return GccgoBin
44}
45
46func checkGccgoBin() {
47	if gccgoErr == nil {
48		return
49	}
50	fmt.Fprintf(os.Stderr, "cmd/go: gccgo: %s\n", gccgoErr)
51	os.Exit(2)
52}
53
54func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg []byte, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) {
55	p := a.Package
56	objdir := a.Objdir
57	out := "_go_.o"
58	ofile = objdir + out
59	gcargs := []string{"-g"}
60	gcargs = append(gcargs, b.gccArchArgs()...)
61	gcargs = append(gcargs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build")
62	gcargs = append(gcargs, "-gno-record-gcc-switches")
63	if pkgpath := gccgoPkgpath(p); pkgpath != "" {
64		gcargs = append(gcargs, "-fgo-pkgpath="+pkgpath)
65	}
66	if p.Internal.LocalPrefix != "" {
67		gcargs = append(gcargs, "-fgo-relative-import-path="+p.Internal.LocalPrefix)
68	}
69
70	args := str.StringList(tools.compiler(), "-c", gcargs, "-o", ofile, forcedGccgoflags)
71	if importcfg != nil {
72		if b.gccSupportsFlag(args[:1], "-fgo-importcfg=/dev/null") {
73			if err := b.writeFile(objdir+"importcfg", importcfg); err != nil {
74				return "", nil, err
75			}
76			args = append(args, "-fgo-importcfg="+objdir+"importcfg")
77		} else {
78			root := objdir + "_importcfgroot_"
79			if err := buildImportcfgSymlinks(b, root, importcfg); err != nil {
80				return "", nil, err
81			}
82			args = append(args, "-I", root)
83		}
84	}
85	args = append(args, a.Package.Internal.Gccgoflags...)
86	for _, f := range gofiles {
87		args = append(args, mkAbs(p.Dir, f))
88	}
89
90	output, err = b.runOut(p.Dir, p.ImportPath, nil, args)
91	return ofile, output, err
92}
93
94// buildImportcfgSymlinks builds in root a tree of symlinks
95// implementing the directives from importcfg.
96// This serves as a temporary transition mechanism until
97// we can depend on gccgo reading an importcfg directly.
98// (The Go 1.9 and later gc compilers already do.)
99func buildImportcfgSymlinks(b *Builder, root string, importcfg []byte) error {
100	for lineNum, line := range strings.Split(string(importcfg), "\n") {
101		lineNum++ // 1-based
102		line = strings.TrimSpace(line)
103		if line == "" {
104			continue
105		}
106		if line == "" || strings.HasPrefix(line, "#") {
107			continue
108		}
109		var verb, args string
110		if i := strings.Index(line, " "); i < 0 {
111			verb = line
112		} else {
113			verb, args = line[:i], strings.TrimSpace(line[i+1:])
114		}
115		var before, after string
116		if i := strings.Index(args, "="); i >= 0 {
117			before, after = args[:i], args[i+1:]
118		}
119		switch verb {
120		default:
121			base.Fatalf("importcfg:%d: unknown directive %q", lineNum, verb)
122		case "packagefile":
123			if before == "" || after == "" {
124				return fmt.Errorf(`importcfg:%d: invalid packagefile: syntax is "packagefile path=filename": %s`, lineNum, line)
125			}
126			archive := gccgoArchive(root, before)
127			if err := b.Mkdir(filepath.Dir(archive)); err != nil {
128				return err
129			}
130			if err := b.Symlink(after, archive); err != nil {
131				return err
132			}
133		case "importmap":
134			if before == "" || after == "" {
135				return fmt.Errorf(`importcfg:%d: invalid importmap: syntax is "importmap old=new": %s`, lineNum, line)
136			}
137			beforeA := gccgoArchive(root, before)
138			afterA := gccgoArchive(root, after)
139			if err := b.Mkdir(filepath.Dir(beforeA)); err != nil {
140				return err
141			}
142			if err := b.Mkdir(filepath.Dir(afterA)); err != nil {
143				return err
144			}
145			if err := b.Symlink(afterA, beforeA); err != nil {
146				return err
147			}
148		case "packageshlib":
149			return fmt.Errorf("gccgo -importcfg does not support shared libraries")
150		}
151	}
152	return nil
153}
154
155func (tools gccgoToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) {
156	p := a.Package
157	var ofiles []string
158	for _, sfile := range sfiles {
159		base := filepath.Base(sfile)
160		ofile := a.Objdir + base[:len(base)-len(".s")] + ".o"
161		ofiles = append(ofiles, ofile)
162		sfile = mkAbs(p.Dir, sfile)
163		defs := []string{"-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch}
164		if pkgpath := gccgoCleanPkgpath(p); pkgpath != "" {
165			defs = append(defs, `-D`, `GOPKGPATH=`+pkgpath)
166		}
167		defs = tools.maybePIC(defs)
168		defs = append(defs, b.gccArchArgs()...)
169		err := b.run(a, p.Dir, p.ImportPath, nil, tools.compiler(), "-xassembler-with-cpp", "-I", a.Objdir, "-c", "-o", ofile, defs, sfile)
170		if err != nil {
171			return nil, err
172		}
173	}
174	return ofiles, nil
175}
176
177func gccgoArchive(basedir, imp string) string {
178	end := filepath.FromSlash(imp + ".a")
179	afile := filepath.Join(basedir, end)
180	// add "lib" to the final element
181	return filepath.Join(filepath.Dir(afile), "lib"+filepath.Base(afile))
182}
183
184func (gccgoToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error {
185	p := a.Package
186	objdir := a.Objdir
187	var absOfiles []string
188	for _, f := range ofiles {
189		absOfiles = append(absOfiles, mkAbs(objdir, f))
190	}
191	absAfile := mkAbs(objdir, afile)
192	// Try with D modifier first, then without if that fails.
193	if cfg.Goos == "aix" || b.run(a, p.Dir, p.ImportPath, nil, "ar", "rcD", absAfile, absOfiles) != nil {
194		var arArgs []string
195		if cfg.Goos == "aix" && cfg.Goarch == "ppc64" {
196			// AIX puts both 32-bit and 64-bit objects in the same archive.
197			// Tell the AIX "ar" command to only care about 64-bit objects.
198			// AIX "ar" command does not know D option.
199			arArgs = append(arArgs, "-X64")
200		}
201		return b.run(a, p.Dir, p.ImportPath, nil, "ar", arArgs, "rc", absAfile, absOfiles)
202	}
203	return nil
204}
205
206func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string, allactions []*Action, buildmode, desc string) error {
207	// gccgo needs explicit linking with all package dependencies,
208	// and all LDFLAGS from cgo dependencies.
209	afiles := []string{}
210	shlibs := []string{}
211	ldflags := b.gccArchArgs()
212	cgoldflags := []string{}
213	usesCgo := false
214	cxx := false
215	objc := false
216	fortran := false
217	if root.Package != nil {
218		cxx = len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
219		objc = len(root.Package.MFiles) > 0
220		fortran = len(root.Package.FFiles) > 0
221	}
222
223	readCgoFlags := func(flagsFile string) error {
224		flags, err := ioutil.ReadFile(flagsFile)
225		if err != nil {
226			return err
227		}
228		const ldflagsPrefix = "_CGO_LDFLAGS="
229		for _, line := range strings.Split(string(flags), "\n") {
230			if strings.HasPrefix(line, ldflagsPrefix) {
231				line = line[len(ldflagsPrefix):]
232				quote := byte(0)
233				start := true
234				var nl []byte
235				for len(line) > 0 {
236					b := line[0]
237					line = line[1:]
238					if quote == 0 && (b == ' ' || b == '\t') {
239						if len(nl) > 0 {
240							cgoldflags = append(cgoldflags, string(nl))
241							nl = nil
242						}
243						start = true
244						continue
245					} else if b == '"' || b == '\'' {
246						quote = b
247					} else if b == quote {
248						quote = 0
249					} else if quote == 0 && start && b == '-' && strings.HasPrefix(line, "g") {
250						line = line[1:]
251						continue
252					} else if quote == 0 && start && b == '-' && strings.HasPrefix(line, "O") {
253						for len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
254							line = line[1:]
255						}
256						continue
257					}
258					nl = append(nl, b)
259					start = false
260				}
261				if len(nl) > 0 {
262					cgoldflags = append(cgoldflags, string(nl))
263				}
264			}
265		}
266		return nil
267	}
268
269	newID := 0
270	readAndRemoveCgoFlags := func(archive string) (string, error) {
271		newID++
272		newArchive := root.Objdir + fmt.Sprintf("_pkg%d_.a", newID)
273		if err := b.copyFile(root, newArchive, archive, 0666, false); err != nil {
274			return "", err
275		}
276		if cfg.BuildN {
277			// TODO(rsc): We could do better about showing the right _cgo_flags even in -n mode.
278			// Either the archive is already built and we can read them out,
279			// or we're printing commands to build the archive and can
280			// forward the _cgo_flags directly to this step.
281			b.Showcmd("", "ar d %s _cgo_flags", newArchive)
282			return "", nil
283		}
284		err := b.run(root, root.Objdir, desc, nil, "ar", "x", newArchive, "_cgo_flags")
285		if err != nil {
286			return "", err
287		}
288		err = b.run(root, ".", desc, nil, "ar", "d", newArchive, "_cgo_flags")
289		if err != nil {
290			return "", err
291		}
292		err = readCgoFlags(filepath.Join(root.Objdir, "_cgo_flags"))
293		if err != nil {
294			return "", err
295		}
296		return newArchive, nil
297	}
298
299	// If using -linkshared, find the shared library deps.
300	haveShlib := make(map[string]bool)
301	targetBase := filepath.Base(root.Target)
302	if cfg.BuildLinkshared {
303		for _, a := range root.Deps {
304			p := a.Package
305			if p == nil || p.Shlib == "" {
306				continue
307			}
308
309			// The .a we are linking into this .so
310			// will have its Shlib set to this .so.
311			// Don't start thinking we want to link
312			// this .so into itself.
313			base := filepath.Base(p.Shlib)
314			if base != targetBase {
315				haveShlib[base] = true
316			}
317		}
318	}
319
320	// Arrange the deps into afiles and shlibs.
321	addedShlib := make(map[string]bool)
322	for _, a := range root.Deps {
323		p := a.Package
324		if p != nil && p.Shlib != "" && haveShlib[filepath.Base(p.Shlib)] {
325			// This is a package linked into a shared
326			// library that we will put into shlibs.
327			continue
328		}
329
330		if haveShlib[filepath.Base(a.Target)] {
331			// This is a shared library we want to link againt.
332			if !addedShlib[a.Target] {
333				shlibs = append(shlibs, a.Target)
334				addedShlib[a.Target] = true
335			}
336			continue
337		}
338
339		if p != nil {
340			target := a.built
341			if p.UsesCgo() || p.UsesSwig() {
342				var err error
343				target, err = readAndRemoveCgoFlags(target)
344				if err != nil {
345					continue
346				}
347			}
348
349			afiles = append(afiles, target)
350		}
351	}
352
353	for _, a := range allactions {
354		// Gather CgoLDFLAGS, but not from standard packages.
355		// The go tool can dig up runtime/cgo from GOROOT and
356		// think that it should use its CgoLDFLAGS, but gccgo
357		// doesn't use runtime/cgo.
358		if a.Package == nil {
359			continue
360		}
361		if !a.Package.Standard {
362			cgoldflags = append(cgoldflags, a.Package.CgoLDFLAGS...)
363		}
364		if len(a.Package.CgoFiles) > 0 {
365			usesCgo = true
366		}
367		if a.Package.UsesSwig() {
368			usesCgo = true
369		}
370		if len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0 {
371			cxx = true
372		}
373		if len(a.Package.MFiles) > 0 {
374			objc = true
375		}
376		if len(a.Package.FFiles) > 0 {
377			fortran = true
378		}
379	}
380
381	wholeArchive := []string{"-Wl,--whole-archive"}
382	noWholeArchive := []string{"-Wl,--no-whole-archive"}
383	if cfg.Goos == "aix" {
384		wholeArchive = nil
385		noWholeArchive = nil
386	}
387	ldflags = append(ldflags, wholeArchive...)
388	ldflags = append(ldflags, afiles...)
389	ldflags = append(ldflags, noWholeArchive...)
390
391	ldflags = append(ldflags, cgoldflags...)
392	ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...)
393	if root.Package != nil {
394		ldflags = append(ldflags, root.Package.CgoLDFLAGS...)
395	}
396
397	if cfg.Goos != "aix" {
398		ldflags = str.StringList("-Wl,-(", ldflags, "-Wl,-)")
399	}
400
401	if root.buildID != "" {
402		// On systems that normally use gold or the GNU linker,
403		// use the --build-id option to write a GNU build ID note.
404		switch cfg.Goos {
405		case "android", "dragonfly", "linux", "netbsd":
406			ldflags = append(ldflags, fmt.Sprintf("-Wl,--build-id=0x%x", root.buildID))
407		}
408	}
409
410	var rLibPath string
411	if cfg.Goos == "aix" {
412		rLibPath = "-Wl,-blibpath="
413	} else {
414		rLibPath = "-Wl,-rpath="
415	}
416	for _, shlib := range shlibs {
417		ldflags = append(
418			ldflags,
419			"-L"+filepath.Dir(shlib),
420			rLibPath+filepath.Dir(shlib),
421			"-l"+strings.TrimSuffix(
422				strings.TrimPrefix(filepath.Base(shlib), "lib"),
423				".so"))
424	}
425
426	var realOut string
427	goLibBegin := str.StringList(wholeArchive, "-lgolibbegin", noWholeArchive)
428	switch buildmode {
429	case "exe":
430		if usesCgo && cfg.Goos == "linux" {
431			ldflags = append(ldflags, "-Wl,-E")
432		}
433
434	case "c-archive":
435		// Link the Go files into a single .o, and also link
436		// in -lgolibbegin.
437		//
438		// We need to use --whole-archive with -lgolibbegin
439		// because it doesn't define any symbols that will
440		// cause the contents to be pulled in; it's just
441		// initialization code.
442		//
443		// The user remains responsible for linking against
444		// -lgo -lpthread -lm in the final link. We can't use
445		// -r to pick them up because we can't combine
446		// split-stack and non-split-stack code in a single -r
447		// link, and libgo picks up non-split-stack code from
448		// libffi.
449		ldflags = append(ldflags, "-Wl,-r", "-nostdlib")
450		ldflags = append(ldflags, goLibBegin...)
451
452		if nopie := b.gccNoPie([]string{tools.linker()}); nopie != "" {
453			ldflags = append(ldflags, nopie)
454		}
455
456		// We are creating an object file, so we don't want a build ID.
457		if root.buildID == "" {
458			ldflags = b.disableBuildID(ldflags)
459		}
460
461		realOut = out
462		out = out + ".o"
463
464	case "c-shared":
465		ldflags = append(ldflags, "-shared", "-nostdlib")
466		ldflags = append(ldflags, goLibBegin...)
467		ldflags = append(ldflags, "-lgo", "-lgcc_s", "-lgcc", "-lc", "-lgcc")
468	case "shared":
469		if cfg.Goos != "aix" {
470			ldflags = append(ldflags, "-zdefs")
471		}
472		ldflags = append(ldflags, "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc")
473
474	case "pie":
475		ldflags = append(ldflags, "-pie")
476
477	default:
478		base.Fatalf("-buildmode=%s not supported for gccgo", buildmode)
479	}
480
481	switch buildmode {
482	case "exe", "c-shared":
483		if cxx {
484			ldflags = append(ldflags, "-lstdc++")
485		}
486		if objc {
487			ldflags = append(ldflags, "-lobjc")
488		}
489		if fortran {
490			fc := os.Getenv("FC")
491			if fc == "" {
492				fc = "gfortran"
493			}
494			// support gfortran out of the box and let others pass the correct link options
495			// via CGO_LDFLAGS
496			if strings.Contains(fc, "gfortran") {
497				ldflags = append(ldflags, "-lgfortran")
498			}
499		}
500	}
501
502	if err := b.run(root, ".", desc, nil, tools.linker(), "-o", out, ldflags, forcedGccgoflags, root.Package.Internal.Gccgoflags); err != nil {
503		return err
504	}
505
506	switch buildmode {
507	case "c-archive":
508		if err := b.run(root, ".", desc, nil, "ar", "rc", realOut, out); err != nil {
509			return err
510		}
511	}
512	return nil
513}
514
515func (tools gccgoToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
516	return tools.link(b, root, out, importcfg, root.Deps, ldBuildmode, root.Package.ImportPath)
517}
518
519func (tools gccgoToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error {
520	return tools.link(b, root, out, importcfg, allactions, "shared", out)
521}
522
523func (tools gccgoToolchain) cc(b *Builder, a *Action, ofile, cfile string) error {
524	p := a.Package
525	inc := filepath.Join(cfg.GOROOT, "pkg", "include")
526	cfile = mkAbs(p.Dir, cfile)
527	defs := []string{"-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch}
528	defs = append(defs, b.gccArchArgs()...)
529	if pkgpath := gccgoCleanPkgpath(p); pkgpath != "" {
530		defs = append(defs, `-D`, `GOPKGPATH="`+pkgpath+`"`)
531	}
532	compiler := envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
533	if b.gccSupportsFlag(compiler, "-fsplit-stack") {
534		defs = append(defs, "-fsplit-stack")
535	}
536	defs = tools.maybePIC(defs)
537	if b.gccSupportsFlag(compiler, "-fdebug-prefix-map=a=b") {
538		defs = append(defs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build")
539	}
540	if b.gccSupportsFlag(compiler, "-gno-record-gcc-switches") {
541		defs = append(defs, "-gno-record-gcc-switches")
542	}
543	return b.run(a, p.Dir, p.ImportPath, nil, compiler, "-Wall", "-g",
544		"-I", a.Objdir, "-I", inc, "-o", ofile, defs, "-c", cfile)
545}
546
547// maybePIC adds -fPIC to the list of arguments if needed.
548func (tools gccgoToolchain) maybePIC(args []string) []string {
549	switch cfg.BuildBuildmode {
550	case "c-archive", "c-shared", "shared", "plugin":
551		args = append(args, "-fPIC")
552	}
553	return args
554}
555
556func gccgoPkgpath(p *load.Package) string {
557	if p.Internal.Build.IsCommand() && !p.Internal.ForceLibrary {
558		return ""
559	}
560	if p.ImportPath == "main" && p.Internal.ForceLibrary {
561		return "testmain"
562	}
563	return p.ImportPath
564}
565
566func gccgoCleanPkgpath(p *load.Package) string {
567	clean := func(r rune) rune {
568		switch {
569		case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z',
570			'0' <= r && r <= '9':
571			return r
572		}
573		return '_'
574	}
575	return strings.Map(clean, gccgoPkgpath(p))
576}
577