xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/compiler_wrapper.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	"bytes"
9	"context"
10	"errors"
11	"fmt"
12	"io"
13	"path/filepath"
14	"strconv"
15	"strings"
16	"time"
17)
18
19const (
20	clangCrashArtifactsSubdir = "toolchain/clang_crash_diagnostics"
21	crosArtifactsEnvVar       = "CROS_ARTIFACTS_TMP_DIR"
22)
23
24func callCompiler(env env, cfg *config, inputCmd *command) int {
25	var compilerErr error
26
27	if !filepath.IsAbs(inputCmd.Path) && !strings.HasPrefix(inputCmd.Path, ".") &&
28		!strings.ContainsRune(inputCmd.Path, filepath.Separator) {
29		if resolvedPath, err := resolveAgainstPathEnv(env, inputCmd.Path); err == nil {
30			inputCmd = &command{
31				Path:       resolvedPath,
32				Args:       inputCmd.Args,
33				EnvUpdates: inputCmd.EnvUpdates,
34			}
35		} else {
36			compilerErr = err
37		}
38	}
39	exitCode := 0
40	if compilerErr == nil {
41		exitCode, compilerErr = callCompilerInternal(env, cfg, inputCmd)
42	}
43	if compilerErr != nil {
44		printCompilerError(env.stderr(), compilerErr)
45		exitCode = 1
46	}
47	return exitCode
48}
49
50// Given the main builder path and the absolute path to our wrapper, returns the path to the
51// 'real' compiler we should invoke.
52func calculateAndroidWrapperPath(mainBuilderPath string, absWrapperPath string) string {
53	// FIXME: This combination of using the directory of the symlink but the basename of the
54	// link target is strange but is the logic that old android wrapper uses. Change this to use
55	// directory and basename either from the absWrapperPath or from the builder.path, but don't
56	// mix anymore.
57
58	// We need to be careful here: path.Join Clean()s its result, so `./foo` will get
59	// transformed to `foo`, which isn't good since we're passing this path to exec.
60	basePart := filepath.Base(absWrapperPath) + ".real"
61	if !strings.ContainsRune(mainBuilderPath, filepath.Separator) {
62		return basePart
63	}
64
65	dirPart := filepath.Dir(mainBuilderPath)
66	if cleanResult := filepath.Join(dirPart, basePart); strings.ContainsRune(cleanResult, filepath.Separator) {
67		return cleanResult
68	}
69
70	return "." + string(filepath.Separator) + basePart
71}
72
73func runAndroidClangTidy(env env, cmd *command) error {
74	timeout, found := env.getenv("TIDY_TIMEOUT")
75	if !found {
76		return env.exec(cmd)
77	}
78	seconds, err := strconv.Atoi(timeout)
79	if err != nil || seconds == 0 {
80		return env.exec(cmd)
81	}
82	getSourceFile := func() string {
83		// Note: This depends on Android build system's clang-tidy command line format.
84		// Last non-flag before "--" in cmd.Args is used as the source file name.
85		sourceFile := "unknown_file"
86		for _, arg := range cmd.Args {
87			if arg == "--" {
88				break
89			}
90			if strings.HasPrefix(arg, "-") {
91				continue
92			}
93			sourceFile = arg
94		}
95		return sourceFile
96	}
97	startTime := time.Now()
98	err = env.runWithTimeout(cmd, time.Duration(seconds)*time.Second)
99	if !errors.Is(err, context.DeadlineExceeded) {
100		// When used time is over half of TIDY_TIMEOUT, give a warning.
101		// These warnings allow users to fix slow jobs before they get worse.
102		usedSeconds := int(time.Since(startTime) / time.Second)
103		if usedSeconds > seconds/2 {
104			warning := "%s:1:1: warning: clang-tidy used %d seconds.\n"
105			fmt.Fprintf(env.stdout(), warning, getSourceFile(), usedSeconds)
106		}
107		return err
108	}
109	// When DeadllineExceeded, print warning messages.
110	warning := "%s:1:1: warning: clang-tidy aborted after %d seconds.\n"
111	fmt.Fprintf(env.stdout(), warning, getSourceFile(), seconds)
112	fmt.Fprintf(env.stdout(), "TIMEOUT: %s %s\n", cmd.Path, strings.Join(cmd.Args, " "))
113	// Do not stop Android build. Just give a warning and return no error.
114	return nil
115}
116
117func detectCrashArtifactsDir(env env, cfg *config) string {
118	if cfg.isAndroidWrapper {
119		return ""
120	}
121
122	tmpdir, ok := env.getenv(crosArtifactsEnvVar)
123	if !ok {
124		return ""
125	}
126	return filepath.Join(tmpdir, clangCrashArtifactsSubdir)
127}
128
129func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int, err error) {
130	if err := checkUnsupportedFlags(inputCmd); err != nil {
131		return 0, err
132	}
133	mainBuilder, err := newCommandBuilder(env, cfg, inputCmd)
134	if err != nil {
135		return 0, err
136	}
137	processPrintConfigFlag(mainBuilder)
138	processPrintCmdlineFlag(mainBuilder)
139	env = mainBuilder.env
140	var compilerCmd *command
141	disableWerrorConfig := processForceDisableWerrorFlag(env, cfg, mainBuilder)
142	clangSyntax := processClangSyntaxFlag(mainBuilder)
143
144	rusageEnabled := isRusageEnabled(env)
145
146	// Disable CCache for rusage logs
147	// Note: Disabling Goma causes timeout related INFRA_FAILUREs in builders
148	allowCCache := !rusageEnabled
149	remoteBuildUsed := false
150
151	workAroundKernelBugWithRetries := false
152	if cfg.isAndroidWrapper {
153		mainBuilder.path = calculateAndroidWrapperPath(mainBuilder.path, mainBuilder.absWrapperPath)
154		switch mainBuilder.target.compilerType {
155		case clangType:
156			mainBuilder.addPreUserArgs(mainBuilder.cfg.clangFlags...)
157			mainBuilder.addPreUserArgs(mainBuilder.cfg.commonFlags...)
158			mainBuilder.addPostUserArgs(mainBuilder.cfg.clangPostFlags...)
159			inheritGomaFromEnv := true
160			// Android doesn't support rewrapper; don't try to use it.
161			if remoteBuildUsed, err = processGomaCccFlags(mainBuilder, inheritGomaFromEnv); err != nil {
162				return 0, err
163			}
164			compilerCmd = mainBuilder.build()
165		case clangTidyType:
166			compilerCmd = mainBuilder.build()
167		default:
168			return 0, newErrorwithSourceLocf("unsupported compiler: %s", mainBuilder.target.compiler)
169		}
170	} else {
171		cSrcFile, tidyFlags, tidyMode := processClangTidyFlags(mainBuilder)
172		crashArtifactsDir := detectCrashArtifactsDir(env, cfg)
173		if mainBuilder.target.compilerType == clangType {
174			err := prepareClangCommand(crashArtifactsDir, mainBuilder)
175			if err != nil {
176				return 0, err
177			}
178			if tidyMode != tidyModeNone {
179				allowCCache = false
180				// Remove and ignore goma flags.
181				_, err := removeOneUserCmdlineFlagWithValue(mainBuilder, "--gomacc-path")
182				if err != nil && err != errNoSuchCmdlineArg {
183					return 0, err
184				}
185
186				clangCmdWithoutRemoteBuildAndCCache := mainBuilder.build()
187
188				switch tidyMode {
189				case tidyModeTricium:
190					err = runClangTidyForTricium(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags, crashArtifactsDir)
191				case tidyModeAll:
192					err = runClangTidy(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags)
193				default:
194					panic(fmt.Sprintf("Unknown tidy mode: %v", tidyMode))
195				}
196
197				if err != nil {
198					return 0, err
199				}
200			}
201
202			if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(allowCCache, mainBuilder); err != nil {
203				return 0, err
204			}
205			compilerCmd = mainBuilder.build()
206		} else {
207			if clangSyntax {
208				allowCCache = false
209				_, clangCmd, err := calcClangCommand(crashArtifactsDir, allowCCache, mainBuilder.clone())
210				if err != nil {
211					return 0, err
212				}
213				_, gccCmd, err := calcGccCommand(rusageEnabled, mainBuilder)
214				if err != nil {
215					return 0, err
216				}
217				return checkClangSyntax(env, clangCmd, gccCmd)
218			}
219			remoteBuildUsed, compilerCmd, err = calcGccCommand(rusageEnabled, mainBuilder)
220			if err != nil {
221				return 0, err
222			}
223			workAroundKernelBugWithRetries = true
224		}
225	}
226
227	// If builds matching some heuristic should crash, crash them. Since this is purely a
228	// debugging tool, don't offer any nice features with it (e.g., rusage, ...).
229	if shouldUseCrashBuildsHeuristic && mainBuilder.target.compilerType == clangType {
230		return buildWithAutocrash(env, cfg, compilerCmd)
231	}
232
233	bisectStage := getBisectStage(env)
234
235	if rusageEnabled {
236		compilerCmd = removeRusageFromCommand(compilerCmd)
237	}
238
239	if disableWerrorConfig.enabled {
240		if bisectStage != "" {
241			return 0, newUserErrorf("BISECT_STAGE is meaningless with FORCE_DISABLE_WERROR")
242		}
243		return doubleBuildWithWNoError(env, cfg, compilerCmd, disableWerrorConfig)
244	}
245	if shouldCompileWithFallback(env) {
246		if rusageEnabled {
247			return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH")
248		}
249		if bisectStage != "" {
250			return 0, newUserErrorf("BISECT_STAGE is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH")
251		}
252		return compileWithFallback(env, cfg, compilerCmd, mainBuilder.absWrapperPath)
253	}
254	if bisectStage != "" {
255		if rusageEnabled {
256			return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with BISECT_STAGE")
257		}
258		compilerCmd, err = calcBisectCommand(env, cfg, bisectStage, compilerCmd)
259		if err != nil {
260			return 0, err
261		}
262	}
263
264	errRetryCompilation := errors.New("compilation retry requested")
265	var runCompiler func(willLogRusage bool) (int, error)
266	if !workAroundKernelBugWithRetries {
267		runCompiler = func(willLogRusage bool) (int, error) {
268			var err error
269			if willLogRusage {
270				err = env.run(compilerCmd, env.stdin(), env.stdout(), env.stderr())
271			} else if cfg.isAndroidWrapper && mainBuilder.target.compilerType == clangTidyType {
272				// Only clang-tidy has timeout feature now.
273				err = runAndroidClangTidy(env, compilerCmd)
274			} else {
275				// Note: We return from this in non-fatal circumstances only if the
276				// underlying env is not really doing an exec, e.g. commandRecordingEnv.
277				err = env.exec(compilerCmd)
278			}
279			return wrapSubprocessErrorWithSourceLoc(compilerCmd, err)
280		}
281	} else {
282		getStdin, err := prebufferStdinIfNeeded(env, compilerCmd)
283		if err != nil {
284			return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin: %v", err)
285		}
286
287		stdoutBuffer := &bytes.Buffer{}
288		stderrBuffer := &bytes.Buffer{}
289		retryAttempt := 0
290		runCompiler = func(willLogRusage bool) (int, error) {
291			retryAttempt++
292			stdoutBuffer.Reset()
293			stderrBuffer.Reset()
294
295			exitCode, compilerErr := wrapSubprocessErrorWithSourceLoc(compilerCmd,
296				env.run(compilerCmd, getStdin(), stdoutBuffer, stderrBuffer))
297
298			if compilerErr != nil || exitCode != 0 {
299				if retryAttempt < kernelBugRetryLimit && (errorContainsTracesOfKernelBug(compilerErr) || containsTracesOfKernelBug(stdoutBuffer.Bytes()) || containsTracesOfKernelBug(stderrBuffer.Bytes())) {
300					return exitCode, errRetryCompilation
301				}
302			}
303			_, stdoutErr := stdoutBuffer.WriteTo(env.stdout())
304			_, stderrErr := stderrBuffer.WriteTo(env.stderr())
305			if stdoutErr != nil {
306				return exitCode, wrapErrorwithSourceLocf(err, "writing stdout: %v", stdoutErr)
307			}
308			if stderrErr != nil {
309				return exitCode, wrapErrorwithSourceLocf(err, "writing stderr: %v", stderrErr)
310			}
311			return exitCode, compilerErr
312		}
313	}
314
315	for {
316		var exitCode int
317		commitRusage, err := maybeCaptureRusage(env, compilerCmd, func(willLogRusage bool) error {
318			var err error
319			exitCode, err = runCompiler(willLogRusage)
320			return err
321		})
322
323		switch {
324		case err == errRetryCompilation:
325			// Loop around again.
326		case err != nil:
327			return exitCode, err
328		default:
329			if !remoteBuildUsed {
330				if err := commitRusage(exitCode); err != nil {
331					return exitCode, fmt.Errorf("commiting rusage: %v", err)
332				}
333			}
334			return exitCode, err
335		}
336	}
337}
338
339func hasUserArg(argName string, builder *commandBuilder) bool {
340	for _, argValue := range builder.args {
341		if strings.Contains(argValue.value, argName) && argValue.fromUser {
342			return true
343		}
344	}
345	return false
346}
347
348func prepareClangCommand(crashArtifactsDir string, builder *commandBuilder) (err error) {
349	if !builder.cfg.isHostWrapper {
350		processSysrootFlag(builder)
351	}
352	builder.addPreUserArgs(builder.cfg.clangFlags...)
353
354	var crashDiagFlagName = "-fcrash-diagnostics-dir"
355	if crashArtifactsDir != "" &&
356		!hasUserArg(crashDiagFlagName, builder) {
357		builder.addPreUserArgs(crashDiagFlagName + "=" + crashArtifactsDir)
358	}
359
360	builder.addPostUserArgs(builder.cfg.clangPostFlags...)
361	calcCommonPreUserArgs(builder)
362	return processClangFlags(builder)
363}
364
365func calcClangCommand(crashArtifactsDir string, allowCCache bool, builder *commandBuilder) (bool, *command, error) {
366	err := prepareClangCommand(crashArtifactsDir, builder)
367	if err != nil {
368		return false, nil, err
369	}
370	remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(allowCCache, builder)
371	if err != nil {
372		return remoteBuildUsed, nil, err
373	}
374	return remoteBuildUsed, builder.build(), nil
375}
376
377func calcGccCommand(enableRusage bool, builder *commandBuilder) (bool, *command, error) {
378	if !builder.cfg.isHostWrapper {
379		processSysrootFlag(builder)
380	}
381	builder.addPreUserArgs(builder.cfg.gccFlags...)
382	calcCommonPreUserArgs(builder)
383	processGccFlags(builder)
384
385	remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(!enableRusage, builder)
386	if err != nil {
387		return remoteBuildUsed, nil, err
388	}
389	return remoteBuildUsed, builder.build(), nil
390}
391
392func calcCommonPreUserArgs(builder *commandBuilder) {
393	builder.addPreUserArgs(builder.cfg.commonFlags...)
394	if !builder.cfg.isHostWrapper {
395		processLibGCCFlags(builder)
396		processThumbCodeFlags(builder)
397		processStackProtectorFlags(builder)
398		processX86Flags(builder)
399	}
400	processSanitizerFlags(builder)
401}
402
403func processRemoteBuildAndCCacheFlags(allowCCache bool, builder *commandBuilder) (remoteBuildUsed bool, err error) {
404	remoteBuildUsed, err = processRemoteBuildFlags(builder)
405	if err != nil {
406		return remoteBuildUsed, err
407	}
408	if !remoteBuildUsed && allowCCache {
409		processCCacheFlag(builder)
410	}
411	return remoteBuildUsed, nil
412}
413
414func getAbsWrapperPath(env env, wrapperCmd *command) (string, error) {
415	wrapperPath := getAbsCmdPath(env, wrapperCmd)
416	evaledCmdPath, err := filepath.EvalSymlinks(wrapperPath)
417	if err != nil {
418		return "", wrapErrorwithSourceLocf(err, "failed to evaluate symlinks for %s", wrapperPath)
419	}
420	return evaledCmdPath, nil
421}
422
423func printCompilerError(writer io.Writer, compilerErr error) {
424	if _, ok := compilerErr.(userError); ok {
425		fmt.Fprintf(writer, "%s\n", compilerErr)
426	} else {
427		emailAccount := "chromeos-toolchain"
428		if isAndroidConfig() {
429			emailAccount = "android-llvm"
430		}
431		fmt.Fprintf(writer,
432			"Internal error. Please report to %[email protected].\n%s\n",
433			emailAccount, compilerErr)
434	}
435}
436
437func needStdinTee(inputCmd *command) bool {
438	lastArg := ""
439	for _, arg := range inputCmd.Args {
440		if arg == "-" && lastArg != "-o" {
441			return true
442		}
443		lastArg = arg
444	}
445	return false
446}
447
448func prebufferStdinIfNeeded(env env, inputCmd *command) (getStdin func() io.Reader, err error) {
449	// We pre-buffer the entirety of stdin, since the compiler may exit mid-invocation with an
450	// error, which may leave stdin partially read.
451	if !needStdinTee(inputCmd) {
452		// This won't produce deterministic input to the compiler, but stdin shouldn't
453		// matter in this case, so...
454		return env.stdin, nil
455	}
456
457	stdinBuffer := &bytes.Buffer{}
458	if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil {
459		return nil, wrapErrorwithSourceLocf(err, "prebuffering stdin")
460	}
461
462	return func() io.Reader { return bytes.NewReader(stdinBuffer.Bytes()) }, nil
463}
464