Skip to content

Commit 742dcba

Browse files
author
Jay Conrod
committed
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 #41400 Fixes #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 <[email protected]> Trust: Michael Matloob <[email protected]> Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/341936 Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 41d991e commit 742dcba

File tree

15 files changed

+209
-104
lines changed

15 files changed

+209
-104
lines changed

src/cmd/cgo/gcc.go

+33-13
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ import (
2323
"internal/xcoff"
2424
"math"
2525
"os"
26+
"os/exec"
2627
"strconv"
2728
"strings"
2829
"unicode"
2930
"unicode/utf8"
31+
32+
"cmd/internal/str"
3033
)
3134

3235
var debugDefine = flag.Bool("debug-define", false, "print relevant #defines")
@@ -382,7 +385,7 @@ func (p *Package) guessKinds(f *File) []*Name {
382385
stderr = p.gccErrors(b.Bytes())
383386
}
384387
if stderr == "" {
385-
fatalf("%s produced no output\non input:\n%s", p.gccBaseCmd()[0], b.Bytes())
388+
fatalf("%s produced no output\non input:\n%s", gccBaseCmd[0], b.Bytes())
386389
}
387390

388391
completed := false
@@ -457,7 +460,7 @@ func (p *Package) guessKinds(f *File) []*Name {
457460
}
458461

459462
if !completed {
460-
fatalf("%s did not produce error at completed:1\non input:\n%s\nfull error output:\n%s", p.gccBaseCmd()[0], b.Bytes(), stderr)
463+
fatalf("%s did not produce error at completed:1\non input:\n%s\nfull error output:\n%s", gccBaseCmd[0], b.Bytes(), stderr)
461464
}
462465

463466
for i, n := range names {
@@ -488,7 +491,7 @@ func (p *Package) guessKinds(f *File) []*Name {
488491
// to users debugging preamble mistakes. See issue 8442.
489492
preambleErrors := p.gccErrors([]byte(f.Preamble))
490493
if len(preambleErrors) > 0 {
491-
error_(token.NoPos, "\n%s errors for preamble:\n%s", p.gccBaseCmd()[0], preambleErrors)
494+
error_(token.NoPos, "\n%s errors for preamble:\n%s", gccBaseCmd[0], preambleErrors)
492495
}
493496

494497
fatalf("unresolved names")
@@ -1545,20 +1548,37 @@ func gofmtPos(n ast.Expr, pos token.Pos) string {
15451548
return fmt.Sprintf("/*line :%d:%d*/%s", p.Line, p.Column, s)
15461549
}
15471550

1548-
// gccBaseCmd returns the start of the compiler command line.
1551+
// checkGCCBaseCmd returns the start of the compiler command line.
15491552
// It uses $CC if set, or else $GCC, or else the compiler recorded
15501553
// during the initial build as defaultCC.
15511554
// defaultCC is defined in zdefaultcc.go, written by cmd/dist.
1552-
func (p *Package) gccBaseCmd() []string {
1555+
//
1556+
// The compiler command line is split into arguments on whitespace. Quotes
1557+
// are understood, so arguments may contain whitespace.
1558+
//
1559+
// checkGCCBaseCmd confirms that the compiler exists in PATH, returning
1560+
// an error if it does not.
1561+
func checkGCCBaseCmd() ([]string, error) {
15531562
// Use $CC if set, since that's what the build uses.
1554-
if ret := strings.Fields(os.Getenv("CC")); len(ret) > 0 {
1555-
return ret
1563+
value := os.Getenv("CC")
1564+
if value == "" {
1565+
// Try $GCC if set, since that's what we used to use.
1566+
value = os.Getenv("GCC")
1567+
}
1568+
if value == "" {
1569+
value = defaultCC(goos, goarch)
1570+
}
1571+
args, err := str.SplitQuotedFields(value)
1572+
if err != nil {
1573+
return nil, err
1574+
}
1575+
if len(args) == 0 {
1576+
return nil, errors.New("CC not set and no default found")
15561577
}
1557-
// Try $GCC if set, since that's what we used to use.
1558-
if ret := strings.Fields(os.Getenv("GCC")); len(ret) > 0 {
1559-
return ret
1578+
if _, err := exec.LookPath(args[0]); err != nil {
1579+
return nil, fmt.Errorf("C compiler %q not found: %v", args[0], err)
15601580
}
1561-
return strings.Fields(defaultCC(goos, goarch))
1581+
return args[:len(args):len(args)], nil
15621582
}
15631583

15641584
// gccMachine returns the gcc -m flag to use, either "-m32", "-m64" or "-marm".
@@ -1604,7 +1624,7 @@ func gccTmp() string {
16041624
// gccCmd returns the gcc command line to use for compiling
16051625
// the input.
16061626
func (p *Package) gccCmd() []string {
1607-
c := append(p.gccBaseCmd(),
1627+
c := append(gccBaseCmd,
16081628
"-w", // no warnings
16091629
"-Wno-error", // warnings are not errors
16101630
"-o"+gccTmp(), // write object to tmp
@@ -2005,7 +2025,7 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
20052025
// #defines that gcc encountered while processing the input
20062026
// and its included files.
20072027
func (p *Package) gccDefines(stdin []byte) string {
2008-
base := append(p.gccBaseCmd(), "-E", "-dM", "-xc")
2028+
base := append(gccBaseCmd, "-E", "-dM", "-xc")
20092029
base = append(base, p.gccMachine()...)
20102030
stdout, _ := runGcc(stdin, append(append(base, p.GccOptions...), "-"))
20112031
return stdout

src/cmd/cgo/main.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"io"
2222
"io/ioutil"
2323
"os"
24-
"os/exec"
2524
"path/filepath"
2625
"reflect"
2726
"runtime"
@@ -248,6 +247,7 @@ var importSyscall = flag.Bool("import_syscall", true, "import syscall in generat
248247
var trimpath = flag.String("trimpath", "", "applies supplied rewrites or trims prefixes to recorded source file paths")
249248

250249
var goarch, goos, gomips, gomips64 string
250+
var gccBaseCmd []string
251251

252252
func main() {
253253
objabi.AddVersionFlag() // -V
@@ -305,10 +305,10 @@ func main() {
305305
p := newPackage(args[:i])
306306

307307
// We need a C compiler to be available. Check this.
308-
gccName := p.gccBaseCmd()[0]
309-
_, err := exec.LookPath(gccName)
308+
var err error
309+
gccBaseCmd, err = checkGCCBaseCmd()
310310
if err != nil {
311-
fatalf("C compiler %q not found: %v", gccName, err)
311+
fatalf("%v", err)
312312
os.Exit(2)
313313
}
314314

src/cmd/compile/internal/ssa/stmtlines_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ssa_test
22

33
import (
44
cmddwarf "cmd/internal/dwarf"
5+
"cmd/internal/str"
56
"debug/dwarf"
67
"debug/elf"
78
"debug/macho"
@@ -57,7 +58,11 @@ func TestStmtLines(t *testing.T) {
5758
if extld == "" {
5859
extld = "gcc"
5960
}
60-
enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extld)
61+
extldArgs, err := str.SplitQuotedFields(extld)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
6166
if err != nil {
6267
t.Fatal(err)
6368
}

src/cmd/dist/buildtool.go

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var bootstrapDirs = []string{
4747
"cmd/internal/objabi",
4848
"cmd/internal/pkgpath",
4949
"cmd/internal/src",
50+
"cmd/internal/str",
5051
"cmd/internal/sys",
5152
"cmd/link",
5253
"cmd/link/internal/...",

src/cmd/go/internal/envcmd/env.go

+24-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"cmd/go/internal/load"
2727
"cmd/go/internal/modload"
2828
"cmd/go/internal/work"
29+
"cmd/internal/str"
2930
)
3031

3132
var CmdEnv = &base.Command{
@@ -104,13 +105,13 @@ func MkEnv() []cfg.EnvVar {
104105
env = append(env, cfg.EnvVar{Name: key, Value: val})
105106
}
106107

107-
cc := cfg.DefaultCC(cfg.Goos, cfg.Goarch)
108-
if env := strings.Fields(cfg.Getenv("CC")); len(env) > 0 {
109-
cc = env[0]
108+
cc := cfg.Getenv("CC")
109+
if cc == "" {
110+
cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
110111
}
111-
cxx := cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
112-
if env := strings.Fields(cfg.Getenv("CXX")); len(env) > 0 {
113-
cxx = env[0]
112+
cxx := cfg.Getenv("CXX")
113+
if cxx == "" {
114+
cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
114115
}
115116
env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")})
116117
env = append(env, cfg.EnvVar{Name: "CC", Value: cc})
@@ -457,10 +458,23 @@ func checkEnvWrite(key, val string) error {
457458
if !filepath.IsAbs(val) && val != "" {
458459
return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
459460
}
460-
// Make sure CC and CXX are absolute paths
461-
case "CC", "CXX", "GOMODCACHE":
462-
if !filepath.IsAbs(val) && val != "" && val != filepath.Base(val) {
463-
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, val)
461+
case "GOMODCACHE":
462+
if !filepath.IsAbs(val) && val != "" {
463+
return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
464+
}
465+
case "CC", "CXX":
466+
if val == "" {
467+
break
468+
}
469+
args, err := str.SplitQuotedFields(val)
470+
if err != nil {
471+
return fmt.Errorf("invalid %s: %v", key, err)
472+
}
473+
if len(args) == 0 {
474+
return fmt.Errorf("%s entry cannot contain only space", key)
475+
}
476+
if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
477+
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
464478
}
465479
}
466480

src/cmd/go/internal/work/exec.go

+15-28
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,8 @@ func (b *Builder) getPkgConfigFlags(p *load.Package) (cflags, ldflags []string,
14871487
return nil, nil, errPrintedOutput
14881488
}
14891489
if len(out) > 0 {
1490+
// NOTE: we don't attempt to parse quotes and unescapes here. pkg-config
1491+
// is typically used within shell backticks, which treats quotes literally.
14901492
ldflags = strings.Fields(string(out))
14911493
if err := checkLinkerFlags("LDFLAGS", "pkg-config --libs", ldflags); err != nil {
14921494
return nil, nil, err
@@ -2429,12 +2431,6 @@ func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flag
24292431
return err
24302432
}
24312433

2432-
// Grab these before main helpfully overwrites them.
2433-
var (
2434-
origCC = cfg.Getenv("CC")
2435-
origCXX = cfg.Getenv("CXX")
2436-
)
2437-
24382434
// gccCmd returns a gcc command line prefix
24392435
// defaultCC is defined in zdefaultcc.go, written by cmd/dist.
24402436
func (b *Builder) GccCmd(incdir, workdir string) []string {
@@ -2454,40 +2450,23 @@ func (b *Builder) gfortranCmd(incdir, workdir string) []string {
24542450

24552451
// ccExe returns the CC compiler setting without all the extra flags we add implicitly.
24562452
func (b *Builder) ccExe() []string {
2457-
return b.compilerExe(origCC, cfg.DefaultCC(cfg.Goos, cfg.Goarch))
2453+
return envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
24582454
}
24592455

24602456
// cxxExe returns the CXX compiler setting without all the extra flags we add implicitly.
24612457
func (b *Builder) cxxExe() []string {
2462-
return b.compilerExe(origCXX, cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
2458+
return envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
24632459
}
24642460

24652461
// fcExe returns the FC compiler setting without all the extra flags we add implicitly.
24662462
func (b *Builder) fcExe() []string {
2467-
return b.compilerExe(cfg.Getenv("FC"), "gfortran")
2468-
}
2469-
2470-
// compilerExe returns the compiler to use given an
2471-
// environment variable setting (the value not the name)
2472-
// and a default. The resulting slice is usually just the name
2473-
// of the compiler but can have additional arguments if they
2474-
// were present in the environment value.
2475-
// For example if CC="gcc -DGOPHER" then the result is ["gcc", "-DGOPHER"].
2476-
func (b *Builder) compilerExe(envValue string, def string) []string {
2477-
compiler := strings.Fields(envValue)
2478-
if len(compiler) == 0 {
2479-
compiler = strings.Fields(def)
2480-
}
2481-
return compiler
2463+
return envList("FC", "gfortran")
24822464
}
24832465

24842466
// compilerCmd returns a command line prefix for the given environment
24852467
// variable and using the default command when the variable is empty.
24862468
func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []string {
2487-
// NOTE: env.go's mkEnv knows that the first three
2488-
// strings returned are "gcc", "-I", incdir (and cuts them off).
2489-
a := []string{compiler[0], "-I", incdir}
2490-
a = append(a, compiler[1:]...)
2469+
a := append(compiler, "-I", incdir)
24912470

24922471
// Definitely want -fPIC but on Windows gcc complains
24932472
// "-fPIC ignored for target (all code is position independent)"
@@ -2658,12 +2637,20 @@ func (b *Builder) gccArchArgs() []string {
26582637

26592638
// envList returns the value of the given environment variable broken
26602639
// into fields, using the default value when the variable is empty.
2640+
//
2641+
// The environment variable must be quoted correctly for
2642+
// str.SplitQuotedFields. This should be done before building
2643+
// anything, for example, in BuildInit.
26612644
func envList(key, def string) []string {
26622645
v := cfg.Getenv(key)
26632646
if v == "" {
26642647
v = def
26652648
}
2666-
return strings.Fields(v)
2649+
args, err := str.SplitQuotedFields(v)
2650+
if err != nil {
2651+
panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
2652+
}
2653+
return args
26672654
}
26682655

26692656
// CFlags returns the flags to use when invoking the C, C++ or Fortran compilers, or cgo.

src/cmd/go/internal/work/gc.go

+14-23
Original file line numberDiff line numberDiff line change
@@ -545,33 +545,18 @@ func packInternal(afile string, ofiles []string) error {
545545
}
546546

547547
// setextld sets the appropriate linker flags for the specified compiler.
548-
func setextld(ldflags []string, compiler []string) []string {
548+
func setextld(ldflags []string, compiler []string) ([]string, error) {
549549
for _, f := range ldflags {
550550
if f == "-extld" || strings.HasPrefix(f, "-extld=") {
551551
// don't override -extld if supplied
552-
return ldflags
552+
return ldflags, nil
553553
}
554554
}
555-
ldflags = append(ldflags, "-extld="+compiler[0])
556-
if len(compiler) > 1 {
557-
extldflags := false
558-
add := strings.Join(compiler[1:], " ")
559-
for i, f := range ldflags {
560-
if f == "-extldflags" && i+1 < len(ldflags) {
561-
ldflags[i+1] = add + " " + ldflags[i+1]
562-
extldflags = true
563-
break
564-
} else if strings.HasPrefix(f, "-extldflags=") {
565-
ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
566-
extldflags = true
567-
break
568-
}
569-
}
570-
if !extldflags {
571-
ldflags = append(ldflags, "-extldflags="+add)
572-
}
555+
joined, err := str.JoinAndQuoteFields(compiler)
556+
if err != nil {
557+
return nil, err
573558
}
574-
return ldflags
559+
return append(ldflags, "-extld="+joined), nil
575560
}
576561

577562
// 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)
658643
}
659644
ldflags = append(ldflags, forcedLdflags...)
660645
ldflags = append(ldflags, root.Package.Internal.Ldflags...)
661-
ldflags = setextld(ldflags, compiler)
646+
ldflags, err := setextld(ldflags, compiler)
647+
if err != nil {
648+
return err
649+
}
662650

663651
// On OS X when using external linking to build a shared library,
664652
// 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,
702690
} else {
703691
compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
704692
}
705-
ldflags = setextld(ldflags, compiler)
693+
ldflags, err := setextld(ldflags, compiler)
694+
if err != nil {
695+
return err
696+
}
706697
for _, d := range toplevelactions {
707698
if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries
708699
continue

0 commit comments

Comments
 (0)