]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd: support space and quotes in CC and CXX
authorJay Conrod <jayconrod@google.com>
Wed, 14 Jul 2021 23:57:24 +0000 (16:57 -0700)
committerJay Conrod <jayconrod@google.com>
Mon, 16 Aug 2021 20:23:11 +0000 (20:23 +0000)
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>
15 files changed:
src/cmd/cgo/gcc.go
src/cmd/cgo/main.go
src/cmd/compile/internal/ssa/stmtlines_test.go
src/cmd/dist/buildtool.go
src/cmd/go/internal/envcmd/env.go
src/cmd/go/internal/work/exec.go
src/cmd/go/internal/work/gc.go
src/cmd/go/internal/work/init.go
src/cmd/go/script_test.go
src/cmd/go/testdata/script/cgo_path_space_quote.txt [new file with mode: 0644]
src/cmd/internal/dwarf/dwarf.go
src/cmd/link/dwarf_test.go
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/main.go
src/cmd/link/link_test.go

index a73e998877af812762afcc4716c17b78adf7f065..92adb1ed9cc60cda8eabcaeef2fa58d688cebd42 100644 (file)
@@ -23,10 +23,13 @@ import (
        "internal/xcoff"
        "math"
        "os"
+       "os/exec"
        "strconv"
        "strings"
        "unicode"
        "unicode/utf8"
+
+       "cmd/internal/str"
 )
 
 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())
        }
        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
@@ -457,7 +460,7 @@ func (p *Package) guessKinds(f *File) []*Name {
        }
 
        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 {
@@ -488,7 +491,7 @@ func (p *Package) guessKinds(f *File) []*Name {
                // to users debugging preamble mistakes. See issue 8442.
                preambleErrors := p.gccErrors([]byte(f.Preamble))
                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")
@@ -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)
 }
 
-// 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
 // during the initial build as defaultCC.
 // 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.
-       if ret := strings.Fields(os.Getenv("CC")); len(ret) > 0 {
-               return ret
+       value := os.Getenv("CC")
+       if value == "" {
+               // Try $GCC if set, since that's what we used to use.
+               value = os.Getenv("GCC")
+       }
+       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")
        }
-       // Try $GCC if set, since that's what we used to use.
-       if ret := strings.Fields(os.Getenv("GCC")); len(ret) > 0 {
-               return ret
+       if _, err := exec.LookPath(args[0]); err != nil {
+               return nil, fmt.Errorf("C compiler %q not found: %v", args[0], err)
        }
-       return strings.Fields(defaultCC(goos, goarch))
+       return args[:len(args):len(args)], nil
 }
 
 // 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
 // the input.
 func (p *Package) gccCmd() []string {
-       c := append(p.gccBaseCmd(),
+       c := append(gccBaseCmd,
                "-w",          // no warnings
                "-Wno-error",  // warnings are not errors
                "-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
 // and its included files.
 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()...)
        stdout, _ := runGcc(stdin, append(append(base, p.GccOptions...), "-"))
        return stdout
index c6a0c525e64f212bd51bb7a5eb317e46a085ffbc..14642b7576b0122e0252960c498fe9ed11a21826 100644 (file)
@@ -21,7 +21,6 @@ import (
        "io"
        "io/ioutil"
        "os"
-       "os/exec"
        "path/filepath"
        "reflect"
        "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 goarch, goos, gomips, gomips64 string
+var gccBaseCmd []string
 
 func main() {
        objabi.AddVersionFlag() // -V
@@ -305,10 +305,10 @@ func main() {
        p := newPackage(args[:i])
 
        // We need a C compiler to be available. Check this.
-       gccName := p.gccBaseCmd()[0]
-       _, err := exec.LookPath(gccName)
+       var err error
+       gccBaseCmd, err = checkGCCBaseCmd()
        if err != nil {
-               fatalf("C compiler %q not found: %v", gccName, err)
+               fatalf("%v", err)
                os.Exit(2)
        }
 
index a510d0b3d0607c268d76b444790ed7744bd1a802..843db8c07ef409a3abdc485dfe9b4221f64fd4cc 100644 (file)
@@ -2,6 +2,7 @@ package ssa_test
 
 import (
        cmddwarf "cmd/internal/dwarf"
+       "cmd/internal/str"
        "debug/dwarf"
        "debug/elf"
        "debug/macho"
@@ -57,7 +58,11 @@ func TestStmtLines(t *testing.T) {
                if extld == "" {
                        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 {
                        t.Fatal(err)
                }
index 26b33e389fe9283aabd5c0c8f44fba88fa44ea8c..320c62f8505aa8bfaa68bb1d7bf81ac5c3d89207 100644 (file)
@@ -47,6 +47,7 @@ var bootstrapDirs = []string{
        "cmd/internal/objabi",
        "cmd/internal/pkgpath",
        "cmd/internal/src",
+       "cmd/internal/str",
        "cmd/internal/sys",
        "cmd/link",
        "cmd/link/internal/...",
index 1553d263914541f05a639e03b8041969ba244301..483ce2a4f5c59ea46e297a2c3dc7b3f4bd87a1f4 100644 (file)
@@ -26,6 +26,7 @@ import (
        "cmd/go/internal/load"
        "cmd/go/internal/modload"
        "cmd/go/internal/work"
+       "cmd/internal/str"
 )
 
 var CmdEnv = &base.Command{
@@ -104,13 +105,13 @@ func MkEnv() []cfg.EnvVar {
                env = append(env, cfg.EnvVar{Name: key, Value: val})
        }
 
-       cc := cfg.DefaultCC(cfg.Goos, cfg.Goarch)
-       if env := strings.Fields(cfg.Getenv("CC")); len(env) > 0 {
-               cc = env[0]
+       cc := cfg.Getenv("CC")
+       if cc == "" {
+               cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
        }
-       cxx := cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
-       if env := strings.Fields(cfg.Getenv("CXX")); len(env) > 0 {
-               cxx = env[0]
+       cxx := cfg.Getenv("CXX")
+       if cxx == "" {
+               cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
        }
        env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")})
        env = append(env, cfg.EnvVar{Name: "CC", Value: cc})
@@ -457,10 +458,23 @@ func checkEnvWrite(key, val string) error {
                if !filepath.IsAbs(val) && val != "" {
                        return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
                }
-       // Make sure CC and CXX are absolute paths
-       case "CC", "CXX", "GOMODCACHE":
-               if !filepath.IsAbs(val) && val != "" && val != filepath.Base(val) {
-                       return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, val)
+       case "GOMODCACHE":
+               if !filepath.IsAbs(val) && val != "" {
+                       return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", 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])
                }
        }
 
index 2aa099bf1716adde033ffc6953a4e1b47d67d0ac..f7fae9fdd9617272311e4f1acaf30702333e22bb 100644 (file)
@@ -1487,6 +1487,8 @@ func (b *Builder) getPkgConfigFlags(p *load.Package) (cflags, ldflags []string,
                        return nil, nil, errPrintedOutput
                }
                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))
                        if err := checkLinkerFlags("LDFLAGS", "pkg-config --libs", ldflags); err != nil {
                                return nil, nil, err
@@ -2429,12 +2431,6 @@ func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flag
        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
 // defaultCC is defined in zdefaultcc.go, written by cmd/dist.
 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.
 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.
 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.
 func (b *Builder) fcExe() []string {
-       return b.compilerExe(cfg.Getenv("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
+       return envList("FC", "gfortran")
 }
 
 // compilerCmd returns a command line prefix for the given environment
 // variable and using the default command when the variable is empty.
 func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []string {
-       // NOTE: env.go's mkEnv knows that the first three
-       // strings returned are "gcc", "-I", incdir (and cuts them off).
-       a := []string{compiler[0], "-I", incdir}
-       a = append(a, compiler[1:]...)
+       a := append(compiler, "-I", incdir)
 
        // Definitely want -fPIC but on Windows gcc complains
        // "-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
 // 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 {
        v := cfg.Getenv(key)
        if v == "" {
                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.
index 70ca5d69f8378f018df76b72259eda801669625a..74e14d0065d5dda234295c54ac0699c27e147322 100644 (file)
@@ -545,33 +545,18 @@ func packInternal(afile string, ofiles []string) error {
 }
 
 // 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 {
                if f == "-extld" || strings.HasPrefix(f, "-extld=") {
                        // don't override -extld if supplied
-                       return ldflags
+                       return ldflags, nil
                }
        }
-       ldflags = append(ldflags, "-extld="+compiler[0])
-       if len(compiler) > 1 {
-               extldflags := false
-               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
-                       }
-               }
-               if !extldflags {
-                       ldflags = append(ldflags, "-extldflags="+add)
-               }
+       joined, err := str.JoinAndQuoteFields(compiler)
+       if err != nil {
+               return nil, err
        }
-       return ldflags
+       return append(ldflags, "-extld="+joined), nil
 }
 
 // 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, 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,
        // 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 {
                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 {
                if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries
                        continue
index 37a3e2d0ffd1232368863992603432fbd783e828..022137390f8fb32e8613ff5aaf79581a775dbf3b 100644 (file)
@@ -11,6 +11,7 @@ import (
        "cmd/go/internal/cfg"
        "cmd/go/internal/fsys"
        "cmd/go/internal/modload"
+       "cmd/internal/str"
        "cmd/internal/sys"
        "flag"
        "fmt"
@@ -39,9 +40,18 @@ func BuildInit() {
                cfg.BuildPkgdir = p
        }
 
-       // Make sure CC and CXX are absolute paths
-       for _, key := range []string{"CC", "CXX"} {
-               if path := cfg.Getenv(key); !filepath.IsAbs(path) && path != "" && path != filepath.Base(path) {
+       // Make sure CC, CXX, and FC are absolute paths.
+       for _, key := range []string{"CC", "CXX", "FC"} {
+               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)
                }
        }
index 9ca297e89b4e5b439b11b1b0facd0915e48595d7..8a7c77a46fa0a35fa0fc3789897ca0bdf6c5d30f 100644 (file)
@@ -184,6 +184,7 @@ func (ts *testScript) setup() {
                "devnull=" + os.DevNull,
                "goversion=" + goVersion(ts),
                ":=" + string(os.PathListSeparator),
+               "/=" + string(os.PathSeparator),
        }
        if !testenv.HasExternalNetwork() {
                ts.env = append(ts.env, "TESTGONETWORK=panic", "TESTGOVCS=panic")
diff --git a/src/cmd/go/testdata/script/cgo_path_space_quote.txt b/src/cmd/go/testdata/script/cgo_path_space_quote.txt
new file mode 100644 (file)
index 0000000..3b89bfb
--- /dev/null
@@ -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)
+}
index 860c7d6c0d9dc134b79f244ce7bf3c03ad4ab168..4e163db020f959751c7fcc79ae8c9838116013e9 100644 (file)
@@ -1617,8 +1617,10 @@ func (s byChildIndex) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 // current extld.
 // AIX ld doesn't support DWARF with -bnoobjreorder with version
 // prior to 7.2.2.
-func IsDWARFEnabledOnAIXLd(extld string) (bool, error) {
-       out, err := exec.Command(extld, "-Wl,-V").CombinedOutput()
+func IsDWARFEnabledOnAIXLd(extld []string) (bool, error) {
+       name, args := extld[0], extld[1:]
+       args = append(args, "-Wl,-V")
+       out, err := exec.Command(name, args...).CombinedOutput()
        if err != nil {
                // The normal output should display ld version and
                // then fails because ".main" is not defined:
index 3ca59bd47f025cf1eac24eac43fddec1700c2348..f7bbb014d9e48e446c8c611516eddbc82472cb3e 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bytes"
        cmddwarf "cmd/internal/dwarf"
        "cmd/internal/objfile"
+       "cmd/internal/str"
        "debug/dwarf"
        "internal/testenv"
        "os"
@@ -67,8 +68,11 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string)
                        if extld == "" {
                                extld = "gcc"
                        }
-                       var err error
-                       expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extld)
+                       extldArgs, err := str.SplitQuotedFields(extld)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
                        if err != nil {
                                t.Fatal(err)
                        }
index 894e5afe6387754f60763d7d76e44a11d68f0eb0..494fea5e724c1dae4578e6664e2f79356278621f 100644 (file)
@@ -464,23 +464,24 @@ func loadinternal(ctxt *Link, name string) *sym.Library {
 }
 
 // extld returns the current external linker.
-func (ctxt *Link) extld() string {
-       if *flagExtld == "" {
-               *flagExtld = "gcc"
+func (ctxt *Link) extld() []string {
+       if len(flagExtld) == 0 {
+               flagExtld = []string{"gcc"}
        }
-       return *flagExtld
+       return flagExtld
 }
 
 // findLibPathCmd uses cmd command to find gcc library libname.
 // It returns library full path if found, or "none" if not found.
 func (ctxt *Link) findLibPathCmd(cmd, libname string) string {
        extld := ctxt.extld()
-       args := hostlinkArchArgs(ctxt.Arch)
+       name, args := extld[0], extld[1:]
+       args = append(args, hostlinkArchArgs(ctxt.Arch)...)
        args = append(args, cmd)
        if ctxt.Debugvlog != 0 {
                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 ctxt.Debugvlog != 0 {
                        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
-       argv = append(argv, ctxt.extld())
+       argv = append(argv, ctxt.extld()...)
        argv = append(argv, hostlinkArchArgs(ctxt.Arch)...)
 
        if *FlagS || debug_s {
@@ -1403,7 +1404,9 @@ func (ctxt *Link) hostlink() {
                        // If gold is not installed, gcc will silently switch
                        // back to ld.bfd. So we parse the version information
                        // 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 !bytes.Contains(out, []byte("GNU gold")) {
                                        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"
 
                // 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 !bytes.Contains(out, []byte("GNU ld")) {
                                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")
 
                extld := ctxt.extld()
+               name, args := extld[0], extld[1:]
                // Get starting files.
                getPathFile := func(file string) string {
-                       args := []string{"-maix64", "--print-file-name=" + file}
-                       out, err := exec.Command(extld, args...).CombinedOutput()
+                       args := append(args, "-maix64", "--print-file-name="+file)
+                       out, err := exec.Command(name, args...).CombinedOutput()
                        if err != nil {
                                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)
                checkStatic(p)
        }
        if ctxt.HeadType == objabi.Hwindows {
                // Determine which linker we're using. Add in the extldflags in
                // 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
                if out, err := cmd.CombinedOutput(); err == nil {
                        if bytes.Contains(out, []byte("LLD ")) {
@@ -1720,8 +1730,7 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
        flags := hostlinkArchArgs(arch)
        keep := false
        skip := false
-       extldflags := strings.Fields(*flagExtldflags)
-       for _, f := range append(extldflags, ldflag...) {
+       for _, f := range append(flagExtldflags, ldflag...) {
                if keep {
                        flags = append(flags, f)
                        keep = false
index cba0e3d81feaea6fafcba964c200b50d96e6d714..33b03b50248972bd11e75309faf83a69cf08a2fc 100644 (file)
@@ -34,6 +34,7 @@ import (
        "bufio"
        "cmd/internal/goobj"
        "cmd/internal/objabi"
+       "cmd/internal/str"
        "cmd/internal/sys"
        "cmd/link/internal/benchmark"
        "flag"
@@ -53,6 +54,8 @@ var (
 
 func init() {
        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.
@@ -72,8 +75,8 @@ var (
        flagLibGCC     = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable")
        flagTmpdir     = flag.String("tmpdir", "", "use `directory` for temporary files")
 
-       flagExtld      = flag.String("extld", "", "use `linker` when linking in external mode")
-       flagExtldflags = flag.String("extldflags", "", "pass `flags` to external linker")
+       flagExtld      str.QuotedStringListFlag
+       flagExtldflags str.QuotedStringListFlag
        flagExtar      = flag.String("extar", "", "archive program for buildmode=c-archive")
 
        flagA             = flag.Bool("a", false, "no-op (deprecated)")
index fed9c7bc3fc433124055700ac063c898c7e43ed4..2b0b2dc4a17f932fe6d4e4e968f15eed8cbb4986 100644 (file)
@@ -282,8 +282,8 @@ func TestBuildForTvOS(t *testing.T) {
                "-isysroot", strings.TrimSpace(string(sdkPath)),
                "-mtvos-version-min=12.0",
                "-fembed-bitcode",
-               "-framework", "CoreFoundation",
        }
+       CGO_LDFLAGS := []string{"-framework", "CoreFoundation"}
        lib := filepath.Join("testdata", "testBuildFortvOS", "lib.go")
        tmpDir := t.TempDir()
 
@@ -295,12 +295,14 @@ func TestBuildForTvOS(t *testing.T) {
                "GOARCH=arm64",
                "CC="+strings.Join(CC, " "),
                "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 {
                t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
        }
 
        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, ar, filepath.Join("testdata", "testBuildFortvOS", "main.m"))
        if out, err := link.CombinedOutput(); err != nil {