1*9bb1b549SSpandan Das// Copyright 2017 The Bazel Authors. All rights reserved. 2*9bb1b549SSpandan Das// 3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License"); 4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License. 5*9bb1b549SSpandan Das// You may obtain a copy of the License at 6*9bb1b549SSpandan Das// 7*9bb1b549SSpandan Das// http://www.apache.org/licenses/LICENSE-2.0 8*9bb1b549SSpandan Das// 9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software 10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS, 11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and 13*9bb1b549SSpandan Das// limitations under the License. 14*9bb1b549SSpandan Das 15*9bb1b549SSpandan Daspackage main 16*9bb1b549SSpandan Das 17*9bb1b549SSpandan Dasimport ( 18*9bb1b549SSpandan Das "bytes" 19*9bb1b549SSpandan Das "errors" 20*9bb1b549SSpandan Das "flag" 21*9bb1b549SSpandan Das "fmt" 22*9bb1b549SSpandan Das "io" 23*9bb1b549SSpandan Das "io/ioutil" 24*9bb1b549SSpandan Das "log" 25*9bb1b549SSpandan Das "os" 26*9bb1b549SSpandan Das "os/exec" 27*9bb1b549SSpandan Das "path/filepath" 28*9bb1b549SSpandan Das "runtime" 29*9bb1b549SSpandan Das "strconv" 30*9bb1b549SSpandan Das "strings" 31*9bb1b549SSpandan Das) 32*9bb1b549SSpandan Das 33*9bb1b549SSpandan Dasvar ( 34*9bb1b549SSpandan Das // cgoEnvVars is the list of all cgo environment variable 35*9bb1b549SSpandan Das cgoEnvVars = []string{"CGO_CFLAGS", "CGO_CXXFLAGS", "CGO_CPPFLAGS", "CGO_LDFLAGS"} 36*9bb1b549SSpandan Das // cgoAbsEnvFlags are all the flags that need absolute path in cgoEnvVars 37*9bb1b549SSpandan Das cgoAbsEnvFlags = []string{"-I", "-L", "-isysroot", "-isystem", "-iquote", "-include", "-gcc-toolchain", "--sysroot", "-resource-dir", "-fsanitize-blacklist", "-fsanitize-ignorelist"} 38*9bb1b549SSpandan Das) 39*9bb1b549SSpandan Das 40*9bb1b549SSpandan Das// env holds a small amount of Go environment and toolchain information 41*9bb1b549SSpandan Das// which is common to multiple builders. Most Bazel-agnostic build information 42*9bb1b549SSpandan Das// is collected in go/build.Default though. 43*9bb1b549SSpandan Das// 44*9bb1b549SSpandan Das// See ./README.rst for more information about handling arguments and 45*9bb1b549SSpandan Das// environment variables. 46*9bb1b549SSpandan Dastype env struct { 47*9bb1b549SSpandan Das // sdk is the path to the Go SDK, which contains tools for the host 48*9bb1b549SSpandan Das // platform. This may be different than GOROOT. 49*9bb1b549SSpandan Das sdk string 50*9bb1b549SSpandan Das 51*9bb1b549SSpandan Das // installSuffix is the name of the directory below GOROOT/pkg that contains 52*9bb1b549SSpandan Das // the .a files for the standard library we should build against. 53*9bb1b549SSpandan Das // For example, linux_amd64_race. 54*9bb1b549SSpandan Das installSuffix string 55*9bb1b549SSpandan Das 56*9bb1b549SSpandan Das // verbose indicates whether subprocess command lines should be printed. 57*9bb1b549SSpandan Das verbose bool 58*9bb1b549SSpandan Das 59*9bb1b549SSpandan Das // workDirPath is a temporary work directory. It is created lazily. 60*9bb1b549SSpandan Das workDirPath string 61*9bb1b549SSpandan Das 62*9bb1b549SSpandan Das shouldPreserveWorkDir bool 63*9bb1b549SSpandan Das} 64*9bb1b549SSpandan Das 65*9bb1b549SSpandan Das// envFlags registers flags common to multiple builders and returns an env 66*9bb1b549SSpandan Das// configured with those flags. 67*9bb1b549SSpandan Dasfunc envFlags(flags *flag.FlagSet) *env { 68*9bb1b549SSpandan Das env := &env{} 69*9bb1b549SSpandan Das flags.StringVar(&env.sdk, "sdk", "", "Path to the Go SDK.") 70*9bb1b549SSpandan Das flags.Var(&tagFlag{}, "tags", "List of build tags considered true.") 71*9bb1b549SSpandan Das flags.StringVar(&env.installSuffix, "installsuffix", "", "Standard library under GOROOT/pkg") 72*9bb1b549SSpandan Das flags.BoolVar(&env.verbose, "v", false, "Whether subprocess command lines should be printed") 73*9bb1b549SSpandan Das flags.BoolVar(&env.shouldPreserveWorkDir, "work", false, "if true, the temporary work directory will be preserved") 74*9bb1b549SSpandan Das return env 75*9bb1b549SSpandan Das} 76*9bb1b549SSpandan Das 77*9bb1b549SSpandan Das// checkFlags checks whether env flags were set to valid values. checkFlags 78*9bb1b549SSpandan Das// should be called after parsing flags. 79*9bb1b549SSpandan Dasfunc (e *env) checkFlags() error { 80*9bb1b549SSpandan Das if e.sdk == "" { 81*9bb1b549SSpandan Das return errors.New("-sdk was not set") 82*9bb1b549SSpandan Das } 83*9bb1b549SSpandan Das return nil 84*9bb1b549SSpandan Das} 85*9bb1b549SSpandan Das 86*9bb1b549SSpandan Das// workDir returns a path to a temporary work directory. The same directory 87*9bb1b549SSpandan Das// is returned on multiple calls. The caller is responsible for cleaning 88*9bb1b549SSpandan Das// up the work directory by calling cleanup. 89*9bb1b549SSpandan Dasfunc (e *env) workDir() (path string, cleanup func(), err error) { 90*9bb1b549SSpandan Das if e.workDirPath != "" { 91*9bb1b549SSpandan Das return e.workDirPath, func() {}, nil 92*9bb1b549SSpandan Das } 93*9bb1b549SSpandan Das // Keep the stem "rules_go_work" in sync with reproducible_binary_test.go. 94*9bb1b549SSpandan Das e.workDirPath, err = ioutil.TempDir("", "rules_go_work-") 95*9bb1b549SSpandan Das if err != nil { 96*9bb1b549SSpandan Das return "", func() {}, err 97*9bb1b549SSpandan Das } 98*9bb1b549SSpandan Das if e.verbose { 99*9bb1b549SSpandan Das log.Printf("WORK=%s\n", e.workDirPath) 100*9bb1b549SSpandan Das } 101*9bb1b549SSpandan Das if e.shouldPreserveWorkDir { 102*9bb1b549SSpandan Das cleanup = func() {} 103*9bb1b549SSpandan Das } else { 104*9bb1b549SSpandan Das cleanup = func() { os.RemoveAll(e.workDirPath) } 105*9bb1b549SSpandan Das } 106*9bb1b549SSpandan Das return e.workDirPath, cleanup, nil 107*9bb1b549SSpandan Das} 108*9bb1b549SSpandan Das 109*9bb1b549SSpandan Das// goTool returns a slice containing the path to an executable at 110*9bb1b549SSpandan Das// $GOROOT/pkg/$GOOS_$GOARCH/$tool and additional arguments. 111*9bb1b549SSpandan Dasfunc (e *env) goTool(tool string, args ...string) []string { 112*9bb1b549SSpandan Das platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) 113*9bb1b549SSpandan Das toolPath := filepath.Join(e.sdk, "pkg", "tool", platform, tool) 114*9bb1b549SSpandan Das if runtime.GOOS == "windows" { 115*9bb1b549SSpandan Das toolPath += ".exe" 116*9bb1b549SSpandan Das } 117*9bb1b549SSpandan Das return append([]string{toolPath}, args...) 118*9bb1b549SSpandan Das} 119*9bb1b549SSpandan Das 120*9bb1b549SSpandan Das// goCmd returns a slice containing the path to the go executable 121*9bb1b549SSpandan Das// and additional arguments. 122*9bb1b549SSpandan Dasfunc (e *env) goCmd(cmd string, args ...string) []string { 123*9bb1b549SSpandan Das exe := filepath.Join(e.sdk, "bin", "go") 124*9bb1b549SSpandan Das if runtime.GOOS == "windows" { 125*9bb1b549SSpandan Das exe += ".exe" 126*9bb1b549SSpandan Das } 127*9bb1b549SSpandan Das return append([]string{exe, cmd}, args...) 128*9bb1b549SSpandan Das} 129*9bb1b549SSpandan Das 130*9bb1b549SSpandan Das// runCommand executes a subprocess that inherits stdout, stderr, and the 131*9bb1b549SSpandan Das// environment from this process. 132*9bb1b549SSpandan Dasfunc (e *env) runCommand(args []string) error { 133*9bb1b549SSpandan Das cmd := exec.Command(args[0], args[1:]...) 134*9bb1b549SSpandan Das // Redirecting stdout to stderr. This mirrors behavior in the go command: 135*9bb1b549SSpandan Das // https://go.googlesource.com/go/+/refs/tags/go1.15.2/src/cmd/go/internal/work/exec.go#1958 136*9bb1b549SSpandan Das buf := &bytes.Buffer{} 137*9bb1b549SSpandan Das cmd.Stdout = buf 138*9bb1b549SSpandan Das cmd.Stderr = buf 139*9bb1b549SSpandan Das err := runAndLogCommand(cmd, e.verbose) 140*9bb1b549SSpandan Das os.Stderr.Write(relativizePaths(buf.Bytes())) 141*9bb1b549SSpandan Das return err 142*9bb1b549SSpandan Das} 143*9bb1b549SSpandan Das 144*9bb1b549SSpandan Das// runCommandToFile executes a subprocess and writes stdout/stderr to the given 145*9bb1b549SSpandan Das// writers. 146*9bb1b549SSpandan Dasfunc (e *env) runCommandToFile(out, err io.Writer, args []string) error { 147*9bb1b549SSpandan Das cmd := exec.Command(args[0], args[1:]...) 148*9bb1b549SSpandan Das cmd.Stdout = out 149*9bb1b549SSpandan Das cmd.Stderr = err 150*9bb1b549SSpandan Das return runAndLogCommand(cmd, e.verbose) 151*9bb1b549SSpandan Das} 152*9bb1b549SSpandan Das 153*9bb1b549SSpandan Dasfunc absEnv(envNameList []string, argList []string) error { 154*9bb1b549SSpandan Das for _, envName := range envNameList { 155*9bb1b549SSpandan Das splitedEnv := strings.Fields(os.Getenv(envName)) 156*9bb1b549SSpandan Das absArgs(splitedEnv, argList) 157*9bb1b549SSpandan Das if err := os.Setenv(envName, strings.Join(splitedEnv, " ")); err != nil { 158*9bb1b549SSpandan Das return err 159*9bb1b549SSpandan Das } 160*9bb1b549SSpandan Das } 161*9bb1b549SSpandan Das return nil 162*9bb1b549SSpandan Das} 163*9bb1b549SSpandan Das 164*9bb1b549SSpandan Dasfunc runAndLogCommand(cmd *exec.Cmd, verbose bool) error { 165*9bb1b549SSpandan Das if verbose { 166*9bb1b549SSpandan Das fmt.Fprintln(os.Stderr, formatCommand(cmd)) 167*9bb1b549SSpandan Das } 168*9bb1b549SSpandan Das cleanup := passLongArgsInResponseFiles(cmd) 169*9bb1b549SSpandan Das defer cleanup() 170*9bb1b549SSpandan Das if err := cmd.Run(); err != nil { 171*9bb1b549SSpandan Das return fmt.Errorf("error running subcommand %s: %v", cmd.Path, err) 172*9bb1b549SSpandan Das } 173*9bb1b549SSpandan Das return nil 174*9bb1b549SSpandan Das} 175*9bb1b549SSpandan Das 176*9bb1b549SSpandan Das// expandParamsFiles looks for arguments in args of the form 177*9bb1b549SSpandan Das// "-param=filename". When it finds these arguments it reads the file "filename" 178*9bb1b549SSpandan Das// and replaces the argument with its content. 179*9bb1b549SSpandan Das// It returns the expanded arguments as well as a bool that is true if any param 180*9bb1b549SSpandan Das// files have been passed. 181*9bb1b549SSpandan Dasfunc expandParamsFiles(args []string) ([]string, bool, error) { 182*9bb1b549SSpandan Das var paramsIndices []int 183*9bb1b549SSpandan Das for i, arg := range args { 184*9bb1b549SSpandan Das if strings.HasPrefix(arg, "-param=") { 185*9bb1b549SSpandan Das paramsIndices = append(paramsIndices, i) 186*9bb1b549SSpandan Das } 187*9bb1b549SSpandan Das } 188*9bb1b549SSpandan Das if len(paramsIndices) == 0 { 189*9bb1b549SSpandan Das return args, false, nil 190*9bb1b549SSpandan Das } 191*9bb1b549SSpandan Das var expandedArgs []string 192*9bb1b549SSpandan Das last := 0 193*9bb1b549SSpandan Das for _, pi := range paramsIndices { 194*9bb1b549SSpandan Das expandedArgs = append(expandedArgs, args[last:pi]...) 195*9bb1b549SSpandan Das last = pi + 1 196*9bb1b549SSpandan Das 197*9bb1b549SSpandan Das fileName := args[pi][len("-param="):] 198*9bb1b549SSpandan Das fileArgs, err := readParamsFile(fileName) 199*9bb1b549SSpandan Das if err != nil { 200*9bb1b549SSpandan Das return nil, true, err 201*9bb1b549SSpandan Das } 202*9bb1b549SSpandan Das expandedArgs = append(expandedArgs, fileArgs...) 203*9bb1b549SSpandan Das } 204*9bb1b549SSpandan Das expandedArgs = append(expandedArgs, args[last:]...) 205*9bb1b549SSpandan Das return expandedArgs, true, nil 206*9bb1b549SSpandan Das} 207*9bb1b549SSpandan Das 208*9bb1b549SSpandan Das// readParamsFiles parses a Bazel params file in "shell" format. The file 209*9bb1b549SSpandan Das// should contain one argument per line. Arguments may be quoted with single 210*9bb1b549SSpandan Das// quotes. All characters within quoted strings are interpreted literally 211*9bb1b549SSpandan Das// including newlines and excepting single quotes. Characters outside quoted 212*9bb1b549SSpandan Das// strings may be escaped with a backslash. 213*9bb1b549SSpandan Dasfunc readParamsFile(name string) ([]string, error) { 214*9bb1b549SSpandan Das data, err := ioutil.ReadFile(name) 215*9bb1b549SSpandan Das if err != nil { 216*9bb1b549SSpandan Das return nil, err 217*9bb1b549SSpandan Das } 218*9bb1b549SSpandan Das 219*9bb1b549SSpandan Das var args []string 220*9bb1b549SSpandan Das var arg []byte 221*9bb1b549SSpandan Das quote := false 222*9bb1b549SSpandan Das escape := false 223*9bb1b549SSpandan Das for p := 0; p < len(data); p++ { 224*9bb1b549SSpandan Das b := data[p] 225*9bb1b549SSpandan Das switch { 226*9bb1b549SSpandan Das case escape: 227*9bb1b549SSpandan Das arg = append(arg, b) 228*9bb1b549SSpandan Das escape = false 229*9bb1b549SSpandan Das 230*9bb1b549SSpandan Das case b == '\'': 231*9bb1b549SSpandan Das quote = !quote 232*9bb1b549SSpandan Das 233*9bb1b549SSpandan Das case !quote && b == '\\': 234*9bb1b549SSpandan Das escape = true 235*9bb1b549SSpandan Das 236*9bb1b549SSpandan Das case !quote && b == '\n': 237*9bb1b549SSpandan Das args = append(args, string(arg)) 238*9bb1b549SSpandan Das arg = arg[:0] 239*9bb1b549SSpandan Das 240*9bb1b549SSpandan Das default: 241*9bb1b549SSpandan Das arg = append(arg, b) 242*9bb1b549SSpandan Das } 243*9bb1b549SSpandan Das } 244*9bb1b549SSpandan Das if quote { 245*9bb1b549SSpandan Das return nil, fmt.Errorf("unterminated quote") 246*9bb1b549SSpandan Das } 247*9bb1b549SSpandan Das if escape { 248*9bb1b549SSpandan Das return nil, fmt.Errorf("unterminated escape") 249*9bb1b549SSpandan Das } 250*9bb1b549SSpandan Das if len(arg) > 0 { 251*9bb1b549SSpandan Das args = append(args, string(arg)) 252*9bb1b549SSpandan Das } 253*9bb1b549SSpandan Das return args, nil 254*9bb1b549SSpandan Das} 255*9bb1b549SSpandan Das 256*9bb1b549SSpandan Das// writeParamsFile formats a list of arguments in Bazel's "shell" format and writes 257*9bb1b549SSpandan Das// it to a file. 258*9bb1b549SSpandan Dasfunc writeParamsFile(path string, args []string) error { 259*9bb1b549SSpandan Das buf := new(bytes.Buffer) 260*9bb1b549SSpandan Das for _, arg := range args { 261*9bb1b549SSpandan Das if !strings.ContainsAny(arg, "'\n\\") { 262*9bb1b549SSpandan Das fmt.Fprintln(buf, arg) 263*9bb1b549SSpandan Das continue 264*9bb1b549SSpandan Das } 265*9bb1b549SSpandan Das buf.WriteByte('\'') 266*9bb1b549SSpandan Das for _, r := range arg { 267*9bb1b549SSpandan Das if r == '\'' { 268*9bb1b549SSpandan Das buf.WriteString(`'\''`) 269*9bb1b549SSpandan Das } else { 270*9bb1b549SSpandan Das buf.WriteRune(r) 271*9bb1b549SSpandan Das } 272*9bb1b549SSpandan Das } 273*9bb1b549SSpandan Das buf.WriteString("'\n") 274*9bb1b549SSpandan Das } 275*9bb1b549SSpandan Das return ioutil.WriteFile(path, buf.Bytes(), 0666) 276*9bb1b549SSpandan Das} 277*9bb1b549SSpandan Das 278*9bb1b549SSpandan Das// splitArgs splits a list of command line arguments into two parts: arguments 279*9bb1b549SSpandan Das// that should be interpreted by the builder (before "--"), and arguments 280*9bb1b549SSpandan Das// that should be passed through to the underlying tool (after "--"). 281*9bb1b549SSpandan Dasfunc splitArgs(args []string) (builderArgs []string, toolArgs []string) { 282*9bb1b549SSpandan Das for i, arg := range args { 283*9bb1b549SSpandan Das if arg == "--" { 284*9bb1b549SSpandan Das return args[:i], args[i+1:] 285*9bb1b549SSpandan Das } 286*9bb1b549SSpandan Das } 287*9bb1b549SSpandan Das return args, nil 288*9bb1b549SSpandan Das} 289*9bb1b549SSpandan Das 290*9bb1b549SSpandan Das// abs returns the absolute representation of path. Some tools/APIs require 291*9bb1b549SSpandan Das// absolute paths to work correctly. Most notably, golang on Windows cannot 292*9bb1b549SSpandan Das// handle relative paths to files whose absolute path is > ~250 chars, while 293*9bb1b549SSpandan Das// it can handle absolute paths. See http://goo.gl/eqeWjm. 294*9bb1b549SSpandan Das// 295*9bb1b549SSpandan Das// Note that strings that begin with "__BAZEL_" are not absolutized. These are 296*9bb1b549SSpandan Das// used on macOS for paths that the compiler wrapper (wrapped_clang) is 297*9bb1b549SSpandan Das// supposed to know about. 298*9bb1b549SSpandan Dasfunc abs(path string) string { 299*9bb1b549SSpandan Das if strings.HasPrefix(path, "__BAZEL_") { 300*9bb1b549SSpandan Das return path 301*9bb1b549SSpandan Das } 302*9bb1b549SSpandan Das 303*9bb1b549SSpandan Das if abs, err := filepath.Abs(path); err != nil { 304*9bb1b549SSpandan Das return path 305*9bb1b549SSpandan Das } else { 306*9bb1b549SSpandan Das return abs 307*9bb1b549SSpandan Das } 308*9bb1b549SSpandan Das} 309*9bb1b549SSpandan Das 310*9bb1b549SSpandan Das// absArgs applies abs to strings that appear in args. Only paths that are 311*9bb1b549SSpandan Das// part of options named by flags are modified. 312*9bb1b549SSpandan Dasfunc absArgs(args []string, flags []string) { 313*9bb1b549SSpandan Das absNext := false 314*9bb1b549SSpandan Das for i := range args { 315*9bb1b549SSpandan Das if absNext { 316*9bb1b549SSpandan Das args[i] = abs(args[i]) 317*9bb1b549SSpandan Das absNext = false 318*9bb1b549SSpandan Das continue 319*9bb1b549SSpandan Das } 320*9bb1b549SSpandan Das for _, f := range flags { 321*9bb1b549SSpandan Das if !strings.HasPrefix(args[i], f) { 322*9bb1b549SSpandan Das continue 323*9bb1b549SSpandan Das } 324*9bb1b549SSpandan Das possibleValue := args[i][len(f):] 325*9bb1b549SSpandan Das if len(possibleValue) == 0 { 326*9bb1b549SSpandan Das absNext = true 327*9bb1b549SSpandan Das break 328*9bb1b549SSpandan Das } 329*9bb1b549SSpandan Das separator := "" 330*9bb1b549SSpandan Das if possibleValue[0] == '=' { 331*9bb1b549SSpandan Das possibleValue = possibleValue[1:] 332*9bb1b549SSpandan Das separator = "=" 333*9bb1b549SSpandan Das } 334*9bb1b549SSpandan Das args[i] = fmt.Sprintf("%s%s%s", f, separator, abs(possibleValue)) 335*9bb1b549SSpandan Das break 336*9bb1b549SSpandan Das } 337*9bb1b549SSpandan Das } 338*9bb1b549SSpandan Das} 339*9bb1b549SSpandan Das 340*9bb1b549SSpandan Das// relativizePaths converts absolute paths found in the given output string to 341*9bb1b549SSpandan Das// relative, if they are within the working directory. 342*9bb1b549SSpandan Dasfunc relativizePaths(output []byte) []byte { 343*9bb1b549SSpandan Das dir, err := os.Getwd() 344*9bb1b549SSpandan Das if dir == "" || err != nil { 345*9bb1b549SSpandan Das return output 346*9bb1b549SSpandan Das } 347*9bb1b549SSpandan Das dirBytes := make([]byte, len(dir), len(dir)+1) 348*9bb1b549SSpandan Das copy(dirBytes, dir) 349*9bb1b549SSpandan Das if bytes.HasSuffix(dirBytes, []byte{filepath.Separator}) { 350*9bb1b549SSpandan Das return bytes.ReplaceAll(output, dirBytes, nil) 351*9bb1b549SSpandan Das } 352*9bb1b549SSpandan Das 353*9bb1b549SSpandan Das // This is the common case. 354*9bb1b549SSpandan Das // Replace "$CWD/" with "" and "$CWD" with "." 355*9bb1b549SSpandan Das dirBytes = append(dirBytes, filepath.Separator) 356*9bb1b549SSpandan Das output = bytes.ReplaceAll(output, dirBytes, nil) 357*9bb1b549SSpandan Das dirBytes = dirBytes[:len(dirBytes)-1] 358*9bb1b549SSpandan Das return bytes.ReplaceAll(output, dirBytes, []byte{'.'}) 359*9bb1b549SSpandan Das} 360*9bb1b549SSpandan Das 361*9bb1b549SSpandan Das// formatCommand formats cmd as a string that can be pasted into a shell. 362*9bb1b549SSpandan Das// Spaces in environment variables and arguments are escaped as needed. 363*9bb1b549SSpandan Dasfunc formatCommand(cmd *exec.Cmd) string { 364*9bb1b549SSpandan Das quoteIfNeeded := func(s string) string { 365*9bb1b549SSpandan Das if strings.IndexByte(s, ' ') < 0 { 366*9bb1b549SSpandan Das return s 367*9bb1b549SSpandan Das } 368*9bb1b549SSpandan Das return strconv.Quote(s) 369*9bb1b549SSpandan Das } 370*9bb1b549SSpandan Das quoteEnvIfNeeded := func(s string) string { 371*9bb1b549SSpandan Das eq := strings.IndexByte(s, '=') 372*9bb1b549SSpandan Das if eq < 0 { 373*9bb1b549SSpandan Das return s 374*9bb1b549SSpandan Das } 375*9bb1b549SSpandan Das key, value := s[:eq], s[eq+1:] 376*9bb1b549SSpandan Das if strings.IndexByte(value, ' ') < 0 { 377*9bb1b549SSpandan Das return s 378*9bb1b549SSpandan Das } 379*9bb1b549SSpandan Das return fmt.Sprintf("%s=%s", key, strconv.Quote(value)) 380*9bb1b549SSpandan Das } 381*9bb1b549SSpandan Das var w bytes.Buffer 382*9bb1b549SSpandan Das environ := cmd.Env 383*9bb1b549SSpandan Das if environ == nil { 384*9bb1b549SSpandan Das environ = os.Environ() 385*9bb1b549SSpandan Das } 386*9bb1b549SSpandan Das for _, e := range environ { 387*9bb1b549SSpandan Das fmt.Fprintf(&w, "%s \\\n", quoteEnvIfNeeded(e)) 388*9bb1b549SSpandan Das } 389*9bb1b549SSpandan Das 390*9bb1b549SSpandan Das sep := "" 391*9bb1b549SSpandan Das for _, arg := range cmd.Args { 392*9bb1b549SSpandan Das fmt.Fprintf(&w, "%s%s", sep, quoteIfNeeded(arg)) 393*9bb1b549SSpandan Das sep = " " 394*9bb1b549SSpandan Das } 395*9bb1b549SSpandan Das return w.String() 396*9bb1b549SSpandan Das} 397*9bb1b549SSpandan Das 398*9bb1b549SSpandan Das// passLongArgsInResponseFiles modifies cmd such that, for 399*9bb1b549SSpandan Das// certain programs, long arguments are passed in "response files", a 400*9bb1b549SSpandan Das// file on disk with the arguments, with one arg per line. An actual 401*9bb1b549SSpandan Das// argument starting with '@' means that the rest of the argument is 402*9bb1b549SSpandan Das// a filename of arguments to expand. 403*9bb1b549SSpandan Das// 404*9bb1b549SSpandan Das// See https://github.com/golang/go/issues/18468 (Windows) and 405*9bb1b549SSpandan Das// https://github.com/golang/go/issues/37768 (Darwin). 406*9bb1b549SSpandan Dasfunc passLongArgsInResponseFiles(cmd *exec.Cmd) (cleanup func()) { 407*9bb1b549SSpandan Das cleanup = func() {} // no cleanup by default 408*9bb1b549SSpandan Das var argLen int 409*9bb1b549SSpandan Das for _, arg := range cmd.Args { 410*9bb1b549SSpandan Das argLen += len(arg) 411*9bb1b549SSpandan Das } 412*9bb1b549SSpandan Das // If we're not approaching 32KB of args, just pass args normally. 413*9bb1b549SSpandan Das // (use 30KB instead to be conservative; not sure how accounting is done) 414*9bb1b549SSpandan Das if !useResponseFile(cmd.Path, argLen) { 415*9bb1b549SSpandan Das return 416*9bb1b549SSpandan Das } 417*9bb1b549SSpandan Das tf, err := ioutil.TempFile("", "args") 418*9bb1b549SSpandan Das if err != nil { 419*9bb1b549SSpandan Das log.Fatalf("error writing long arguments to response file: %v", err) 420*9bb1b549SSpandan Das } 421*9bb1b549SSpandan Das cleanup = func() { os.Remove(tf.Name()) } 422*9bb1b549SSpandan Das var buf bytes.Buffer 423*9bb1b549SSpandan Das for _, arg := range cmd.Args[1:] { 424*9bb1b549SSpandan Das fmt.Fprintf(&buf, "%s\n", arg) 425*9bb1b549SSpandan Das } 426*9bb1b549SSpandan Das if _, err := tf.Write(buf.Bytes()); err != nil { 427*9bb1b549SSpandan Das tf.Close() 428*9bb1b549SSpandan Das cleanup() 429*9bb1b549SSpandan Das log.Fatalf("error writing long arguments to response file: %v", err) 430*9bb1b549SSpandan Das } 431*9bb1b549SSpandan Das if err := tf.Close(); err != nil { 432*9bb1b549SSpandan Das cleanup() 433*9bb1b549SSpandan Das log.Fatalf("error writing long arguments to response file: %v", err) 434*9bb1b549SSpandan Das } 435*9bb1b549SSpandan Das cmd.Args = []string{cmd.Args[0], "@" + tf.Name()} 436*9bb1b549SSpandan Das return cleanup 437*9bb1b549SSpandan Das} 438*9bb1b549SSpandan Das 439*9bb1b549SSpandan Das// quotePathIfNeeded quotes path if it contains whitespace and isn't already quoted. 440*9bb1b549SSpandan Das// Use this for paths that will be passed through 441*9bb1b549SSpandan Das// https://github.com/golang/go/blob/06264b740e3bfe619f5e90359d8f0d521bd47806/src/cmd/internal/quoted/quoted.go#L25 442*9bb1b549SSpandan Dasfunc quotePathIfNeeded(path string) string { 443*9bb1b549SSpandan Das if strings.HasPrefix(path, "\"") || strings.HasPrefix(path, "'") { 444*9bb1b549SSpandan Das // Assume already quoted 445*9bb1b549SSpandan Das return path 446*9bb1b549SSpandan Das } 447*9bb1b549SSpandan Das // https://github.com/golang/go/blob/06264b740e3bfe619f5e90359d8f0d521bd47806/src/cmd/internal/quoted/quoted.go#L16 448*9bb1b549SSpandan Das if strings.IndexAny(path, " \t\n\r") < 0 { 449*9bb1b549SSpandan Das // Does not require quoting 450*9bb1b549SSpandan Das return path 451*9bb1b549SSpandan Das } 452*9bb1b549SSpandan Das // Escaping quotes is not supported, so we can assume path doesn't contain any quotes. 453*9bb1b549SSpandan Das return "'" + path + "'" 454*9bb1b549SSpandan Das} 455*9bb1b549SSpandan Das 456*9bb1b549SSpandan Dasfunc useResponseFile(path string, argLen int) bool { 457*9bb1b549SSpandan Das // Unless the program uses objabi.Flagparse, which understands 458*9bb1b549SSpandan Das // response files, don't use response files. 459*9bb1b549SSpandan Das // TODO: do we need more commands? asm? cgo? For now, no. 460*9bb1b549SSpandan Das prog := strings.TrimSuffix(filepath.Base(path), ".exe") 461*9bb1b549SSpandan Das switch prog { 462*9bb1b549SSpandan Das case "compile", "link": 463*9bb1b549SSpandan Das default: 464*9bb1b549SSpandan Das return false 465*9bb1b549SSpandan Das } 466*9bb1b549SSpandan Das // Windows has a limit of 32 KB arguments. To be conservative and not 467*9bb1b549SSpandan Das // worry about whether that includes spaces or not, just use 30 KB. 468*9bb1b549SSpandan Das // Darwin's limit is less clear. The OS claims 256KB, but we've seen 469*9bb1b549SSpandan Das // failures with arglen as small as 50KB. 470*9bb1b549SSpandan Das if argLen > (30 << 10) { 471*9bb1b549SSpandan Das return true 472*9bb1b549SSpandan Das } 473*9bb1b549SSpandan Das return false 474*9bb1b549SSpandan Das} 475