1// Copyright 2019 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package main 6 7import ( 8 "fmt" 9 "io" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14) 15 16type command struct { 17 Path string `json:"path"` 18 Args []string `json:"args"` 19 // Updates and additions have the form: 20 // `NAME=VALUE` 21 // Removals have the form: 22 // `NAME=`. 23 EnvUpdates []string `json:"env_updates,omitempty"` 24} 25 26func newProcessCommand() *command { 27 // This is a workaround for the fact that ld.so does not support 28 // passing in the executable name when ld.so is invoked as 29 // an executable (crbug/1003841). 30 path := os.Getenv("LD_ARGV0") 31 if path == "" { 32 path = os.Args[0] 33 } 34 return &command{ 35 Path: path, 36 Args: os.Args[1:], 37 } 38} 39 40func mergeEnvValues(values []string, updates []string) []string { 41 envMap := map[string]string{} 42 for _, entry := range values { 43 equalPos := strings.IndexRune(entry, '=') 44 envMap[entry[:equalPos]] = entry[equalPos+1:] 45 } 46 for _, update := range updates { 47 equalPos := strings.IndexRune(update, '=') 48 key := update[:equalPos] 49 value := update[equalPos+1:] 50 if value == "" { 51 delete(envMap, key) 52 } else { 53 envMap[key] = value 54 } 55 } 56 env := []string{} 57 for key, value := range envMap { 58 env = append(env, key+"="+value) 59 } 60 return env 61} 62 63func runCmd(env env, cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 64 execCmd := exec.Command(cmd.Path, cmd.Args...) 65 execCmd.Env = mergeEnvValues(env.environ(), cmd.EnvUpdates) 66 execCmd.Dir = env.getwd() 67 execCmd.Stdin = stdin 68 execCmd.Stdout = stdout 69 execCmd.Stderr = stderr 70 return execCmd.Run() 71} 72 73func resolveAgainstPathEnv(env env, cmd string) (string, error) { 74 path, _ := env.getenv("PATH") 75 for _, path := range strings.Split(path, ":") { 76 resolvedPath := filepath.Join(path, cmd) 77 if _, err := os.Lstat(resolvedPath); err == nil { 78 return resolvedPath, nil 79 } 80 } 81 return "", fmt.Errorf("Couldn't find cmd %q in path", cmd) 82} 83 84func getAbsCmdPath(env env, cmd *command) string { 85 path := cmd.Path 86 if !filepath.IsAbs(path) { 87 path = filepath.Join(env.getwd(), path) 88 } 89 return path 90} 91 92func newCommandBuilder(env env, cfg *config, cmd *command) (*commandBuilder, error) { 93 basename := filepath.Base(cmd.Path) 94 var nameParts []string 95 if basename == "clang-tidy" { 96 nameParts = []string{basename} 97 } else { 98 nameParts = strings.Split(basename, "-") 99 } 100 target := builderTarget{} 101 switch len(nameParts) { 102 case 1: 103 // E.g. gcc 104 target = builderTarget{ 105 compiler: nameParts[0], 106 } 107 case 4: 108 // E.g. armv7m-cros-eabi-gcc 109 target = builderTarget{ 110 arch: nameParts[0], 111 vendor: nameParts[1], 112 abi: nameParts[2], 113 compiler: nameParts[3], 114 target: basename[:strings.LastIndex(basename, "-")], 115 } 116 case 5: 117 // E.g. x86_64-cros-linux-gnu-gcc 118 target = builderTarget{ 119 arch: nameParts[0], 120 vendor: nameParts[1], 121 sys: nameParts[2], 122 abi: nameParts[3], 123 compiler: nameParts[4], 124 target: basename[:strings.LastIndex(basename, "-")], 125 } 126 default: 127 return nil, newErrorwithSourceLocf("unexpected compiler name pattern. Actual: %s", basename) 128 } 129 130 var compilerType compilerType 131 switch { 132 case strings.HasPrefix(target.compiler, "clang-tidy"): 133 compilerType = clangTidyType 134 case strings.HasPrefix(target.compiler, "clang"): 135 compilerType = clangType 136 default: 137 compilerType = gccType 138 } 139 target.compilerType = compilerType 140 absWrapperPath, err := getAbsWrapperPath(env, cmd) 141 if err != nil { 142 return nil, err 143 } 144 rootPath := filepath.Join(filepath.Dir(absWrapperPath), cfg.rootRelPath) 145 return &commandBuilder{ 146 path: cmd.Path, 147 args: createBuilderArgs( /*fromUser=*/ true, cmd.Args), 148 env: env, 149 cfg: cfg, 150 rootPath: rootPath, 151 absWrapperPath: absWrapperPath, 152 target: target, 153 }, nil 154} 155 156type commandBuilder struct { 157 path string 158 target builderTarget 159 args []builderArg 160 envUpdates []string 161 env env 162 cfg *config 163 rootPath string 164 absWrapperPath string 165} 166 167type builderArg struct { 168 value string 169 fromUser bool 170} 171 172type compilerType int32 173 174const ( 175 gccType compilerType = iota 176 clangType 177 clangTidyType 178) 179 180type builderTarget struct { 181 target string 182 arch string 183 vendor string 184 sys string 185 abi string 186 compiler string 187 compilerType compilerType 188} 189 190func createBuilderArgs(fromUser bool, args []string) []builderArg { 191 builderArgs := make([]builderArg, len(args)) 192 for i, arg := range args { 193 builderArgs[i] = builderArg{value: arg, fromUser: fromUser} 194 } 195 return builderArgs 196} 197 198func (builder *commandBuilder) clone() *commandBuilder { 199 return &commandBuilder{ 200 path: builder.path, 201 args: append([]builderArg{}, builder.args...), 202 env: builder.env, 203 cfg: builder.cfg, 204 rootPath: builder.rootPath, 205 target: builder.target, 206 absWrapperPath: builder.absWrapperPath, 207 } 208} 209 210func (builder *commandBuilder) wrapPath(path string) { 211 builder.args = append([]builderArg{{value: builder.path, fromUser: false}}, builder.args...) 212 builder.path = path 213} 214 215func (builder *commandBuilder) addPreUserArgs(args ...string) { 216 index := 0 217 for _, arg := range builder.args { 218 if arg.fromUser { 219 break 220 } 221 index++ 222 } 223 builder.args = append(builder.args[:index], append(createBuilderArgs( /*fromUser=*/ false, args), builder.args[index:]...)...) 224} 225 226func (builder *commandBuilder) addPostUserArgs(args ...string) { 227 builder.args = append(builder.args, createBuilderArgs( /*fromUser=*/ false, args)...) 228} 229 230// Allows to map and filter arguments. Filters when the callback returns an empty string. 231func (builder *commandBuilder) transformArgs(transform func(arg builderArg) string) { 232 // See https://github.com/golang/go/wiki/SliceTricks 233 newArgs := builder.args[:0] 234 for _, arg := range builder.args { 235 newArg := transform(arg) 236 if newArg != "" { 237 newArgs = append(newArgs, builderArg{ 238 value: newArg, 239 fromUser: arg.fromUser, 240 }) 241 } 242 } 243 builder.args = newArgs 244} 245 246func (builder *commandBuilder) updateEnv(updates ...string) { 247 builder.envUpdates = append(builder.envUpdates, updates...) 248} 249 250func (builder *commandBuilder) build() *command { 251 cmdArgs := make([]string, len(builder.args)) 252 for i, builderArg := range builder.args { 253 cmdArgs[i] = builderArg.value 254 } 255 return &command{ 256 Path: builder.path, 257 Args: cmdArgs, 258 EnvUpdates: builder.envUpdates, 259 } 260} 261