Skip to content

Commit 52e970b

Browse files
author
Jay Conrod
committed
[dev.cmdgo] 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 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]>
1 parent 3a69cef commit 52e970b

File tree

14 files changed

+206
-103
lines changed

14 files changed

+206
-103
lines changed

src/cmd/cgo/gcc.go

Lines changed: 33 additions & 13 deletions
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 6 additions & 1 deletion
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 24 additions & 10 deletions
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})
@@ -458,10 +459,23 @@ func checkEnvWrite(key, val string) error {
458459
if !filepath.IsAbs(val) && val != "" {
459460
return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
460461
}
461-
// Make sure CC and CXX are absolute paths
462-
case "CC", "CXX", "GOMODCACHE":
463-
if !filepath.IsAbs(val) && val != "" && val != filepath.Base(val) {
464-
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, val)
462+
case "GOMODCACHE":
463+
if !filepath.IsAbs(val) && val != "" {
464+
return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
465+
}
466+
case "CC", "CXX":
467+
if val == "" {
468+
break
469+
}
470+
args, err := str.SplitQuotedFields(val)
471+
if err != nil {
472+
return fmt.Errorf("invalid %s: %v", key, err)
473+
}
474+
if len(args) == 0 {
475+
return fmt.Errorf("%s entry cannot contain only space", key)
476+
}
477+
if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
478+
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
465479
}
466480
}
467481

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

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,8 @@ func (b *Builder) getPkgConfigFlags(p *load.Package) (cflags, ldflags []string,
14801480
return nil, nil, errPrintedOutput
14811481
}
14821482
if len(out) > 0 {
1483+
// NOTE: we don't attempt to parse quotes and unescapes here. pkg-config
1484+
// is typically used within shell backticks, which treats quotes literally.
14831485
ldflags = strings.Fields(string(out))
14841486
if err := checkLinkerFlags("LDFLAGS", "pkg-config --libs", ldflags); err != nil {
14851487
return nil, nil, err
@@ -2422,12 +2424,6 @@ func (b *Builder) gccld(a *Action, p *load.Package, objdir, outfile string, flag
24222424
return err
24232425
}
24242426

2425-
// Grab these before main helpfully overwrites them.
2426-
var (
2427-
origCC = cfg.Getenv("CC")
2428-
origCXX = cfg.Getenv("CXX")
2429-
)
2430-
24312427
// gccCmd returns a gcc command line prefix
24322428
// defaultCC is defined in zdefaultcc.go, written by cmd/dist.
24332429
func (b *Builder) GccCmd(incdir, workdir string) []string {
@@ -2447,40 +2443,23 @@ func (b *Builder) gfortranCmd(incdir, workdir string) []string {
24472443

24482444
// ccExe returns the CC compiler setting without all the extra flags we add implicitly.
24492445
func (b *Builder) ccExe() []string {
2450-
return b.compilerExe(origCC, cfg.DefaultCC(cfg.Goos, cfg.Goarch))
2446+
return envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
24512447
}
24522448

24532449
// cxxExe returns the CXX compiler setting without all the extra flags we add implicitly.
24542450
func (b *Builder) cxxExe() []string {
2455-
return b.compilerExe(origCXX, cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
2451+
return envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
24562452
}
24572453

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

24772459
// compilerCmd returns a command line prefix for the given environment
24782460
// variable and using the default command when the variable is empty.
24792461
func (b *Builder) compilerCmd(compiler []string, incdir, workdir string) []string {
2480-
// NOTE: env.go's mkEnv knows that the first three
2481-
// strings returned are "gcc", "-I", incdir (and cuts them off).
2482-
a := []string{compiler[0], "-I", incdir}
2483-
a = append(a, compiler[1:]...)
2462+
a := append(compiler, "-I", incdir)
24842463

24852464
// Definitely want -fPIC but on Windows gcc complains
24862465
// "-fPIC ignored for target (all code is position independent)"
@@ -2651,12 +2630,20 @@ func (b *Builder) gccArchArgs() []string {
26512630

26522631
// envList returns the value of the given environment variable broken
26532632
// into fields, using the default value when the variable is empty.
2633+
//
2634+
// The environment variable must be quoted correctly for
2635+
// str.SplitQuotedFields. This should be done before building
2636+
// anything, for example, in BuildInit.
26542637
func envList(key, def string) []string {
26552638
v := cfg.Getenv(key)
26562639
if v == "" {
26572640
v = def
26582641
}
2659-
return strings.Fields(v)
2642+
args, err := str.SplitQuotedFields(v)
2643+
if err != nil {
2644+
panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
2645+
}
2646+
return args
26602647
}
26612648

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

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

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -536,33 +536,18 @@ func packInternal(afile string, ofiles []string) error {
536536
}
537537

538538
// setextld sets the appropriate linker flags for the specified compiler.
539-
func setextld(ldflags []string, compiler []string) []string {
539+
func setextld(ldflags []string, compiler []string) ([]string, error) {
540540
for _, f := range ldflags {
541541
if f == "-extld" || strings.HasPrefix(f, "-extld=") {
542542
// don't override -extld if supplied
543-
return ldflags
543+
return ldflags, nil
544544
}
545545
}
546-
ldflags = append(ldflags, "-extld="+compiler[0])
547-
if len(compiler) > 1 {
548-
extldflags := false
549-
add := strings.Join(compiler[1:], " ")
550-
for i, f := range ldflags {
551-
if f == "-extldflags" && i+1 < len(ldflags) {
552-
ldflags[i+1] = add + " " + ldflags[i+1]
553-
extldflags = true
554-
break
555-
} else if strings.HasPrefix(f, "-extldflags=") {
556-
ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
557-
extldflags = true
558-
break
559-
}
560-
}
561-
if !extldflags {
562-
ldflags = append(ldflags, "-extldflags="+add)
563-
}
546+
joined, err := str.JoinAndQuoteFields(compiler)
547+
if err != nil {
548+
return nil, err
564549
}
565-
return ldflags
550+
return append(ldflags, "-extld="+joined), nil
566551
}
567552

568553
// pluginPath computes the package path for a plugin main package.
@@ -649,7 +634,10 @@ func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string)
649634
}
650635
ldflags = append(ldflags, forcedLdflags...)
651636
ldflags = append(ldflags, root.Package.Internal.Ldflags...)
652-
ldflags = setextld(ldflags, compiler)
637+
ldflags, err := setextld(ldflags, compiler)
638+
if err != nil {
639+
return err
640+
}
653641

654642
// On OS X when using external linking to build a shared library,
655643
// the argument passed here to -o ends up recorded in the final
@@ -693,7 +681,10 @@ func (gcToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action,
693681
} else {
694682
compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
695683
}
696-
ldflags = setextld(ldflags, compiler)
684+
ldflags, err := setextld(ldflags, compiler)
685+
if err != nil {
686+
return err
687+
}
697688
for _, d := range toplevelactions {
698689
if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries
699690
continue

0 commit comments

Comments
 (0)