diff --git a/go/private/context.bzl b/go/private/context.bzl index b71a355b3d..781285c5de 100644 --- a/go/private/context.bzl +++ b/go/private/context.bzl @@ -128,7 +128,7 @@ def _new_args(go): def _builder_args(go, command = None): args = go.actions.args() args.use_param_file("-param=%s") - args.set_param_file_format("multiline") + args.set_param_file_format("shell") if command: args.add(command) args.add("-sdk", go.sdk.root_file.dirname) @@ -139,7 +139,7 @@ def _builder_args(go, command = None): def _tool_args(go): args = go.actions.args() args.use_param_file("-param=%s") - args.set_param_file_format("multiline") + args.set_param_file_format("shell") return args def _new_library(go, name = None, importpath = None, resolver = None, importable = True, testfilter = None, is_main = False, **kwargs): diff --git a/go/tools/builders/asm.go b/go/tools/builders/asm.go index bbacdea940..c3fb845b3e 100644 --- a/go/tools/builders/asm.go +++ b/go/tools/builders/asm.go @@ -30,7 +30,7 @@ import ( // Go rules as an action. func asm(args []string) error { // Parse arguments. - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/builder.go b/go/tools/builders/builder.go index 978d40f81b..905c04e697 100644 --- a/go/tools/builders/builder.go +++ b/go/tools/builders/builder.go @@ -28,7 +28,7 @@ func main() { log.SetFlags(0) log.SetPrefix("builder: ") - args, err := readParamsFiles(os.Args[1:]) + args, err := expandParamsFiles(os.Args[1:]) if err != nil { log.Fatal(err) } diff --git a/go/tools/builders/compile.go b/go/tools/builders/compile.go index a74d9141b8..f22ac432fb 100644 --- a/go/tools/builders/compile.go +++ b/go/tools/builders/compile.go @@ -29,7 +29,7 @@ import ( func compile(args []string) error { // Parse arguments. - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/compilepkg.go b/go/tools/builders/compilepkg.go index 24e629543d..77bc28b116 100644 --- a/go/tools/builders/compilepkg.go +++ b/go/tools/builders/compilepkg.go @@ -33,7 +33,7 @@ import ( func compilePkg(args []string) error { // Parse arguments. - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } @@ -446,13 +446,12 @@ func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string args = append(args, "-x", outFactsPath) args = append(args, srcs...) - paramFile := filepath.Join(workDir, "nogo.param") - params := strings.Join(args[1:], "\n") - if err := ioutil.WriteFile(paramFile, []byte(params), 0666); err != nil { - return fmt.Errorf("error writing nogo paramfile: %v", err) + paramsFile := filepath.Join(workDir, "nogo.param") + if err := writeParamsFile(paramsFile, args[1:]); err != nil { + return fmt.Errorf("error writing nogo params file: %v", err) } - cmd := exec.CommandContext(ctx, args[0], "-param="+paramFile) + cmd := exec.CommandContext(ctx, args[0], "-param="+paramsFile) out := &bytes.Buffer{} cmd.Stdout, cmd.Stderr = out, out if err := cmd.Run(); err != nil { diff --git a/go/tools/builders/cover.go b/go/tools/builders/cover.go index 5748b8111a..860705003d 100644 --- a/go/tools/builders/cover.go +++ b/go/tools/builders/cover.go @@ -28,7 +28,7 @@ import ( // cover transforms a source file with "go tool cover". It is invoked by the // Go rules as an action. func cover(args []string) error { - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/env.go b/go/tools/builders/env.go index a87291e3e9..30fcf9edfb 100644 --- a/go/tools/builders/env.go +++ b/go/tools/builders/env.go @@ -169,11 +169,10 @@ func runAndLogCommand(cmd *exec.Cmd, verbose bool) error { return nil } -// readParamsFile looks for arguments in args of the form +// expandParamsFiles looks for arguments in args of the form // "-param=filename". When it finds these arguments it reads the file "filename" -// and replaces the argument with its content (each argument must be on a -// separate line; blank lines are ignored). -func readParamsFiles(args []string) ([]string, error) { +// and replaces the argument with its content. +func expandParamsFiles(args []string) ([]string, error) { var paramsIndices []int for i, arg := range args { if strings.HasPrefix(arg, "-param=") { @@ -190,21 +189,86 @@ func readParamsFiles(args []string) ([]string, error) { last = pi + 1 fileName := args[pi][len("-param="):] - content, err := ioutil.ReadFile(fileName) + fileArgs, err := readParamsFile(fileName) if err != nil { return nil, err } - fileArgs := strings.Split(string(content), "\n") - if len(fileArgs) >= 0 && fileArgs[len(fileArgs)-1] == "" { - // Ignore final empty line. - fileArgs = fileArgs[:len(fileArgs)-1] - } expandedArgs = append(expandedArgs, fileArgs...) } expandedArgs = append(expandedArgs, args[last:]...) return expandedArgs, nil } +// readParamsFiles parses a Bazel params file in "shell" format. The file +// should contain one argument per line. Arguments may be quoted with single +// quotes. All characters within quoted strings are interpreted literally +// including newlines and excepting single quotes. Characters outside quoted +// strings may be escaped with a backslash. +func readParamsFile(name string) ([]string, error) { + data, err := ioutil.ReadFile(name) + if err != nil { + return nil, err + } + + var args []string + var arg []byte + quote := false + escape := false + for p := 0; p < len(data); p++ { + b := data[p] + switch { + case escape: + arg = append(arg, b) + escape = false + + case b == '\'': + quote = !quote + + case !quote && b == '\\': + escape = true + + case !quote && b == '\n': + args = append(args, string(arg)) + arg = arg[:0] + + default: + arg = append(arg, b) + } + } + if quote { + return nil, fmt.Errorf("unterminated quote") + } + if escape { + return nil, fmt.Errorf("unterminated escape") + } + if len(arg) > 0 { + args = append(args, string(arg)) + } + return args, nil +} + +// writeParamsFile formats a list of arguments in Bazel's "shell" format and writes +// it to a file. +func writeParamsFile(path string, args []string) error { + buf := new(bytes.Buffer) + for _, arg := range args { + if !strings.ContainsAny(arg, "'\n\\") { + fmt.Fprintln(buf, arg) + continue + } + buf.WriteByte('\'') + for _, r := range arg { + if r == '\'' { + buf.WriteString(`'\''`) + } else { + buf.WriteRune(r) + } + } + buf.WriteString("'\n") + } + return ioutil.WriteFile(path, buf.Bytes(), 0666) +} + // splitArgs splits a list of command line arguments into two parts: arguments // that should be interpreted by the builder (before "--"), and arguments // that should be passed through to the underlying tool (after "--"). diff --git a/go/tools/builders/generate_test_main.go b/go/tools/builders/generate_test_main.go index 865997d893..986a34386a 100644 --- a/go/tools/builders/generate_test_main.go +++ b/go/tools/builders/generate_test_main.go @@ -175,7 +175,7 @@ func main() { func genTestMain(args []string) error { // Prepare our flags - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/info.go b/go/tools/builders/info.go index 0a6b19c3a3..e0586a8275 100644 --- a/go/tools/builders/info.go +++ b/go/tools/builders/info.go @@ -24,7 +24,7 @@ import ( ) func run(args []string) error { - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/link.go b/go/tools/builders/link.go index 8ead5cde72..71f9559c7a 100644 --- a/go/tools/builders/link.go +++ b/go/tools/builders/link.go @@ -31,7 +31,7 @@ import ( func link(args []string) error { // Parse arguments. - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/nogo_main.go b/go/tools/builders/nogo_main.go index 2830a66677..3f355a4bad 100644 --- a/go/tools/builders/nogo_main.go +++ b/go/tools/builders/nogo_main.go @@ -62,7 +62,7 @@ func main() { // run returns an error if there is a problem loading the package or if any // analysis fails. func run(args []string) error { - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return fmt.Errorf("error reading paramfiles: %v", err) } diff --git a/go/tools/builders/pack.go b/go/tools/builders/pack.go index be698e1327..1bf23425b2 100644 --- a/go/tools/builders/pack.go +++ b/go/tools/builders/pack.go @@ -38,7 +38,7 @@ import ( // handle them, and ar may not be available (cpp.ar_executable is libtool // on darwin). func pack(args []string) error { - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err } diff --git a/go/tools/builders/protoc.go b/go/tools/builders/protoc.go index 8e5b2adefb..3562ec99b8 100644 --- a/go/tools/builders/protoc.go +++ b/go/tools/builders/protoc.go @@ -40,7 +40,7 @@ type genFileInfo struct { func run(args []string) error { // process the args - args, err := readParamsFiles(args) + args, err := expandParamsFiles(args) if err != nil { return err }