diff options
Diffstat (limited to 'compiler_wrapper/command.go')
-rw-r--r-- | compiler_wrapper/command.go | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/compiler_wrapper/command.go b/compiler_wrapper/command.go new file mode 100644 index 00000000..6c5beae1 --- /dev/null +++ b/compiler_wrapper/command.go @@ -0,0 +1,252 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package main + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" +) + +type command struct { + Path string `json:"path"` + Args []string `json:"args"` + // Updates and additions have the form: + // `NAME=VALUE` + // Removals have the form: + // `NAME=`. + EnvUpdates []string `json:"env_updates,omitempty"` +} + +func newProcessCommand() *command { + // This is a workaround for the fact that ld.so does not support + // passing in the executable name when ld.so is invoked as + // an executable (crbug/1003841). + path := os.Getenv("LD_ARGV0") + if path == "" { + path = os.Args[0] + } + return &command{ + Path: path, + Args: os.Args[1:], + } +} + +func mergeEnvValues(values []string, updates []string) []string { + envMap := map[string]string{} + for _, entry := range values { + equalPos := strings.IndexRune(entry, '=') + envMap[entry[:equalPos]] = entry[equalPos+1:] + } + for _, update := range updates { + equalPos := strings.IndexRune(update, '=') + key := update[:equalPos] + value := update[equalPos+1:] + if value == "" { + delete(envMap, key) + } else { + envMap[key] = value + } + } + env := []string{} + for key, value := range envMap { + env = append(env, key+"="+value) + } + return env +} + +func runCmd(env env, cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { + execCmd := exec.Command(cmd.Path, cmd.Args...) + execCmd.Env = mergeEnvValues(env.environ(), cmd.EnvUpdates) + execCmd.Dir = env.getwd() + execCmd.Stdin = stdin + execCmd.Stdout = stdout + execCmd.Stderr = stderr + return execCmd.Run() +} + +func resolveAgainstPathEnv(env env, cmd string) (string, error) { + path, _ := env.getenv("PATH") + for _, path := range strings.Split(path, ":") { + resolvedPath := filepath.Join(path, cmd) + if _, err := os.Lstat(resolvedPath); err == nil { + return resolvedPath, nil + } + } + return "", fmt.Errorf("Couldn't find cmd %q in path", cmd) +} + +func getAbsCmdPath(env env, cmd *command) string { + path := cmd.Path + if !filepath.IsAbs(path) { + path = filepath.Join(env.getwd(), path) + } + return path +} + +func newCommandBuilder(env env, cfg *config, cmd *command) (*commandBuilder, error) { + basename := filepath.Base(cmd.Path) + nameParts := strings.Split(basename, "-") + target := builderTarget{} + switch len(nameParts) { + case 1: + // E.g. gcc + target = builderTarget{ + compiler: nameParts[0], + } + case 4: + // E.g. armv7m-cros-eabi-gcc + target = builderTarget{ + arch: nameParts[0], + vendor: nameParts[1], + abi: nameParts[2], + compiler: nameParts[3], + target: basename[:strings.LastIndex(basename, "-")], + } + case 5: + // E.g. x86_64-cros-linux-gnu-gcc + target = builderTarget{ + arch: nameParts[0], + vendor: nameParts[1], + sys: nameParts[2], + abi: nameParts[3], + compiler: nameParts[4], + target: basename[:strings.LastIndex(basename, "-")], + } + default: + return nil, newErrorwithSourceLocf("unexpected compiler name pattern. Actual: %s", basename) + } + + var compilerType compilerType + switch { + case strings.HasPrefix(target.compiler, "clang"): + compilerType = clangType + default: + compilerType = gccType + } + target.compilerType = compilerType + absWrapperPath, err := getAbsWrapperPath(env, cmd) + if err != nil { + return nil, err + } + rootPath := filepath.Join(filepath.Dir(absWrapperPath), cfg.rootRelPath) + return &commandBuilder{ + path: cmd.Path, + args: createBuilderArgs( /*fromUser=*/ true, cmd.Args), + env: env, + cfg: cfg, + rootPath: rootPath, + absWrapperPath: absWrapperPath, + target: target, + }, nil +} + +type commandBuilder struct { + path string + target builderTarget + args []builderArg + envUpdates []string + env env + cfg *config + rootPath string + absWrapperPath string +} + +type builderArg struct { + value string + fromUser bool +} + +type compilerType int32 + +const ( + gccType compilerType = iota + clangType +) + +type builderTarget struct { + target string + arch string + vendor string + sys string + abi string + compiler string + compilerType compilerType +} + +func createBuilderArgs(fromUser bool, args []string) []builderArg { + builderArgs := make([]builderArg, len(args)) + for i, arg := range args { + builderArgs[i] = builderArg{value: arg, fromUser: fromUser} + } + return builderArgs +} + +func (builder *commandBuilder) clone() *commandBuilder { + return &commandBuilder{ + path: builder.path, + args: append([]builderArg{}, builder.args...), + env: builder.env, + cfg: builder.cfg, + rootPath: builder.rootPath, + target: builder.target, + absWrapperPath: builder.absWrapperPath, + } +} + +func (builder *commandBuilder) wrapPath(path string) { + builder.args = append([]builderArg{{value: builder.path, fromUser: false}}, builder.args...) + builder.path = path +} + +func (builder *commandBuilder) addPreUserArgs(args ...string) { + index := 0 + for _, arg := range builder.args { + if arg.fromUser { + break + } + index++ + } + builder.args = append(builder.args[:index], append(createBuilderArgs( /*fromUser=*/ false, args), builder.args[index:]...)...) +} + +func (builder *commandBuilder) addPostUserArgs(args ...string) { + builder.args = append(builder.args, createBuilderArgs( /*fromUser=*/ false, args)...) +} + +// Allows to map and filter arguments. Filters when the callback returns an empty string. +func (builder *commandBuilder) transformArgs(transform func(arg builderArg) string) { + // See https://github.com/golang/go/wiki/SliceTricks + newArgs := builder.args[:0] + for _, arg := range builder.args { + newArg := transform(arg) + if newArg != "" { + newArgs = append(newArgs, builderArg{ + value: newArg, + fromUser: arg.fromUser, + }) + } + } + builder.args = newArgs +} + +func (builder *commandBuilder) updateEnv(updates ...string) { + builder.envUpdates = append(builder.envUpdates, updates...) +} + +func (builder *commandBuilder) build() *command { + cmdArgs := make([]string, len(builder.args)) + for i, builderArg := range builder.args { + cmdArgs[i] = builderArg.value + } + return &command{ + Path: builder.path, + Args: cmdArgs, + EnvUpdates: builder.envUpdates, + } +} |