| // 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, |
| } |
| } |