blob: b58c8aa8854a459a53f5226a322eee342af296ab [file] [log] [blame]
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package work
import (
"fmt"
exec "internal/execabs"
"os"
"path/filepath"
"strings"
"sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/load"
"cmd/go/internal/str"
"cmd/internal/pkgpath"
)
// The Gccgo toolchain.
type gccgoToolchain struct{}
var GccgoName, GccgoBin string
var gccgoErr error
func init() {
GccgoName = cfg.Getenv("GCCGO")
if GccgoName == "" {
GccgoName = "gccgo"
}
GccgoBin, gccgoErr = exec.LookPath(GccgoName)
}
func (gccgoToolchain) compiler() string {
checkGccgoBin()
return GccgoBin
}
func (gccgoToolchain) linker() string {
checkGccgoBin()
return GccgoBin
}
func (gccgoToolchain) ar() string {
ar := cfg.Getenv("AR")
if ar == "" {
ar = "ar"
}
return ar
}
func checkGccgoBin() {
if gccgoErr == nil {
return
}
fmt.Fprintf(os.Stderr, "cmd/go: gccgo: %s\n", gccgoErr)
base.SetExitStatus(2)
base.Exit()
}
func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) {
p := a.Package
objdir := a.Objdir
out := "_go_.o"
ofile = objdir + out
gcargs := []string{"-g"}
gcargs = append(gcargs, b.gccArchArgs()...)
gcargs = append(gcargs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build")
gcargs = append(gcargs, "-gno-record-gcc-switches")
if pkgpath := gccgoPkgpath(p); pkgpath != "" {
gcargs = append(gcargs, "-fgo-pkgpath="+pkgpath)
}
if p.Internal.LocalPrefix != "" {
gcargs = append(gcargs, "-fgo-relative-import-path="+p.Internal.LocalPrefix)
}
args := str.StringList(tools.compiler(), "-c", gcargs, "-o", ofile, forcedGccgoflags)
if importcfg != nil {
if b.gccSupportsFlag(args[:1], "-fgo-importcfg=/dev/null") {
if err := b.writeFile(objdir+"importcfg", importcfg); err != nil {
return "", nil, err
}
args = append(args, "-fgo-importcfg="+objdir+"importcfg")
} else {
root := objdir + "_importcfgroot_"
if err := buildImportcfgSymlinks(b, root, importcfg); err != nil {
return "", nil, err
}
args = append(args, "-I", root)
}
}
if embedcfg != nil && b.gccSupportsFlag(args[:1], "-fgo-embedcfg=/dev/null") {
if err := b.writeFile(objdir+"embedcfg", embedcfg); err != nil {
return "", nil, err
}
args = append(args, "-fgo-embedcfg="+objdir+"embedcfg")
}
if b.gccSupportsFlag(args[:1], "-ffile-prefix-map=a=b") {
if cfg.BuildTrimpath {
args = append(args, "-ffile-prefix-map="+base.Cwd+"=.")
args = append(args, "-ffile-prefix-map="+b.WorkDir+"=/tmp/go-build")
}
if fsys.OverlayFile != "" {
for _, name := range gofiles {
absPath := mkAbs(p.Dir, name)
overlayPath, ok := fsys.OverlayPath(absPath)
if !ok {
continue
}
toPath := absPath
// gccgo only applies the last matching rule, so also handle the case where
// BuildTrimpath is true and the path is relative to base.Cwd.
if cfg.BuildTrimpath && str.HasFilePathPrefix(toPath, base.Cwd) {
toPath = "." + toPath[len(base.Cwd):]
}
args = append(args, "-ffile-prefix-map="+overlayPath+"="+toPath)
}
}
}
args = append(args, a.Package.Internal.Gccgoflags...)
for _, f := range gofiles {
f := mkAbs(p.Dir, f)
// Overlay files if necessary.
// See comment on gctoolchain.gc about overlay TODOs
f, _ = fsys.OverlayPath(f)
args = append(args, f)
}
output, err = b.runOut(a, p.Dir, nil, args)
return ofile, output, err
}
// buildImportcfgSymlinks builds in root a tree of symlinks
// implementing the directives from importcfg.
// This serves as a temporary transition mechanism until
// we can depend on gccgo reading an importcfg directly.
// (The Go 1.9 and later gc compilers already do.)
func buildImportcfgSymlinks(b *Builder, root string, importcfg []byte) error {
for lineNum, line := range strings.Split(string(importcfg), "\n") {
lineNum++ // 1-based
line = strings.TrimSpace(line)
if line == "" {
continue
}
if line == "" || strings.HasPrefix(line, "#") {
continue
}
var verb, args string
if i := strings.Index(line, " "); i < 0 {
verb = line
} else {
verb, args = line[:i], strings.TrimSpace(line[i+1:])
}
var before, after string
if i := strings.Index(args, "="); i >= 0 {
before, after = args[:i], args[i+1:]
}
switch verb {
default:
base.Fatalf("importcfg:%d: unknown directive %q", lineNum, verb)
case "packagefile":
if before == "" || after == "" {
return fmt.Errorf(`importcfg:%d: invalid packagefile: syntax is "packagefile path=filename": %s`, lineNum, line)
}
archive := gccgoArchive(root, before)
if err := b.Mkdir(filepath.Dir(archive)); err != nil {
return err
}
if err := b.Symlink(after, archive); err != nil {
return err
}
case "importmap":
if before == "" || after == "" {
return fmt.Errorf(`importcfg:%d: invalid importmap: syntax is "importmap old=new": %s`, lineNum, line)
}
beforeA := gccgoArchive(root, before)
afterA := gccgoArchive(root, after)
if err := b.Mkdir(filepath.Dir(beforeA)); err != nil {
return err
}
if err := b.Mkdir(filepath.Dir(afterA)); err != nil {
return err
}
if err := b.Symlink(afterA, beforeA); err != nil {
return err
}
case "packageshlib":
return fmt.Errorf("gccgo -importcfg does not support shared libraries")
}
}
return nil
}
func (tools gccgoToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) {
p := a.Package
var ofiles []string
for _, sfile := range sfiles {
base := filepath.Base(sfile)
ofile := a.Objdir + base[:len(base)-len(".s")] + ".o"
ofiles = append(ofiles, ofile)
sfile, _ = fsys.OverlayPath(mkAbs(p.Dir, sfile))
defs := []string{"-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch}
if pkgpath := tools.gccgoCleanPkgpath(b, p); pkgpath != "" {
defs = append(defs, `-D`, `GOPKGPATH=`+pkgpath)
}
defs = tools.maybePIC(defs)
defs = append(defs, b.gccArchArgs()...)
err := b.run(a, p.Dir, p.ImportPath, nil, tools.compiler(), "-xassembler-with-cpp", "-I", a.Objdir, "-c", "-o", ofile, defs, sfile)
if err != nil {
return nil, err
}
}
return ofiles, nil
}
func (gccgoToolchain) symabis(b *Builder, a *Action, sfiles []string) (string, error) {
return "", nil
}
func gccgoArchive(basedir, imp string) string {
end := filepath.FromSlash(imp + ".a")
afile := filepath.Join(basedir, end)
// add "lib" to the final element
return filepath.Join(filepath.Dir(afile), "lib"+filepath.Base(afile))
}
func (tools gccgoToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error {
p := a.Package
objdir := a.Objdir
var absOfiles []string
for _, f := range ofiles {
absOfiles = append(absOfiles, mkAbs(objdir, f))
}
var arArgs []string
if cfg.Goos == "aix" && cfg.Goarch == "ppc64" {
// AIX puts both 32-bit and 64-bit objects in the same archive.
// Tell the AIX "ar" command to only care about 64-bit objects.
arArgs = []string{"-X64"}
}
absAfile := mkAbs(objdir, afile)
// Try with D modifier first, then without if that fails.
output, err := b.runOut(a, p.Dir, nil, tools.ar(), arArgs, "rcD", absAfile, absOfiles)
if err != nil {
return b.run(a, p.Dir, p.ImportPath, nil, tools.ar(), arArgs, "rc", absAfile, absOfiles)
}
if len(output) > 0 {
// Show the output if there is any even without errors.
b.showOutput(a, p.Dir, p.ImportPath, b.processOutput(output))
}
return nil
}
func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string, allactions []*Action, buildmode, desc string) error {
// gccgo needs explicit linking with all package dependencies,
// and all LDFLAGS from cgo dependencies.
afiles := []string{}
shlibs := []string{}
ldflags := b.gccArchArgs()
cgoldflags := []string{}
usesCgo := false
cxx := false
objc := false
fortran := false
if root.Package != nil {
cxx = len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
objc = len(root.Package.MFiles) > 0
fortran = len(root.Package.FFiles) > 0
}
readCgoFlags := func(flagsFile string) error {
flags, err := os.ReadFile(flagsFile)
if err != nil {
return err
}
const ldflagsPrefix = "_CGO_LDFLAGS="
for _, line := range strings.Split(string(flags), "\n") {
if strings.HasPrefix(line, ldflagsPrefix) {
newFlags := strings.Fields(line[len(ldflagsPrefix):])
for _, flag := range newFlags {
// Every _cgo_flags file has -g and -O2 in _CGO_LDFLAGS
// but they don't mean anything to the linker so filter
// them out.
if flag != "-g" && !strings.HasPrefix(flag, "-O") {
cgoldflags = append(cgoldflags, flag)
}
}
}
}
return nil
}
var arArgs []string
if cfg.Goos == "aix" && cfg.Goarch == "ppc64" {
// AIX puts both 32-bit and 64-bit objects in the same archive.
// Tell the AIX "ar" command to only care about 64-bit objects.
arArgs = []string{"-X64"}
}
newID := 0
readAndRemoveCgoFlags := func(archive string) (string, error) {
newID++
newArchive := root.Objdir + fmt.Sprintf("_pkg%d_.a", newID)
if err := b.copyFile(newArchive, archive, 0666, false); err != nil {
return "", err
}
if cfg.BuildN || cfg.BuildX {
b.Showcmd("", "ar d %s _cgo_flags", newArchive)
if cfg.BuildN {
// TODO(rsc): We could do better about showing the right _cgo_flags even in -n mode.
// Either the archive is already built and we can read them out,
// or we're printing commands to build the archive and can
// forward the _cgo_flags directly to this step.
return "", nil
}
}
err := b.run(root, root.Objdir, desc, nil, tools.ar(), arArgs, "x", newArchive, "_cgo_flags")
if err != nil {
return "", err
}
err = b.run(root, ".", desc, nil, tools.ar(), arArgs, "d", newArchive, "_cgo_flags")
if err != nil {
return "", err
}
err = readCgoFlags(filepath.Join(root.Objdir, "_cgo_flags"))
if err != nil {
return "", err
}
return newArchive, nil
}
// If using -linkshared, find the shared library deps.
haveShlib := make(map[string]bool)
targetBase := filepath.Base(root.Target)
if cfg.BuildLinkshared {
for _, a := range root.Deps {
p := a.Package
if p == nil || p.Shlib == "" {
continue
}
// The .a we are linking into this .so
// will have its Shlib set to this .so.
// Don't start thinking we want to link
// this .so into itself.
base := filepath.Base(p.Shlib)
if base != targetBase {
haveShlib[base] = true
}
}
}
// Arrange the deps into afiles and shlibs.
addedShlib := make(map[string]bool)
for _, a := range root.Deps {
p := a.Package
if p != nil && p.Shlib != "" && haveShlib[filepath.Base(p.Shlib)] {
// This is a package linked into a shared
// library that we will put into shlibs.
continue
}
if haveShlib[filepath.Base(a.Target)] {
// This is a shared library we want to link against.
if !addedShlib[a.Target] {
shlibs = append(shlibs, a.Target)
addedShlib[a.Target] = true
}
continue
}
if p != nil {
target := a.built
if p.UsesCgo() || p.UsesSwig() {
var err error
target, err = readAndRemoveCgoFlags(target)
if err != nil {
continue
}
}
afiles = append(afiles, target)
}
}
for _, a := range allactions {
// Gather CgoLDFLAGS, but not from standard packages.
// The go tool can dig up runtime/cgo from GOROOT and
// think that it should use its CgoLDFLAGS, but gccgo
// doesn't use runtime/cgo.
if a.Package == nil {
continue
}
if !a.Package.Standard {
cgoldflags = append(cgoldflags, a.Package.CgoLDFLAGS...)
}
if len(a.Package.CgoFiles) > 0 {
usesCgo = true
}
if a.Package.UsesSwig() {
usesCgo = true
}
if len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0 {
cxx = true
}
if len(a.Package.MFiles) > 0 {
objc = true
}
if len(a.Package.FFiles) > 0 {
fortran = true
}
}
wholeArchive := []string{"-Wl,--whole-archive"}
noWholeArchive := []string{"-Wl,--no-whole-archive"}
if cfg.Goos == "aix" {
wholeArchive = nil
noWholeArchive = nil
}
ldflags = append(ldflags, wholeArchive...)
ldflags = append(ldflags, afiles...)
ldflags = append(ldflags, noWholeArchive...)
ldflags = append(ldflags, cgoldflags...)
ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...)
if root.Package != nil {
ldflags = append(ldflags, root.Package.CgoLDFLAGS...)
}
if cfg.Goos != "aix" {
ldflags = str.StringList("-Wl,-(", ldflags, "-Wl,-)")
}
if root.buildID != "" {
// On systems that normally use gold or the GNU linker,
// use the --build-id option to write a GNU build ID note.
switch cfg.Goos {
case "android", "dragonfly", "linux", "netbsd":
ldflags = append(ldflags, fmt.Sprintf("-Wl,--build-id=0x%x", root.buildID))
}
}
var rLibPath string
if cfg.Goos == "aix" {
rLibPath = "-Wl,-blibpath="
} else {
rLibPath = "-Wl,-rpath="
}
for _, shlib := range shlibs {
ldflags = append(
ldflags,
"-L"+filepath.Dir(shlib),
rLibPath+filepath.Dir(shlib),
"-l"+strings.TrimSuffix(
strings.TrimPrefix(filepath.Base(shlib), "lib"),
".so"))
}
var realOut string
goLibBegin := str.StringList(wholeArchive, "-lgolibbegin", noWholeArchive)
switch buildmode {
case "exe":
if usesCgo && cfg.Goos == "linux" {
ldflags = append(ldflags, "-Wl,-E")
}
case "c-archive":
// Link the Go files into a single .o, and also link
// in -lgolibbegin.
//
// We need to use --whole-archive with -lgolibbegin
// because it doesn't define any symbols that will
// cause the contents to be pulled in; it's just
// initialization code.
//
// The user remains responsible for linking against
// -lgo -lpthread -lm in the final link. We can't use
// -r to pick them up because we can't combine
// split-stack and non-split-stack code in a single -r
// link, and libgo picks up non-split-stack code from
// libffi.
ldflags = append(ldflags, "-Wl,-r", "-nostdlib")
ldflags = append(ldflags, goLibBegin...)
if nopie := b.gccNoPie([]string{tools.linker()}); nopie != "" {
ldflags = append(ldflags, nopie)
}
// We are creating an object file, so we don't want a build ID.
if root.buildID == "" {
ldflags = b.disableBuildID(ldflags)
}
realOut = out
out = out + ".o"
case "c-shared":
ldflags = append(ldflags, "-shared", "-nostdlib")
ldflags = append(ldflags, goLibBegin...)
ldflags = append(ldflags, "-lgo", "-lgcc_s", "-lgcc", "-lc", "-lgcc")
case "shared":
if cfg.Goos != "aix" {
ldflags = append(ldflags, "-zdefs")
}
ldflags = append(ldflags, "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc")
default:
base.Fatalf("-buildmode=%s not supported for gccgo", buildmode)
}
switch buildmode {
case "exe", "c-shared":
if cxx {
ldflags = append(ldflags, "-lstdc++")
}
if objc {
ldflags = append(ldflags, "-lobjc")
}
if fortran {
fc := cfg.Getenv("FC")
if fc == "" {
fc = "gfortran"
}
// support gfortran out of the box and let others pass the correct link options
// via CGO_LDFLAGS
if strings.Contains(fc, "gfortran") {
ldflags = append(ldflags, "-lgfortran")
}
}
}
if err := b.run(root, ".", desc, nil, tools.linker(), "-o", out, ldflags, forcedGccgoflags, root.Package.Internal.Gccgoflags); err != nil {
return err
}
switch buildmode {
case "c-archive":
if err := b.run(root, ".", desc, nil, tools.ar(), arArgs, "rc", realOut, out); err != nil {
return err
}
}
return nil
}
func (tools gccgoToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
return tools.link(b, root, out, importcfg, root.Deps, ldBuildmode, root.Package.ImportPath)
}
func (tools gccgoToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error {
return tools.link(b, root, out, importcfg, allactions, "shared", out)
}
func (tools gccgoToolchain) cc(b *Builder, a *Action, ofile, cfile string) error {
p := a.Package
inc := filepath.Join(cfg.GOROOT, "pkg", "include")
cfile = mkAbs(p.Dir, cfile)
defs := []string{"-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch}
defs = append(defs, b.gccArchArgs()...)
if pkgpath := tools.gccgoCleanPkgpath(b, p); pkgpath != "" {
defs = append(defs, `-D`, `GOPKGPATH="`+pkgpath+`"`)
}
compiler := envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
if b.gccSupportsFlag(compiler, "-fsplit-stack") {
defs = append(defs, "-fsplit-stack")
}
defs = tools.maybePIC(defs)
if b.gccSupportsFlag(compiler, "-ffile-prefix-map=a=b") {
defs = append(defs, "-ffile-prefix-map="+base.Cwd+"=.")
defs = append(defs, "-ffile-prefix-map="+b.WorkDir+"=/tmp/go-build")
} else if b.gccSupportsFlag(compiler, "-fdebug-prefix-map=a=b") {
defs = append(defs, "-fdebug-prefix-map="+b.WorkDir+"=/tmp/go-build")
}
if b.gccSupportsFlag(compiler, "-gno-record-gcc-switches") {
defs = append(defs, "-gno-record-gcc-switches")
}
return b.run(a, p.Dir, p.ImportPath, nil, compiler, "-Wall", "-g",
"-I", a.Objdir, "-I", inc, "-o", ofile, defs, "-c", cfile)
}
// maybePIC adds -fPIC to the list of arguments if needed.
func (tools gccgoToolchain) maybePIC(args []string) []string {
switch cfg.BuildBuildmode {
case "c-shared", "shared", "plugin":
args = append(args, "-fPIC")
}
return args
}
func gccgoPkgpath(p *load.Package) string {
if p.Internal.Build.IsCommand() && !p.Internal.ForceLibrary {
return ""
}
return p.ImportPath
}
var gccgoToSymbolFuncOnce sync.Once
var gccgoToSymbolFunc func(string) string
func (tools gccgoToolchain) gccgoCleanPkgpath(b *Builder, p *load.Package) string {
gccgoToSymbolFuncOnce.Do(func() {
fn, err := pkgpath.ToSymbolFunc(tools.compiler(), b.WorkDir)
if err != nil {
fmt.Fprintf(os.Stderr, "cmd/go: %v\n", err)
base.SetExitStatus(2)
base.Exit()
}
gccgoToSymbolFunc = fn
})
return gccgoToSymbolFunc(gccgoPkgpath(p))
}