aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go
diff options
context:
space:
mode:
authorJay Conrod <jayconrod@google.com>2021-07-14 16:57:24 -0700
committerJay Conrod <jayconrod@google.com>2021-07-30 17:08:28 +0000
commit52e970b1c86f18806232adb0e1f42636645d21ff (patch)
treee987a5ba5830265f322419bf7306f03127351ab7 /src/cmd/go
parent3a69cef65a856afd1f8b9d5c22e6729d0f4d84ac (diff)
downloadgo-52e970b1c86f18806232adb0e1f42636645d21ff.tar.gz
go-52e970b1c86f18806232adb0e1f42636645d21ff.zip
[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 golang/go#41400 Fixes golang/go#43078 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>
Diffstat (limited to 'src/cmd/go')
-rw-r--r--src/cmd/go/internal/envcmd/env.go34
-rw-r--r--src/cmd/go/internal/work/exec.go43
-rw-r--r--src/cmd/go/internal/work/gc.go37
-rw-r--r--src/cmd/go/internal/work/init.go16
-rw-r--r--src/cmd/go/script_test.go1
-rw-r--r--src/cmd/go/testdata/script/cgo_path_space_quote.txt56
6 files changed, 123 insertions, 64 deletions
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index f68090f21f..5c45e34330 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -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})
@@ -458,10 +459,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])
}
}
diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go
index d51ec144f3..6c646ebf28 100644
--- a/src/cmd/go/internal/work/exec.go
+++ b/src/cmd/go/internal/work/exec.go
@@ -1480,6 +1480,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
@@ -2422,12 +2424,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 {
@@ -2447,40 +2443,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)"
@@ -2651,12 +2630,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.
diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go
index c5f3fc264f..1fc825de47 100644
--- a/src/cmd/go/internal/work/gc.go
+++ b/src/cmd/go/internal/work/gc.go
@@ -536,33 +536,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.
@@ -649,7 +634,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
@@ -693,7 +681,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
diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go
index 37a3e2d0ff..022137390f 100644
--- a/src/cmd/go/internal/work/init.go
+++ b/src/cmd/go/internal/work/init.go
@@ -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)
}
}
diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go
index 9ca297e89b..8a7c77a46f 100644
--- a/src/cmd/go/script_test.go
+++ b/src/cmd/go/script_test.go
@@ -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
index 0000000000..3b89bfb800
--- /dev/null
+++ b/src/cmd/go/testdata/script/cgo_path_space_quote.txt
@@ -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)
+}