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"
"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")
}
// 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 value == "" {
value = defaultCC(goos, goarch)
}
return strings.Fields(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".
@ -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

View 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)
}

View 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)
}

View 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/...",

View 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])
}
}

View 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.

View 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

View 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)
}
}

View 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")

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.
// 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:

View 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)
}

View 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

View 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)")

View 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 {