You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
6.4 KiB
261 lines
6.4 KiB
// 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,
|
|
}
|
|
}
|