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