blob: b20f19eb4f44e65e2a7a5c7c8223e0f8e79df8ec [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 (
"os"
"os/exec"
"path/filepath"
"strings"
)
type command struct {
Path string `json:"path"`
Args []string `json:"args"`
EnvUpdates []string `json:"env_updates,omitempty"`
}
func newProcessCommand() *command {
return &command{
Path: os.Args[0],
Args: os.Args[1:],
}
}
func newExecCmd(env env, cmd *command) *exec.Cmd {
execCmd := exec.Command(cmd.Path, cmd.Args...)
execCmd.Env = append(env.environ(), cmd.EnvUpdates...)
ensurePathEnv(execCmd)
execCmd.Dir = env.getwd()
return execCmd
}
func ensurePathEnv(cmd *exec.Cmd) {
for _, env := range cmd.Env {
if strings.HasPrefix(env, "PATH=") {
return
}
}
cmd.Env = append(cmd.Env, "PATH=")
}
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, "-")
if len(nameParts) != 5 {
return nil, newErrorwithSourceLocf("expected 5 parts in the compiler name. Actual: %s", basename)
}
compiler := nameParts[4]
var compilerType compilerType
switch {
case strings.HasPrefix(compiler, "clang"):
compilerType = clangType
default:
compilerType = gccType
}
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: builderTarget{
target: strings.Join(nameParts[:4], "-"),
arch: nameParts[0],
vendor: nameParts[1],
sys: nameParts[2],
abi: nameParts[3],
compiler: compiler,
compilerType: compilerType,
},
}, 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,
}
}