cmd: support space and quotes in CC and CXX

The CC and CXX environment variables now support spaces and quotes
(both double and single). This fixes two issues: first, if CC is a
single path that contains spaces (like 'c:\Program
Files\gcc\bin\gcc.exe'), that should now work if the space is quoted
or escaped (#41400). Second, if CC or CXX has multiple arguments (like
'gcc -O2'), they are now split correctly, and the arguments are passed
before other arguments when invoking the C compiler. Previously,
strings.Fields was used to split arguments, and the arguments were
placed later in the command line. (#43078).

Fixes golang/go#41400
Fixes golang/go#43078

NOTE: This change also includes a fix (CL 341929) for a test that was
broken by the original CL. Commit message for the fix is below.

[dev.cmdgo] cmd/link: fix TestBuildForTvOS

This test was broken in CL 334732 on darwin.

The test invokes 'go build' with a CC containing the arguments
-framework CoreFoundation. Previously, the go command split CC on
whitespace, and inserted the arguments after the command line when
running CC directly. Those arguments weren't passed to cgo though,
so cgo ran CC without -framework CoreFoundation (or any of the other
flags).

In CL 334732, we pass CC through to cgo, and cgo splits arguments
using str.SplitQuotedFields. So -framework CoreFoundation actually
gets passed to the C compiler. It appears that -framework flags are
only meant to be used in linking operations, so when cgo invokes clang
with -E (run preprocessor only), clang emits an error that -framework
is unused.

This change fixes the test by moving -framework CoreFoundation out of
CC and into CGO_LDFLAGS.

Change-Id: I2d5d89ddb19c94adef65982a8137b01f037d5c11
Reviewed-on: https://go-review.googlesource.com/c/go/+/334732
Trust: Jay Conrod <jayconrod@google.com>
Trust: Michael Matloob <matloob@golang.org>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/go/+/341936
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Jay Conrod 2021-07-14 16:57:24 -07:00
parent 41d991e4e1
commit 742dcba7bb
15 changed files with 209 additions and 104 deletions

View file

@ -23,10 +23,13 @@ import (
"internal/xcoff" "internal/xcoff"
"math" "math"
"os" "os"
"os/exec"
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"cmd/internal/str"
) )
var debugDefine = flag.Bool("debug-define", false, "print relevant #defines") var debugDefine = flag.Bool("debug-define", false, "print relevant #defines")
@ -382,7 +385,7 @@ func (p *Package) guessKinds(f *File) []*Name {
stderr = p.gccErrors(b.Bytes()) stderr = p.gccErrors(b.Bytes())
} }
if stderr == "" { if stderr == "" {
fatalf("%s produced no output\non input:\n%s", p.gccBaseCmd()[0], b.Bytes()) fatalf("%s produced no output\non input:\n%s", gccBaseCmd[0], b.Bytes())
} }
completed := false completed := false
@ -457,7 +460,7 @@ func (p *Package) guessKinds(f *File) []*Name {
} }
if !completed { if !completed {
fatalf("%s did not produce error at completed:1\non input:\n%s\nfull error output:\n%s", p.gccBaseCmd()[0], b.Bytes(), stderr) fatalf("%s did not produce error at completed:1\non input:\n%s\nfull error output:\n%s", gccBaseCmd[0], b.Bytes(), stderr)
} }
for i, n := range names { for i, n := range names {
@ -488,7 +491,7 @@ func (p *Package) guessKinds(f *File) []*Name {
// to users debugging preamble mistakes. See issue 8442. // to users debugging preamble mistakes. See issue 8442.
preambleErrors := p.gccErrors([]byte(f.Preamble)) preambleErrors := p.gccErrors([]byte(f.Preamble))
if len(preambleErrors) > 0 { if len(preambleErrors) > 0 {
error_(token.NoPos, "\n%s errors for preamble:\n%s", p.gccBaseCmd()[0], preambleErrors) error_(token.NoPos, "\n%s errors for preamble:\n%s", gccBaseCmd[0], preambleErrors)
} }
fatalf("unresolved names") fatalf("unresolved names")
@ -1545,20 +1548,37 @@ func gofmtPos(n ast.Expr, pos token.Pos) string {
return fmt.Sprintf("/*line :%d:%d*/%s", p.Line, p.Column, s) return fmt.Sprintf("/*line :%d:%d*/%s", p.Line, p.Column, s)
} }
// gccBaseCmd returns the start of the compiler command line. // checkGCCBaseCmd returns the start of the compiler command line.
// It uses $CC if set, or else $GCC, or else the compiler recorded // It uses $CC if set, or else $GCC, or else the compiler recorded
// during the initial build as defaultCC. // during the initial build as defaultCC.
// defaultCC is defined in zdefaultcc.go, written by cmd/dist. // defaultCC is defined in zdefaultcc.go, written by cmd/dist.
func (p *Package) gccBaseCmd() []string { //
// The compiler command line is split into arguments on whitespace. Quotes
// are understood, so arguments may contain whitespace.
//
// checkGCCBaseCmd confirms that the compiler exists in PATH, returning
// an error if it does not.
func checkGCCBaseCmd() ([]string, error) {
// Use $CC if set, since that's what the build uses. // Use $CC if set, since that's what the build uses.
if ret := strings.Fields(os.Getenv("CC")); len(ret) > 0 { value := os.Getenv("CC")
return ret if value == "" {
}
// Try $GCC if set, since that's what we used to use. // Try $GCC if set, since that's what we used to use.
if ret := strings.Fields(os.Getenv("GCC")); len(ret) > 0 { value = os.Getenv("GCC")
return ret
} }
return strings.Fields(defaultCC(goos, goarch)) if value == "" {
value = defaultCC(goos, goarch)
}
args, err := str.SplitQuotedFields(value)
if err != nil {
return nil, err
}
if len(args) == 0 {
return nil, errors.New("CC not set and no default found")
}
if _, err := exec.LookPath(args[0]); err != nil {
return nil, fmt.Errorf("C compiler %q not found: %v", args[0], err)
}
return args[:len(args):len(args)], nil
} }
// gccMachine returns the gcc -m flag to use, either "-m32", "-m64" or "-marm". // gccMachine returns the gcc -m flag to use, either "-m32", "-m64" or "-marm".
@ -1604,7 +1624,7 @@ func gccTmp() string {
// gccCmd returns the gcc command line to use for compiling // gccCmd returns the gcc command line to use for compiling
// the input. // the input.
func (p *Package) gccCmd() []string { func (p *Package) gccCmd() []string {
c := append(p.gccBaseCmd(), c := append(gccBaseCmd,
"-w", // no warnings "-w", // no warnings
"-Wno-error", // warnings are not errors "-Wno-error", // warnings are not errors
"-o"+gccTmp(), // write object to tmp "-o"+gccTmp(), // write object to tmp
@ -2005,7 +2025,7 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
// #defines that gcc encountered while processing the input // #defines that gcc encountered while processing the input
// and its included files. // and its included files.
func (p *Package) gccDefines(stdin []byte) string { func (p *Package) gccDefines(stdin []byte) string {
base := append(p.gccBaseCmd(), "-E", "-dM", "-xc") base := append(gccBaseCmd, "-E", "-dM", "-xc")
base = append(base, p.gccMachine()...) base = append(base, p.gccMachine()...)
stdout, _ := runGcc(stdin, append(append(base, p.GccOptions...), "-")) stdout, _ := runGcc(stdin, append(append(base, p.GccOptions...), "-"))
return stdout return stdout

View file

@ -21,7 +21,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
@ -248,6 +247,7 @@ var importSyscall = flag.Bool("import_syscall", true, "import syscall in generat
var trimpath = flag.String("trimpath", "", "applies supplied rewrites or trims prefixes to recorded source file paths") var trimpath = flag.String("trimpath", "", "applies supplied rewrites or trims prefixes to recorded source file paths")
var goarch, goos, gomips, gomips64 string var goarch, goos, gomips, gomips64 string
var gccBaseCmd []string
func main() { func main() {
objabi.AddVersionFlag() // -V objabi.AddVersionFlag() // -V
@ -305,10 +305,10 @@ func main() {
p := newPackage(args[:i]) p := newPackage(args[:i])
// We need a C compiler to be available. Check this. // We need a C compiler to be available. Check this.
gccName := p.gccBaseCmd()[0] var err error
_, err := exec.LookPath(gccName) gccBaseCmd, err = checkGCCBaseCmd()
if err != nil { if err != nil {
fatalf("C compiler %q not found: %v", gccName, err) fatalf("%v", err)
os.Exit(2) os.Exit(2)
} }

View file

@ -2,6 +2,7 @@ package ssa_test
import ( import (
cmddwarf "cmd/internal/dwarf" cmddwarf "cmd/internal/dwarf"
"cmd/internal/str"
"debug/dwarf" "debug/dwarf"
"debug/elf" "debug/elf"
"debug/macho" "debug/macho"
@ -57,7 +58,11 @@ func TestStmtLines(t *testing.T) {
if extld == "" { if extld == "" {
extld = "gcc" extld = "gcc"
} }
enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extld) extldArgs, err := str.SplitQuotedFields(extld)
if err != nil {
t.Fatal(err)
}
enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -47,6 +47,7 @@ var bootstrapDirs = []string{
"cmd/internal/objabi", "cmd/internal/objabi",
"cmd/internal/pkgpath", "cmd/internal/pkgpath",
"cmd/internal/src", "cmd/internal/src",
"cmd/internal/str",
"cmd/internal/sys", "cmd/internal/sys",
"cmd/link", "cmd/link",
"cmd/link/internal/...", "cmd/link/internal/...",

View file

@ -26,6 +26,7 @@ import (
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str"
) )
var CmdEnv = &base.Command{ var CmdEnv = &base.Command{
@ -104,13 +105,13 @@ func MkEnv() []cfg.EnvVar {
env = append(env, cfg.EnvVar{Name: key, Value: val}) env = append(env, cfg.EnvVar{Name: key, Value: val})
} }
cc := cfg.DefaultCC(cfg.Goos, cfg.Goarch) cc := cfg.Getenv("CC")
if env := strings.Fields(cfg.Getenv("CC")); len(env) > 0 { if cc == "" {
cc = env[0] cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
} }
cxx := cfg.DefaultCXX(cfg.Goos, cfg.Goarch) cxx := cfg.Getenv("CXX")
if env := strings.Fields(cfg.Getenv("CXX")); len(env) > 0 { if cxx == "" {
cxx = env[0] cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
} }
env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")}) env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")})
env = append(env, cfg.EnvVar{Name: "CC", Value: cc}) env = append(env, cfg.EnvVar{Name: "CC", Value: cc})
@ -457,10 +458,23 @@ func checkEnvWrite(key, val string) error {
if !filepath.IsAbs(val) && val != "" { if !filepath.IsAbs(val) && val != "" {
return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val) return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
} }
// Make sure CC and CXX are absolute paths case "GOMODCACHE":
case "CC", "CXX", "GOMODCACHE": if !filepath.IsAbs(val) && val != "" {
if !filepath.IsAbs(val) && val != "" && val != filepath.Base(val) { return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, val) }
case "CC", "CXX":
if val == "" {
break
}
args, err := str.SplitQuotedFields(val)
if err != nil {
return fmt.Errorf("invalid %s: %v", key, err)
}
if len(args) == 0 {
return fmt.Errorf("%s entry cannot contain only space", key)
}
if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
} }
} }

View file

@ -1487,6 +1487,8 @@ func (b *Builder) getPkgConfigFlags(p *load.Package) (cflags, ldflags []string,
return nil, nil, errPrintedOutput return nil, nil, errPrintedOutput
} }
if len(out) > 0 { if len(out) > 0 {
// NOTE: we don't attempt to parse quotes and unescapes here. pkg-config
// is typically used within shell backticks, which treats quotes literally.
ldflags = strings.Fields(string(out)) ldflags = strings.Fields(string(out))
if err := checkLinkerFlags("LDFLAGS", "pkg-config --libs", ldflags); err != nil { if err := checkLinkerFlags("LDFLAGS", "pkg-config --libs", ldflags); err != nil {
return nil, nil, err return nil, nil, err
@ -2429,12 +2431,6 @@ func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flag
return err return err
} }
// Grab these before main helpfully overwrites them.
var (
origCC = cfg.Getenv("CC")
origCXX = cfg.Getenv("CXX")
)
// gccCmd returns a gcc command line prefix // gccCmd returns a gcc command line prefix
// defaultCC is defined in zdefaultcc.go, written by cmd/dist. // defaultCC is defined in zdefaultcc.go, written by cmd/dist.
func (b *Builder) GccCmd(incdir, workdir string) []string { func (b *Builder) GccCmd(incdir, workdir string) []string {
@ -2454,40 +2450,23 @@ func (b *Builder) gfortranCmd(incdir, workdir string) []string {
// ccExe returns the CC compiler setting without all the extra flags we add implicitly. // ccExe returns the CC compiler setting without all the extra flags we add implicitly.
func (b *Builder) ccExe() []string { func (b *Builder) ccExe() []string {
return b.compilerExe(origCC, cfg.DefaultCC(cfg.Goos, cfg.Goarch)) return envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
} }
// cxxExe returns the CXX compiler setting without all the extra flags we add implicitly. // cxxExe returns the CXX compiler setting without all the extra flags we add implicitly.
func (b *Builder) cxxExe() []string { func (b *Builder) cxxExe() []string {
return b.compilerExe(origCXX, cfg.DefaultCXX(cfg.Goos, cfg.Goarch)) return envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
} }
// fcExe returns the FC compiler setting without all the extra flags we add implicitly. // fcExe returns the FC compiler setting without all the extra flags we add implicitly.
func (b *Builder) fcExe() []string { func (b *Builder) fcExe() []string {
return b.compilerExe(cfg.Getenv("FC"), "gfortran") return envList("FC", "gfortran")
}
// compilerExe returns the compiler to use given an
// environment variable setting (the value not the name)
// and a default. The resulting slice is usually just the name
// of the compiler but can have additional arguments if they
// were present in the environment value.
// For example if CC="gcc -DGOPHER" then the result is ["gcc", "-DGOPHER"].
func (b *Builder) compilerExe(envValue string, def string) []string {
compiler := strings.Fields(envValue)
if len(compiler) == 0 {
compiler = strings.Fields(def)
}
return compiler
} }
// compilerCmd returns a command line prefix for the given environment // compilerCmd returns a command line prefix for the given environment
// variable and using the default command when the variable is empty. // variable and using the default command when the variable is empty.
func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []string { func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []string {
// NOTE: env.go's mkEnv knows that the first three a := append(compiler, "-I", incdir)
// strings returned are "gcc", "-I", incdir (and cuts them off).
a := []string{compiler[0], "-I", incdir}
a = append(a, compiler[1:]...)
// Definitely want -fPIC but on Windows gcc complains // Definitely want -fPIC but on Windows gcc complains
// "-fPIC ignored for target (all code is position independent)" // "-fPIC ignored for target (all code is position independent)"
@ -2658,12 +2637,20 @@ func (b *Builder) gccArchArgs() []string {
// envList returns the value of the given environment variable broken // envList returns the value of the given environment variable broken
// into fields, using the default value when the variable is empty. // into fields, using the default value when the variable is empty.
//
// The environment variable must be quoted correctly for
// str.SplitQuotedFields. This should be done before building
// anything, for example, in BuildInit.
func envList(key, def string) []string { func envList(key, def string) []string {
v := cfg.Getenv(key) v := cfg.Getenv(key)
if v == "" { if v == "" {
v = def v = def
} }
return strings.Fields(v) args, err := str.SplitQuotedFields(v)
if err != nil {
panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
}
return args
} }
// CFlags returns the flags to use when invoking the C, C++ or Fortran compilers, or cgo. // CFlags returns the flags to use when invoking the C, C++ or Fortran compilers, or cgo.

View file

@ -545,33 +545,18 @@ func packInternal(afile string, ofiles []string) error {
} }
// setextld sets the appropriate linker flags for the specified compiler. // setextld sets the appropriate linker flags for the specified compiler.
func setextld(ldflags []string, compiler []string) []string { func setextld(ldflags []string, compiler []string) ([]string, error) {
for _, f := range ldflags { for _, f := range ldflags {
if f == "-extld" || strings.HasPrefix(f, "-extld=") { if f == "-extld" || strings.HasPrefix(f, "-extld=") {
// don't override -extld if supplied // don't override -extld if supplied
return ldflags return ldflags, nil
} }
} }
ldflags = append(ldflags, "-extld="+compiler[0]) joined, err := str.JoinAndQuoteFields(compiler)
if len(compiler) > 1 { if err != nil {
extldflags := false return nil, err
add := strings.Join(compiler[1:], " ")
for i, f := range ldflags {
if f == "-extldflags" && i+1 < len(ldflags) {
ldflags[i+1] = add + " " + ldflags[i+1]
extldflags = true
break
} else if strings.HasPrefix(f, "-extldflags=") {
ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
extldflags = true
break
} }
} return append(ldflags, "-extld="+joined), nil
if !extldflags {
ldflags = append(ldflags, "-extldflags="+add)
}
}
return ldflags
} }
// pluginPath computes the package path for a plugin main package. // pluginPath computes the package path for a plugin main package.
@ -658,7 +643,10 @@ func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string)
} }
ldflags = append(ldflags, forcedLdflags...) ldflags = append(ldflags, forcedLdflags...)
ldflags = append(ldflags, root.Package.Internal.Ldflags...) ldflags = append(ldflags, root.Package.Internal.Ldflags...)
ldflags = setextld(ldflags, compiler) ldflags, err := setextld(ldflags, compiler)
if err != nil {
return err
}
// On OS X when using external linking to build a shared library, // On OS X when using external linking to build a shared library,
// the argument passed here to -o ends up recorded in the final // the argument passed here to -o ends up recorded in the final
@ -702,7 +690,10 @@ func (gcToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action,
} else { } else {
compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch)) compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
} }
ldflags = setextld(ldflags, compiler) ldflags, err := setextld(ldflags, compiler)
if err != nil {
return err
}
for _, d := range toplevelactions { for _, d := range toplevelactions {
if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries
continue continue

View file

@ -11,6 +11,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/internal/str"
"cmd/internal/sys" "cmd/internal/sys"
"flag" "flag"
"fmt" "fmt"
@ -39,9 +40,18 @@ func BuildInit() {
cfg.BuildPkgdir = p cfg.BuildPkgdir = p
} }
// Make sure CC and CXX are absolute paths // Make sure CC, CXX, and FC are absolute paths.
for _, key := range []string{"CC", "CXX"} { for _, key := range []string{"CC", "CXX", "FC"} {
if path := cfg.Getenv(key); !filepath.IsAbs(path) && path != "" && path != filepath.Base(path) { value := cfg.Getenv(key)
args, err := str.SplitQuotedFields(value)
if err != nil {
base.Fatalf("go %s: %s environment variable could not be parsed: %v", flag.Args()[0], key, err)
}
if len(args) == 0 {
continue
}
path := args[0]
if !filepath.IsAbs(path) && path != filepath.Base(path) {
base.Fatalf("go %s: %s environment variable is relative; must be absolute path: %s\n", flag.Args()[0], key, path) base.Fatalf("go %s: %s environment variable is relative; must be absolute path: %s\n", flag.Args()[0], key, path)
} }
} }

View file

@ -184,6 +184,7 @@ func (ts *testScript) setup() {
"devnull=" + os.DevNull, "devnull=" + os.DevNull,
"goversion=" + goVersion(ts), "goversion=" + goVersion(ts),
":=" + string(os.PathListSeparator), ":=" + string(os.PathListSeparator),
"/=" + string(os.PathSeparator),
} }
if !testenv.HasExternalNetwork() { if !testenv.HasExternalNetwork() {
ts.env = append(ts.env, "TESTGONETWORK=panic", "TESTGOVCS=panic") ts.env = append(ts.env, "TESTGONETWORK=panic", "TESTGOVCS=panic")

View file

@ -0,0 +1,56 @@
# This test checks that the CC environment variable may contain quotes and
# spaces. Arguments are normally split on spaces, tabs, newlines. If an
# argument contains these characters, the entire argument may be quoted
# with single or double quotes. This is the same as -gcflags and similar
# options.
[short] skip
[!exec:clang] [!exec:gcc] skip
env GOENV=$WORK/go.env
mkdir 'program files'
go build -o 'program files' './which cc/which cc.go'
[exec:clang] env CC='"'$PWD${/}program' 'files${/}which' 'cc"' 'clang
[!exec:clang] env CC='"'$PWD${/}program' 'files${/}which' 'cc"' 'gcc
go env CC
stdout 'program files[/\\]which cc" (clang|gcc)$'
go env -w CC=$CC
env CC=
go env CC
stdout 'program files[/\\]which cc" (clang|gcc)$'
go run .
-- go.mod --
module test
go 1.17
-- which cc/which cc.go --
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
args := append([]string{"-DWRAPPER_WAS_USED=1"}, os.Args[2:]...)
cmd := exec.Command(os.Args[1], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
-- hello.go --
package main
// int x = WRAPPER_WAS_USED;
import "C"
import "fmt"
func main() {
fmt.Println(C.x)
}

View file

@ -1617,8 +1617,10 @@ func (s byChildIndex) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// current extld. // current extld.
// AIX ld doesn't support DWARF with -bnoobjreorder with version // AIX ld doesn't support DWARF with -bnoobjreorder with version
// prior to 7.2.2. // prior to 7.2.2.
func IsDWARFEnabledOnAIXLd(extld string) (bool, error) { func IsDWARFEnabledOnAIXLd(extld []string) (bool, error) {
out, err := exec.Command(extld, "-Wl,-V").CombinedOutput() name, args := extld[0], extld[1:]
args = append(args, "-Wl,-V")
out, err := exec.Command(name, args...).CombinedOutput()
if err != nil { if err != nil {
// The normal output should display ld version and // The normal output should display ld version and
// then fails because ".main" is not defined: // then fails because ".main" is not defined:

View file

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
cmddwarf "cmd/internal/dwarf" cmddwarf "cmd/internal/dwarf"
"cmd/internal/objfile" "cmd/internal/objfile"
"cmd/internal/str"
"debug/dwarf" "debug/dwarf"
"internal/testenv" "internal/testenv"
"os" "os"
@ -67,8 +68,11 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string)
if extld == "" { if extld == "" {
extld = "gcc" extld = "gcc"
} }
var err error extldArgs, err := str.SplitQuotedFields(extld)
expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extld) if err != nil {
t.Fatal(err)
}
expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -464,23 +464,24 @@ func loadinternal(ctxt *Link, name string) *sym.Library {
} }
// extld returns the current external linker. // extld returns the current external linker.
func (ctxt *Link) extld() string { func (ctxt *Link) extld() []string {
if *flagExtld == "" { if len(flagExtld) == 0 {
*flagExtld = "gcc" flagExtld = []string{"gcc"}
} }
return *flagExtld return flagExtld
} }
// findLibPathCmd uses cmd command to find gcc library libname. // findLibPathCmd uses cmd command to find gcc library libname.
// It returns library full path if found, or "none" if not found. // It returns library full path if found, or "none" if not found.
func (ctxt *Link) findLibPathCmd(cmd, libname string) string { func (ctxt *Link) findLibPathCmd(cmd, libname string) string {
extld := ctxt.extld() extld := ctxt.extld()
args := hostlinkArchArgs(ctxt.Arch) name, args := extld[0], extld[1:]
args = append(args, hostlinkArchArgs(ctxt.Arch)...)
args = append(args, cmd) args = append(args, cmd)
if ctxt.Debugvlog != 0 { if ctxt.Debugvlog != 0 {
ctxt.Logf("%s %v\n", extld, args) ctxt.Logf("%s %v\n", extld, args)
} }
out, err := exec.Command(extld, args...).Output() out, err := exec.Command(name, args...).Output()
if err != nil { if err != nil {
if ctxt.Debugvlog != 0 { if ctxt.Debugvlog != 0 {
ctxt.Logf("not using a %s file because compiler failed\n%v\n%s\n", libname, err, out) ctxt.Logf("not using a %s file because compiler failed\n%v\n%s\n", libname, err, out)
@ -1242,7 +1243,7 @@ func (ctxt *Link) hostlink() {
} }
var argv []string var argv []string
argv = append(argv, ctxt.extld()) argv = append(argv, ctxt.extld()...)
argv = append(argv, hostlinkArchArgs(ctxt.Arch)...) argv = append(argv, hostlinkArchArgs(ctxt.Arch)...)
if *FlagS || debug_s { if *FlagS || debug_s {
@ -1403,7 +1404,9 @@ func (ctxt *Link) hostlink() {
// If gold is not installed, gcc will silently switch // If gold is not installed, gcc will silently switch
// back to ld.bfd. So we parse the version information // back to ld.bfd. So we parse the version information
// and provide a useful error if gold is missing. // and provide a useful error if gold is missing.
cmd := exec.Command(*flagExtld, "-fuse-ld=gold", "-Wl,--version") name, args := flagExtld[0], flagExtld[1:]
args = append(args, "-fuse-ld=gold", "-Wl,--version")
cmd := exec.Command(name, args...)
if out, err := cmd.CombinedOutput(); err == nil { if out, err := cmd.CombinedOutput(); err == nil {
if !bytes.Contains(out, []byte("GNU gold")) { if !bytes.Contains(out, []byte("GNU gold")) {
log.Fatalf("ARM external linker must be gold (issue #15696), but is not: %s", out) log.Fatalf("ARM external linker must be gold (issue #15696), but is not: %s", out)
@ -1416,7 +1419,9 @@ func (ctxt *Link) hostlink() {
altLinker = "bfd" altLinker = "bfd"
// Provide a useful error if ld.bfd is missing. // Provide a useful error if ld.bfd is missing.
cmd := exec.Command(*flagExtld, "-fuse-ld=bfd", "-Wl,--version") name, args := flagExtld[0], flagExtld[1:]
args = append(args, "-fuse-ld=bfd", "-Wl,--version")
cmd := exec.Command(name, args...)
if out, err := cmd.CombinedOutput(); err == nil { if out, err := cmd.CombinedOutput(); err == nil {
if !bytes.Contains(out, []byte("GNU ld")) { if !bytes.Contains(out, []byte("GNU ld")) {
log.Fatalf("ARM64 external linker must be ld.bfd (issue #35197), please install devel/binutils") log.Fatalf("ARM64 external linker must be ld.bfd (issue #35197), please install devel/binutils")
@ -1484,10 +1489,11 @@ func (ctxt *Link) hostlink() {
argv = append(argv, "/lib/crt0_64.o") argv = append(argv, "/lib/crt0_64.o")
extld := ctxt.extld() extld := ctxt.extld()
name, args := extld[0], extld[1:]
// Get starting files. // Get starting files.
getPathFile := func(file string) string { getPathFile := func(file string) string {
args := []string{"-maix64", "--print-file-name=" + file} args := append(args, "-maix64", "--print-file-name="+file)
out, err := exec.Command(extld, args...).CombinedOutput() out, err := exec.Command(name, args...).CombinedOutput()
if err != nil { if err != nil {
log.Fatalf("running %s failed: %v\n%s", extld, err, out) log.Fatalf("running %s failed: %v\n%s", extld, err, out)
} }
@ -1569,14 +1575,18 @@ func (ctxt *Link) hostlink() {
} }
} }
for _, p := range strings.Fields(*flagExtldflags) { for _, p := range flagExtldflags {
argv = append(argv, p) argv = append(argv, p)
checkStatic(p) checkStatic(p)
} }
if ctxt.HeadType == objabi.Hwindows { if ctxt.HeadType == objabi.Hwindows {
// Determine which linker we're using. Add in the extldflags in // Determine which linker we're using. Add in the extldflags in
// case used has specified "-fuse-ld=...". // case used has specified "-fuse-ld=...".
cmd := exec.Command(*flagExtld, *flagExtldflags, "-Wl,--version") extld := ctxt.extld()
name, args := extld[0], extld[1:]
args = append(args, flagExtldflags...)
args = append(args, "-Wl,--version")
cmd := exec.Command(name, args...)
usingLLD := false usingLLD := false
if out, err := cmd.CombinedOutput(); err == nil { if out, err := cmd.CombinedOutput(); err == nil {
if bytes.Contains(out, []byte("LLD ")) { if bytes.Contains(out, []byte("LLD ")) {
@ -1720,8 +1730,7 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
flags := hostlinkArchArgs(arch) flags := hostlinkArchArgs(arch)
keep := false keep := false
skip := false skip := false
extldflags := strings.Fields(*flagExtldflags) for _, f := range append(flagExtldflags, ldflag...) {
for _, f := range append(extldflags, ldflag...) {
if keep { if keep {
flags = append(flags, f) flags = append(flags, f)
keep = false keep = false

View file

@ -34,6 +34,7 @@ import (
"bufio" "bufio"
"cmd/internal/goobj" "cmd/internal/goobj"
"cmd/internal/objabi" "cmd/internal/objabi"
"cmd/internal/str"
"cmd/internal/sys" "cmd/internal/sys"
"cmd/link/internal/benchmark" "cmd/link/internal/benchmark"
"flag" "flag"
@ -53,6 +54,8 @@ var (
func init() { func init() {
flag.Var(&rpath, "r", "set the ELF dynamic linker search `path` to dir1:dir2:...") flag.Var(&rpath, "r", "set the ELF dynamic linker search `path` to dir1:dir2:...")
flag.Var(&flagExtld, "extld", "use `linker` when linking in external mode")
flag.Var(&flagExtldflags, "extldflags", "pass `flags` to external linker")
} }
// Flags used by the linker. The exported flags are used by the architecture-specific packages. // Flags used by the linker. The exported flags are used by the architecture-specific packages.
@ -72,8 +75,8 @@ var (
flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable") flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable")
flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files") flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files")
flagExtld = flag.String("extld", "", "use `linker` when linking in external mode") flagExtld str.QuotedStringListFlag
flagExtldflags = flag.String("extldflags", "", "pass `flags` to external linker") flagExtldflags str.QuotedStringListFlag
flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive") flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive")
flagA = flag.Bool("a", false, "no-op (deprecated)") flagA = flag.Bool("a", false, "no-op (deprecated)")

View file

@ -282,8 +282,8 @@ func TestBuildForTvOS(t *testing.T) {
"-isysroot", strings.TrimSpace(string(sdkPath)), "-isysroot", strings.TrimSpace(string(sdkPath)),
"-mtvos-version-min=12.0", "-mtvos-version-min=12.0",
"-fembed-bitcode", "-fembed-bitcode",
"-framework", "CoreFoundation",
} }
CGO_LDFLAGS := []string{"-framework", "CoreFoundation"}
lib := filepath.Join("testdata", "testBuildFortvOS", "lib.go") lib := filepath.Join("testdata", "testBuildFortvOS", "lib.go")
tmpDir := t.TempDir() tmpDir := t.TempDir()
@ -295,12 +295,14 @@ func TestBuildForTvOS(t *testing.T) {
"GOARCH=arm64", "GOARCH=arm64",
"CC="+strings.Join(CC, " "), "CC="+strings.Join(CC, " "),
"CGO_CFLAGS=", // ensure CGO_CFLAGS does not contain any flags. Issue #35459 "CGO_CFLAGS=", // ensure CGO_CFLAGS does not contain any flags. Issue #35459
"CGO_LDFLAGS="+strings.Join(CGO_LDFLAGS, " "),
) )
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
} }
link := exec.Command(CC[0], CC[1:]...) link := exec.Command(CC[0], CC[1:]...)
link.Args = append(link.Args, CGO_LDFLAGS...)
link.Args = append(link.Args, "-o", filepath.Join(tmpDir, "a.out")) // Avoid writing to package directory. link.Args = append(link.Args, "-o", filepath.Join(tmpDir, "a.out")) // Avoid writing to package directory.
link.Args = append(link.Args, ar, filepath.Join("testdata", "testBuildFortvOS", "main.m")) link.Args = append(link.Args, ar, filepath.Join("testdata", "testBuildFortvOS", "main.m"))
if out, err := link.CombinedOutput(); err != nil { if out, err := link.CombinedOutput(); err != nil {