blob: 69578597f3fccb89c0603d4a5a2391d9cd0b1b78 [file] [log] [blame]
// 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)
var nameParts []string
if basename == "clang-tidy" {
nameParts = []string{basename}
} else {
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-tidy"):
compilerType = clangTidyType
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
clangTidyType
)
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,
}
}