// Copyright 2019 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package main import ( "bytes" "context" "errors" "fmt" "io" "path/filepath" "strconv" "strings" "time" ) func callCompiler(env env, cfg *config, inputCmd *command) int { var compilerErr error if !filepath.IsAbs(inputCmd.Path) && !strings.HasPrefix(inputCmd.Path, ".") && !strings.ContainsRune(inputCmd.Path, filepath.Separator) { if resolvedPath, err := resolveAgainstPathEnv(env, inputCmd.Path); err == nil { inputCmd = &command{ Path: resolvedPath, Args: inputCmd.Args, EnvUpdates: inputCmd.EnvUpdates, } } else { compilerErr = err } } exitCode := 0 if compilerErr == nil { exitCode, compilerErr = callCompilerInternal(env, cfg, inputCmd) } if compilerErr != nil { printCompilerError(env.stderr(), compilerErr) exitCode = 1 } return exitCode } // Given the main builder path and the absolute path to our wrapper, returns the path to the // 'real' compiler we should invoke. func calculateAndroidWrapperPath(mainBuilderPath string, absWrapperPath string) string { // FIXME: This combination of using the directory of the symlink but the basename of the // link target is strange but is the logic that old android wrapper uses. Change this to use // directory and basename either from the absWrapperPath or from the builder.path, but don't // mix anymore. // We need to be careful here: path.Join Clean()s its result, so `./foo` will get // transformed to `foo`, which isn't good since we're passing this path to exec. basePart := filepath.Base(absWrapperPath) + ".real" if !strings.ContainsRune(mainBuilderPath, filepath.Separator) { return basePart } dirPart := filepath.Dir(mainBuilderPath) if cleanResult := filepath.Join(dirPart, basePart); strings.ContainsRune(cleanResult, filepath.Separator) { return cleanResult } return "." + string(filepath.Separator) + basePart } func runAndroidClangTidy(env env, cmd *command) error { timeout, found := env.getenv("TIDY_TIMEOUT") if !found { return env.exec(cmd) } seconds, err := strconv.Atoi(timeout) if err != nil || seconds == 0 { return env.exec(cmd) } getSourceFile := func() string { // Note: This depends on Android build system's clang-tidy command line format. // Last non-flag before "--" in cmd.Args is used as the source file name. sourceFile := "unknown_file" for _, arg := range cmd.Args { if arg == "--" { break } if strings.HasPrefix(arg, "-") { continue } sourceFile = arg } return sourceFile } startTime := time.Now() err = env.runWithTimeout(cmd, time.Duration(seconds)*time.Second) if !errors.Is(err, context.DeadlineExceeded) { // When used time is over half of TIDY_TIMEOUT, give a warning. // These warnings allow users to fix slow jobs before they get worse. usedSeconds := int(time.Now().Sub(startTime) / time.Second) if usedSeconds > seconds/2 { warning := "%s:1:1: warning: clang-tidy used %d seconds.\n" fmt.Fprintf(env.stdout(), warning, getSourceFile(), usedSeconds) } return err } // When DeadllineExceeded, print warning messages. warning := "%s:1:1: warning: clang-tidy aborted after %d seconds.\n" fmt.Fprintf(env.stdout(), warning, getSourceFile(), seconds) fmt.Fprintf(env.stdout(), "TIMEOUT: %s %s\n", cmd.Path, strings.Join(cmd.Args, " ")) // Do not stop Android build. Just give a warning and return no error. return nil } func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int, err error) { if err := checkUnsupportedFlags(inputCmd); err != nil { return 0, err } mainBuilder, err := newCommandBuilder(env, cfg, inputCmd) if err != nil { return 0, err } processPrintConfigFlag(mainBuilder) processPrintCmdlineFlag(mainBuilder) env = mainBuilder.env var compilerCmd *command clangSyntax := processClangSyntaxFlag(mainBuilder) rusageEnabled := isRusageEnabled(env) // Disable CCache for rusage logs // Note: Disabling Goma causes timeout related INFRA_FAILUREs in builders allowCCache := !rusageEnabled remoteBuildUsed := false workAroundKernelBugWithRetries := false if cfg.isAndroidWrapper { mainBuilder.path = calculateAndroidWrapperPath(mainBuilder.path, mainBuilder.absWrapperPath) switch mainBuilder.target.compilerType { case clangType: mainBuilder.addPreUserArgs(mainBuilder.cfg.clangFlags...) mainBuilder.addPreUserArgs(mainBuilder.cfg.commonFlags...) mainBuilder.addPostUserArgs(mainBuilder.cfg.clangPostFlags...) inheritGomaFromEnv := true // Android doesn't support rewrapper; don't try to use it. if remoteBuildUsed, err = processGomaCccFlags(mainBuilder, inheritGomaFromEnv); err != nil { return 0, err } compilerCmd = mainBuilder.build() case clangTidyType: compilerCmd = mainBuilder.build() default: return 0, newErrorwithSourceLocf("unsupported compiler: %s", mainBuilder.target.compiler) } } else { cSrcFile, tidyFlags, tidyMode := processClangTidyFlags(mainBuilder) if mainBuilder.target.compilerType == clangType { err := prepareClangCommand(mainBuilder) if err != nil { return 0, err } if tidyMode != tidyModeNone { allowCCache = false clangCmdWithoutRemoteBuildAndCCache := mainBuilder.build() var err error switch tidyMode { case tidyModeTricium: if cfg.triciumNitsDir == "" { return 0, newErrorwithSourceLocf("tricium linting was requested, but no nits directory is configured") } err = runClangTidyForTricium(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, cfg.triciumNitsDir, tidyFlags, cfg.crashArtifactsDir) case tidyModeAll: err = runClangTidy(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags) default: panic(fmt.Sprintf("Unknown tidy mode: %v", tidyMode)) } if err != nil { return 0, err } } if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(allowCCache, mainBuilder); err != nil { return 0, err } compilerCmd = mainBuilder.build() } else { if clangSyntax { allowCCache = false _, clangCmd, err := calcClangCommand(allowCCache, mainBuilder.clone()) if err != nil { return 0, err } _, gccCmd, err := calcGccCommand(rusageEnabled, mainBuilder) if err != nil { return 0, err } return checkClangSyntax(env, clangCmd, gccCmd) } remoteBuildUsed, compilerCmd, err = calcGccCommand(rusageEnabled, mainBuilder) if err != nil { return 0, err } workAroundKernelBugWithRetries = true } } bisectStage := getBisectStage(env) if rusageEnabled { compilerCmd = removeRusageFromCommand(compilerCmd) } if shouldForceDisableWerror(env, cfg, mainBuilder.target.compilerType) { if bisectStage != "" { return 0, newUserErrorf("BISECT_STAGE is meaningless with FORCE_DISABLE_WERROR") } return doubleBuildWithWNoError(env, cfg, compilerCmd) } if shouldCompileWithFallback(env) { if rusageEnabled { return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH") } if bisectStage != "" { return 0, newUserErrorf("BISECT_STAGE is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH") } return compileWithFallback(env, cfg, compilerCmd, mainBuilder.absWrapperPath) } if bisectStage != "" { if rusageEnabled { return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with BISECT_STAGE") } compilerCmd, err = calcBisectCommand(env, cfg, bisectStage, compilerCmd) if err != nil { return 0, err } } errRetryCompilation := errors.New("compilation retry requested") var runCompiler func(willLogRusage bool) (int, error) if !workAroundKernelBugWithRetries { runCompiler = func(willLogRusage bool) (int, error) { var err error if willLogRusage { err = env.run(compilerCmd, env.stdin(), env.stdout(), env.stderr()) } else if cfg.isAndroidWrapper && mainBuilder.target.compilerType == clangTidyType { // Only clang-tidy has timeout feature now. err = runAndroidClangTidy(env, compilerCmd) } else { // Note: We return from this in non-fatal circumstances only if the // underlying env is not really doing an exec, e.g. commandRecordingEnv. err = env.exec(compilerCmd) } return wrapSubprocessErrorWithSourceLoc(compilerCmd, err) } } else { getStdin, err := prebufferStdinIfNeeded(env, compilerCmd) if err != nil { return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin: %v", err) } stdoutBuffer := &bytes.Buffer{} stderrBuffer := &bytes.Buffer{} retryAttempt := 0 runCompiler = func(willLogRusage bool) (int, error) { retryAttempt++ stdoutBuffer.Reset() stderrBuffer.Reset() exitCode, compilerErr := wrapSubprocessErrorWithSourceLoc(compilerCmd, env.run(compilerCmd, getStdin(), stdoutBuffer, stderrBuffer)) if compilerErr != nil || exitCode != 0 { if retryAttempt < kernelBugRetryLimit && (errorContainsTracesOfKernelBug(compilerErr) || containsTracesOfKernelBug(stdoutBuffer.Bytes()) || containsTracesOfKernelBug(stderrBuffer.Bytes())) { return exitCode, errRetryCompilation } } _, stdoutErr := stdoutBuffer.WriteTo(env.stdout()) _, stderrErr := stderrBuffer.WriteTo(env.stderr()) if stdoutErr != nil { return exitCode, wrapErrorwithSourceLocf(err, "writing stdout: %v", stdoutErr) } if stderrErr != nil { return exitCode, wrapErrorwithSourceLocf(err, "writing stderr: %v", stderrErr) } return exitCode, compilerErr } } for { var exitCode int commitRusage, err := maybeCaptureRusage(env, compilerCmd, func(willLogRusage bool) error { var err error exitCode, err = runCompiler(willLogRusage) return err }) switch { case err == errRetryCompilation: // Loop around again. case err != nil: return exitCode, err default: if !remoteBuildUsed { if err := commitRusage(exitCode); err != nil { return exitCode, fmt.Errorf("commiting rusage: %v", err) } } return exitCode, err } } } func prepareClangCommand(builder *commandBuilder) (err error) { if !builder.cfg.isHostWrapper { processSysrootFlag(builder) } builder.addPreUserArgs(builder.cfg.clangFlags...) if builder.cfg.crashArtifactsDir != "" { builder.addPreUserArgs("-fcrash-diagnostics-dir=" + builder.cfg.crashArtifactsDir) } builder.addPostUserArgs(builder.cfg.clangPostFlags...) calcCommonPreUserArgs(builder) return processClangFlags(builder) } func calcClangCommand(allowCCache bool, builder *commandBuilder) (bool, *command, error) { err := prepareClangCommand(builder) if err != nil { return false, nil, err } remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(allowCCache, builder) if err != nil { return remoteBuildUsed, nil, err } return remoteBuildUsed, builder.build(), nil } func calcGccCommand(enableRusage bool, builder *commandBuilder) (bool, *command, error) { if !builder.cfg.isHostWrapper { processSysrootFlag(builder) } builder.addPreUserArgs(builder.cfg.gccFlags...) calcCommonPreUserArgs(builder) processGccFlags(builder) remoteBuildUsed := false if !builder.cfg.isHostWrapper { var err error if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(!enableRusage, builder); err != nil { return remoteBuildUsed, nil, err } } return remoteBuildUsed, builder.build(), nil } func calcCommonPreUserArgs(builder *commandBuilder) { builder.addPreUserArgs(builder.cfg.commonFlags...) if !builder.cfg.isHostWrapper { processLibGCCFlags(builder) processPieFlags(builder) processThumbCodeFlags(builder) processStackProtectorFlags(builder) processX86Flags(builder) } processSanitizerFlags(builder) } func processRemoteBuildAndCCacheFlags(allowCCache bool, builder *commandBuilder) (remoteBuildUsed bool, err error) { remoteBuildUsed = false if !builder.cfg.isHostWrapper { remoteBuildUsed, err = processRemoteBuildFlags(builder) if err != nil { return remoteBuildUsed, err } } if !remoteBuildUsed && allowCCache { processCCacheFlag(builder) } return remoteBuildUsed, nil } func getAbsWrapperPath(env env, wrapperCmd *command) (string, error) { wrapperPath := getAbsCmdPath(env, wrapperCmd) evaledCmdPath, err := filepath.EvalSymlinks(wrapperPath) if err != nil { return "", wrapErrorwithSourceLocf(err, "failed to evaluate symlinks for %s", wrapperPath) } return evaledCmdPath, nil } func printCompilerError(writer io.Writer, compilerErr error) { if _, ok := compilerErr.(userError); ok { fmt.Fprintf(writer, "%s\n", compilerErr) } else { emailAccount := "chromeos-toolchain" if isAndroidConfig() { emailAccount = "android-llvm" } fmt.Fprintf(writer, "Internal error. Please report to %s@google.com.\n%s\n", emailAccount, compilerErr) } } func needStdinTee(inputCmd *command) bool { lastArg := "" for _, arg := range inputCmd.Args { if arg == "-" && lastArg != "-o" { return true } lastArg = arg } return false } func prebufferStdinIfNeeded(env env, inputCmd *command) (getStdin func() io.Reader, err error) { // We pre-buffer the entirety of stdin, since the compiler may exit mid-invocation with an // error, which may leave stdin partially read. if !needStdinTee(inputCmd) { // This won't produce deterministic input to the compiler, but stdin shouldn't // matter in this case, so... return env.stdin, nil } stdinBuffer := &bytes.Buffer{} if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil { return nil, wrapErrorwithSourceLocf(err, "prebuffering stdin") } return func() io.Reader { return bytes.NewReader(stdinBuffer.Bytes()) }, nil }