• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"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		if mainBuilder.target.compilerType == clangType {
155			err := prepareClangCommand(mainBuilder)
156			if err != nil {
157				return 0, err
158			}
159			if tidyMode != tidyModeNone {
160				allowCCache = false
161				clangCmdWithoutRemoteBuildAndCCache := mainBuilder.build()
162				var err error
163				switch tidyMode {
164				case tidyModeTricium:
165					if cfg.triciumNitsDir == "" {
166						return 0, newErrorwithSourceLocf("tricium linting was requested, but no nits directory is configured")
167					}
168					err = runClangTidyForTricium(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, cfg.triciumNitsDir, tidyFlags, cfg.crashArtifactsDir)
169				case tidyModeAll:
170					err = runClangTidy(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags)
171				default:
172					panic(fmt.Sprintf("Unknown tidy mode: %v", tidyMode))
173				}
174
175				if err != nil {
176					return 0, err
177				}
178			}
179			if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(allowCCache, mainBuilder); err != nil {
180				return 0, err
181			}
182			compilerCmd = mainBuilder.build()
183		} else {
184			if clangSyntax {
185				allowCCache = false
186				_, clangCmd, err := calcClangCommand(allowCCache, mainBuilder.clone())
187				if err != nil {
188					return 0, err
189				}
190				_, gccCmd, err := calcGccCommand(rusageEnabled, mainBuilder)
191				if err != nil {
192					return 0, err
193				}
194				return checkClangSyntax(env, clangCmd, gccCmd)
195			}
196			remoteBuildUsed, compilerCmd, err = calcGccCommand(rusageEnabled, mainBuilder)
197			if err != nil {
198				return 0, err
199			}
200			workAroundKernelBugWithRetries = true
201		}
202	}
203
204	bisectStage := getBisectStage(env)
205
206	if rusageEnabled {
207		compilerCmd = removeRusageFromCommand(compilerCmd)
208	}
209
210	if shouldForceDisableWerror(env, cfg, mainBuilder.target.compilerType) {
211		if bisectStage != "" {
212			return 0, newUserErrorf("BISECT_STAGE is meaningless with FORCE_DISABLE_WERROR")
213		}
214		return doubleBuildWithWNoError(env, cfg, compilerCmd)
215	}
216	if shouldCompileWithFallback(env) {
217		if rusageEnabled {
218			return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH")
219		}
220		if bisectStage != "" {
221			return 0, newUserErrorf("BISECT_STAGE is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH")
222		}
223		return compileWithFallback(env, cfg, compilerCmd, mainBuilder.absWrapperPath)
224	}
225	if bisectStage != "" {
226		if rusageEnabled {
227			return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with BISECT_STAGE")
228		}
229		compilerCmd, err = calcBisectCommand(env, cfg, bisectStage, compilerCmd)
230		if err != nil {
231			return 0, err
232		}
233	}
234
235	errRetryCompilation := errors.New("compilation retry requested")
236	var runCompiler func(willLogRusage bool) (int, error)
237	if !workAroundKernelBugWithRetries {
238		runCompiler = func(willLogRusage bool) (int, error) {
239			var err error
240			if willLogRusage {
241				err = env.run(compilerCmd, env.stdin(), env.stdout(), env.stderr())
242			} else if cfg.isAndroidWrapper && mainBuilder.target.compilerType == clangTidyType {
243				// Only clang-tidy has timeout feature now.
244				err = runAndroidClangTidy(env, compilerCmd)
245			} else {
246				// Note: We return from this in non-fatal circumstances only if the
247				// underlying env is not really doing an exec, e.g. commandRecordingEnv.
248				err = env.exec(compilerCmd)
249			}
250			return wrapSubprocessErrorWithSourceLoc(compilerCmd, err)
251		}
252	} else {
253		getStdin, err := prebufferStdinIfNeeded(env, compilerCmd)
254		if err != nil {
255			return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin: %v", err)
256		}
257
258		stdoutBuffer := &bytes.Buffer{}
259		stderrBuffer := &bytes.Buffer{}
260		retryAttempt := 0
261		runCompiler = func(willLogRusage bool) (int, error) {
262			retryAttempt++
263			stdoutBuffer.Reset()
264			stderrBuffer.Reset()
265
266			exitCode, compilerErr := wrapSubprocessErrorWithSourceLoc(compilerCmd,
267				env.run(compilerCmd, getStdin(), stdoutBuffer, stderrBuffer))
268
269			if compilerErr != nil || exitCode != 0 {
270				if retryAttempt < kernelBugRetryLimit && (errorContainsTracesOfKernelBug(compilerErr) || containsTracesOfKernelBug(stdoutBuffer.Bytes()) || containsTracesOfKernelBug(stderrBuffer.Bytes())) {
271					return exitCode, errRetryCompilation
272				}
273			}
274			_, stdoutErr := stdoutBuffer.WriteTo(env.stdout())
275			_, stderrErr := stderrBuffer.WriteTo(env.stderr())
276			if stdoutErr != nil {
277				return exitCode, wrapErrorwithSourceLocf(err, "writing stdout: %v", stdoutErr)
278			}
279			if stderrErr != nil {
280				return exitCode, wrapErrorwithSourceLocf(err, "writing stderr: %v", stderrErr)
281			}
282			return exitCode, compilerErr
283		}
284	}
285
286	for {
287		var exitCode int
288		commitRusage, err := maybeCaptureRusage(env, compilerCmd, func(willLogRusage bool) error {
289			var err error
290			exitCode, err = runCompiler(willLogRusage)
291			return err
292		})
293
294		switch {
295		case err == errRetryCompilation:
296			// Loop around again.
297		case err != nil:
298			return exitCode, err
299		default:
300			if !remoteBuildUsed {
301				if err := commitRusage(exitCode); err != nil {
302					return exitCode, fmt.Errorf("commiting rusage: %v", err)
303				}
304			}
305			return exitCode, err
306		}
307	}
308}
309
310func prepareClangCommand(builder *commandBuilder) (err error) {
311	if !builder.cfg.isHostWrapper {
312		processSysrootFlag(builder)
313	}
314	builder.addPreUserArgs(builder.cfg.clangFlags...)
315	if builder.cfg.crashArtifactsDir != "" {
316		builder.addPreUserArgs("-fcrash-diagnostics-dir=" + builder.cfg.crashArtifactsDir)
317	}
318	builder.addPostUserArgs(builder.cfg.clangPostFlags...)
319	calcCommonPreUserArgs(builder)
320	return processClangFlags(builder)
321}
322
323func calcClangCommand(allowCCache bool, builder *commandBuilder) (bool, *command, error) {
324	err := prepareClangCommand(builder)
325	if err != nil {
326		return false, nil, err
327	}
328	remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(allowCCache, builder)
329	if err != nil {
330		return remoteBuildUsed, nil, err
331	}
332	return remoteBuildUsed, builder.build(), nil
333}
334
335func calcGccCommand(enableRusage bool, builder *commandBuilder) (bool, *command, error) {
336	if !builder.cfg.isHostWrapper {
337		processSysrootFlag(builder)
338	}
339	builder.addPreUserArgs(builder.cfg.gccFlags...)
340	calcCommonPreUserArgs(builder)
341	processGccFlags(builder)
342
343	remoteBuildUsed := false
344	if !builder.cfg.isHostWrapper {
345		var err error
346		if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(!enableRusage, builder); err != nil {
347			return remoteBuildUsed, nil, err
348		}
349	}
350	return remoteBuildUsed, builder.build(), nil
351}
352
353func calcCommonPreUserArgs(builder *commandBuilder) {
354	builder.addPreUserArgs(builder.cfg.commonFlags...)
355	if !builder.cfg.isHostWrapper {
356		processLibGCCFlags(builder)
357		processPieFlags(builder)
358		processThumbCodeFlags(builder)
359		processStackProtectorFlags(builder)
360		processX86Flags(builder)
361	}
362	processSanitizerFlags(builder)
363}
364
365func processRemoteBuildAndCCacheFlags(allowCCache bool, builder *commandBuilder) (remoteBuildUsed bool, err error) {
366	remoteBuildUsed = false
367	if !builder.cfg.isHostWrapper {
368		remoteBuildUsed, err = processRemoteBuildFlags(builder)
369		if err != nil {
370			return remoteBuildUsed, err
371		}
372	}
373	if !remoteBuildUsed && allowCCache {
374		processCCacheFlag(builder)
375	}
376	return remoteBuildUsed, nil
377}
378
379func getAbsWrapperPath(env env, wrapperCmd *command) (string, error) {
380	wrapperPath := getAbsCmdPath(env, wrapperCmd)
381	evaledCmdPath, err := filepath.EvalSymlinks(wrapperPath)
382	if err != nil {
383		return "", wrapErrorwithSourceLocf(err, "failed to evaluate symlinks for %s", wrapperPath)
384	}
385	return evaledCmdPath, nil
386}
387
388func printCompilerError(writer io.Writer, compilerErr error) {
389	if _, ok := compilerErr.(userError); ok {
390		fmt.Fprintf(writer, "%s\n", compilerErr)
391	} else {
392		emailAccount := "chromeos-toolchain"
393		if isAndroidConfig() {
394			emailAccount = "android-llvm"
395		}
396		fmt.Fprintf(writer,
397			"Internal error. Please report to %s@google.com.\n%s\n",
398			emailAccount, compilerErr)
399	}
400}
401
402func needStdinTee(inputCmd *command) bool {
403	lastArg := ""
404	for _, arg := range inputCmd.Args {
405		if arg == "-" && lastArg != "-o" {
406			return true
407		}
408		lastArg = arg
409	}
410	return false
411}
412
413func prebufferStdinIfNeeded(env env, inputCmd *command) (getStdin func() io.Reader, err error) {
414	// We pre-buffer the entirety of stdin, since the compiler may exit mid-invocation with an
415	// error, which may leave stdin partially read.
416	if !needStdinTee(inputCmd) {
417		// This won't produce deterministic input to the compiler, but stdin shouldn't
418		// matter in this case, so...
419		return env.stdin, nil
420	}
421
422	stdinBuffer := &bytes.Buffer{}
423	if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil {
424		return nil, wrapErrorwithSourceLocf(err, "prebuffering stdin")
425	}
426
427	return func() io.Reader { return bytes.NewReader(stdinBuffer.Bytes()) }, nil
428}
429