xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/command.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1// Copyright 2019 The ChromiumOS Authors
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	"context"
9	"fmt"
10	"io"
11	"os"
12	"os/exec"
13	"path/filepath"
14	"strings"
15	"time"
16)
17
18type command struct {
19	Path string   `json:"path"`
20	Args []string `json:"args"`
21	// Updates and additions have the form:
22	// `NAME=VALUE`
23	// Removals have the form:
24	// `NAME=`.
25	EnvUpdates []string `json:"env_updates,omitempty"`
26}
27
28func newProcessCommand() *command {
29	return &command{
30		Path: os.Args[0],
31		Args: os.Args[1:],
32	}
33}
34
35func mergeEnvValues(values []string, updates []string) []string {
36	envMap := map[string]string{}
37	for _, entry := range values {
38		equalPos := strings.IndexRune(entry, '=')
39		envMap[entry[:equalPos]] = entry[equalPos+1:]
40	}
41	for _, update := range updates {
42		equalPos := strings.IndexRune(update, '=')
43		key := update[:equalPos]
44		value := update[equalPos+1:]
45		if value == "" {
46			delete(envMap, key)
47		} else {
48			envMap[key] = value
49		}
50	}
51	env := []string{}
52	for key, value := range envMap {
53		env = append(env, key+"="+value)
54	}
55	return env
56}
57
58func runCmd(env env, cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
59	execCmd := exec.Command(cmd.Path, cmd.Args...)
60	execCmd.Env = mergeEnvValues(env.environ(), cmd.EnvUpdates)
61	execCmd.Dir = env.getwd()
62	execCmd.Stdin = stdin
63	execCmd.Stdout = stdout
64	execCmd.Stderr = stderr
65	return execCmd.Run()
66}
67
68func runCmdWithTimeout(env env, cmd *command, t time.Duration) error {
69	ctx, cancel := context.WithTimeout(context.Background(), t)
70	defer cancel()
71	cmdCtx := exec.CommandContext(ctx, cmd.Path, cmd.Args...)
72	cmdCtx.Env = mergeEnvValues(env.environ(), cmd.EnvUpdates)
73	cmdCtx.Dir = env.getwd()
74	cmdCtx.Stdin = env.stdin()
75	cmdCtx.Stdout = env.stdout()
76	cmdCtx.Stderr = env.stderr()
77
78	if err := cmdCtx.Start(); err != nil {
79		return fmt.Errorf("exec error: %w", err)
80	}
81	err := cmdCtx.Wait()
82	if ctx.Err() == nil {
83		return err
84	}
85	return ctx.Err()
86}
87
88func resolveAgainstPathEnv(env env, cmd string) (string, error) {
89	path, _ := env.getenv("PATH")
90	for _, path := range strings.Split(path, ":") {
91		resolvedPath := filepath.Join(path, cmd)
92		if _, err := os.Lstat(resolvedPath); err == nil {
93			return resolvedPath, nil
94		}
95	}
96	return "", fmt.Errorf("couldn't find cmd %q in path", cmd)
97}
98
99func getAbsCmdPath(env env, cmd *command) string {
100	path := cmd.Path
101	if !filepath.IsAbs(path) {
102		path = filepath.Join(env.getwd(), path)
103	}
104	return path
105}
106
107func newCommandBuilder(env env, cfg *config, cmd *command) (*commandBuilder, error) {
108	basename := filepath.Base(cmd.Path)
109	var nameParts []string
110	if basename == "clang-tidy" {
111		nameParts = []string{basename}
112	} else {
113		nameParts = strings.Split(basename, "-")
114	}
115	target := builderTarget{}
116	switch len(nameParts) {
117	case 1:
118		// E.g. gcc
119		target = builderTarget{
120			compiler: nameParts[0],
121		}
122	case 4:
123		// E.g. armv7m-cros-eabi-gcc
124		target = builderTarget{
125			arch:     nameParts[0],
126			vendor:   nameParts[1],
127			abi:      nameParts[2],
128			compiler: nameParts[3],
129			target:   basename[:strings.LastIndex(basename, "-")],
130		}
131	case 5:
132		// E.g. x86_64-cros-linux-gnu-gcc
133		target = builderTarget{
134			arch:     nameParts[0],
135			vendor:   nameParts[1],
136			sys:      nameParts[2],
137			abi:      nameParts[3],
138			compiler: nameParts[4],
139			target:   basename[:strings.LastIndex(basename, "-")],
140		}
141	default:
142		return nil, newErrorwithSourceLocf("unexpected compiler name pattern. Actual: %s", basename)
143	}
144
145	var compilerType compilerType
146	switch {
147	case strings.HasPrefix(target.compiler, "clang-tidy"):
148		compilerType = clangTidyType
149	case strings.HasPrefix(target.compiler, "clang"):
150		compilerType = clangType
151	default:
152		compilerType = gccType
153	}
154	target.compilerType = compilerType
155	absWrapperPath, err := getAbsWrapperPath(env, cmd)
156	if err != nil {
157		return nil, err
158	}
159	var rootPath string
160	if compilerType == gccType {
161		rootPath = filepath.Join(filepath.Dir(absWrapperPath), cfg.gccRootRelPath)
162	} else {
163		rootPath = filepath.Join(filepath.Dir(absWrapperPath), cfg.clangRootRelPath)
164	}
165	return &commandBuilder{
166		path:           cmd.Path,
167		args:           createBuilderArgs( /*fromUser=*/ true, cmd.Args),
168		env:            env,
169		cfg:            cfg,
170		rootPath:       rootPath,
171		absWrapperPath: absWrapperPath,
172		target:         target,
173	}, nil
174}
175
176type commandBuilder struct {
177	path           string
178	target         builderTarget
179	args           []builderArg
180	envUpdates     []string
181	env            env
182	cfg            *config
183	rootPath       string
184	absWrapperPath string
185}
186
187type builderArg struct {
188	value    string
189	fromUser bool
190}
191
192type compilerType int32
193
194const (
195	gccType compilerType = iota
196	clangType
197	clangTidyType
198)
199
200type builderTarget struct {
201	target       string
202	arch         string
203	vendor       string
204	sys          string
205	abi          string
206	compiler     string
207	compilerType compilerType
208}
209
210func createBuilderArgs(fromUser bool, args []string) []builderArg {
211	builderArgs := make([]builderArg, len(args))
212	for i, arg := range args {
213		builderArgs[i] = builderArg{value: arg, fromUser: fromUser}
214	}
215	return builderArgs
216}
217
218func (builder *commandBuilder) clone() *commandBuilder {
219	return &commandBuilder{
220		path:           builder.path,
221		args:           append([]builderArg{}, builder.args...),
222		env:            builder.env,
223		cfg:            builder.cfg,
224		rootPath:       builder.rootPath,
225		target:         builder.target,
226		absWrapperPath: builder.absWrapperPath,
227	}
228}
229
230func (builder *commandBuilder) wrapPath(path string, extraFlags ...string) {
231	newArgs := createBuilderArgs( /*fromUser=*/ false, extraFlags)
232	newArgs = append(newArgs, builderArg{value: builder.path, fromUser: false})
233	builder.args = append(newArgs, builder.args...)
234	builder.path = path
235}
236
237func (builder *commandBuilder) addPreUserArgs(args ...string) {
238	index := 0
239	for _, arg := range builder.args {
240		if arg.fromUser {
241			break
242		}
243		index++
244	}
245	builder.args = append(builder.args[:index], append(createBuilderArgs( /*fromUser=*/ false, args), builder.args[index:]...)...)
246}
247
248func (builder *commandBuilder) addPostUserArgs(args ...string) {
249	builder.args = append(builder.args, createBuilderArgs( /*fromUser=*/ false, args)...)
250}
251
252// Allows to map and filter arguments. Filters when the callback returns an empty string.
253func (builder *commandBuilder) transformArgs(transform func(arg builderArg) string) {
254	// See https://github.com/golang/go/wiki/SliceTricks
255	newArgs := builder.args[:0]
256	for _, arg := range builder.args {
257		newArg := transform(arg)
258		if newArg != "" {
259			newArgs = append(newArgs, builderArg{
260				value:    newArg,
261				fromUser: arg.fromUser,
262			})
263		}
264	}
265	builder.args = newArgs
266}
267
268// Allows to filter arg pairs, useful for eg when having adjacent unsupported args
269// like "-Wl,-z -Wl,defs"
270func (builder *commandBuilder) filterArgPairs(keepPair func(arg1, arg2 builderArg) bool) {
271	newArgs := builder.args[:0]
272	for i := 0; i < len(builder.args); i++ {
273		if i == len(builder.args)-1 || keepPair(builder.args[i], builder.args[i+1]) {
274			newArgs = append(newArgs, builder.args[i])
275		} else {
276			// skip builder.args[i]) as well as next item
277			i++
278		}
279	}
280	builder.args = newArgs
281}
282
283func (builder *commandBuilder) updateEnv(updates ...string) {
284	builder.envUpdates = append(builder.envUpdates, updates...)
285}
286
287func (builder *commandBuilder) build() *command {
288	cmdArgs := make([]string, len(builder.args))
289	for i, builderArg := range builder.args {
290		cmdArgs[i] = builderArg.value
291	}
292	return &command{
293		Path:       builder.path,
294		Args:       cmdArgs,
295		EnvUpdates: builder.envUpdates,
296	}
297}
298