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