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