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