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