diff options
Diffstat (limited to 'compiler_wrapper/compiler_wrapper.go')
-rw-r--r-- | compiler_wrapper/compiler_wrapper.go | 223 |
1 files changed, 177 insertions, 46 deletions
diff --git a/compiler_wrapper/compiler_wrapper.go b/compiler_wrapper/compiler_wrapper.go index 2b9459e4..986eabab 100644 --- a/compiler_wrapper/compiler_wrapper.go +++ b/compiler_wrapper/compiler_wrapper.go @@ -6,10 +6,14 @@ package main import ( "bytes" + "context" + "errors" "fmt" "io" "path/filepath" + "strconv" "strings" + "time" ) func callCompiler(env env, cfg *config, inputCmd *command) int { @@ -61,6 +65,50 @@ func calculateAndroidWrapperPath(mainBuilderPath string, absWrapperPath string) 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 @@ -74,6 +122,15 @@ func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int 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 { @@ -81,7 +138,9 @@ func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int mainBuilder.addPreUserArgs(mainBuilder.cfg.clangFlags...) mainBuilder.addPreUserArgs(mainBuilder.cfg.commonFlags...) mainBuilder.addPostUserArgs(mainBuilder.cfg.clangPostFlags...) - if _, err := processGomaCccFlags(mainBuilder); err != nil { + 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() @@ -97,19 +156,18 @@ func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int if err != nil { return 0, err } - allowCCache := true if tidyMode != tidyModeNone { allowCCache = false - clangCmdWithoutGomaAndCCache := mainBuilder.build() + 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, clangCmdWithoutGomaAndCCache, cSrcFile, cfg.triciumNitsDir, tidyFlags, cfg.crashArtifactsDir) + err = runClangTidyForTricium(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, cfg.triciumNitsDir, tidyFlags, cfg.crashArtifactsDir) case tidyModeAll: - err = runClangTidy(env, clangCmdWithoutGomaAndCCache, cSrcFile, tidyFlags) + err = runClangTidy(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags) default: panic(fmt.Sprintf("Unknown tidy mode: %v", tidyMode)) } @@ -118,64 +176,135 @@ func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int return 0, err } } - if err := processGomaCCacheFlags(allowCCache, mainBuilder); err != nil { + 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()) + allowCCache = false + _, clangCmd, err := calcClangCommand(allowCCache, mainBuilder.clone()) if err != nil { return 0, err } - gccCmd, err := calcGccCommand(mainBuilder) + _, gccCmd, err := calcGccCommand(rusageEnabled, mainBuilder) if err != nil { return 0, err } return checkClangSyntax(env, clangCmd, gccCmd) } - compilerCmd, err = calcGccCommand(mainBuilder) + remoteBuildUsed, compilerCmd, err = calcGccCommand(rusageEnabled, mainBuilder) if err != nil { return 0, err } + workAroundKernelBugWithRetries = true } } - rusageLogfileName := getRusageLogFilename(env) + bisectStage := getBisectStage(env) - if shouldForceDisableWerror(env, cfg) { - if rusageLogfileName != "" { - return 0, newUserErrorf("GETRUSAGE is meaningless with FORCE_DISABLE_WERROR") - } + + 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 rusageLogfileName != "" { - return 0, newUserErrorf("GETRUSAGE is meaningless with FORCE_DISABLE_WERROR") + 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 FORCE_DISABLE_WERROR") + return 0, newUserErrorf("BISECT_STAGE is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH") } return compileWithFallback(env, cfg, compilerCmd, mainBuilder.absWrapperPath) } - if rusageLogfileName != "" { - if bisectStage != "" { - return 0, newUserErrorf("BISECT_STAGE is meaningless with GETRUSAGE") - } - return logRusage(env, rusageLogfileName, compilerCmd) - } 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 } } - // Note: We return an exit code only if the underlying env is not - // really doing an exec, e.g. commandRecordingEnv. - return wrapSubprocessErrorWithSourceLoc(compilerCmd, env.exec(compilerCmd)) + + 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) { @@ -191,38 +320,40 @@ func prepareClangCommand(builder *commandBuilder) (err error) { return processClangFlags(builder) } -func calcClangCommand(allowCCache bool, builder *commandBuilder) (*command, error) { +func calcClangCommand(allowCCache bool, builder *commandBuilder) (bool, *command, error) { err := prepareClangCommand(builder) if err != nil { - return nil, err + return false, nil, err } - if err := processGomaCCacheFlags(allowCCache, builder); err != nil { - return nil, err + remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(allowCCache, builder) + if err != nil { + return remoteBuildUsed, nil, err } - return builder.build(), nil + return remoteBuildUsed, builder.build(), nil } -func calcGccCommand(builder *commandBuilder) (*command, error) { +func calcGccCommand(enableRusage bool, builder *commandBuilder) (bool, *command, error) { if !builder.cfg.isHostWrapper { processSysrootFlag(builder) } builder.addPreUserArgs(builder.cfg.gccFlags...) - if !builder.cfg.isHostWrapper { - calcCommonPreUserArgs(builder) - } + calcCommonPreUserArgs(builder) processGccFlags(builder) + + remoteBuildUsed := false if !builder.cfg.isHostWrapper { - allowCCache := true - if err := processGomaCCacheFlags(allowCCache, builder); err != nil { - return nil, err + var err error + if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(!enableRusage, builder); err != nil { + return remoteBuildUsed, nil, err } } - return builder.build(), nil + 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) @@ -231,18 +362,18 @@ func calcCommonPreUserArgs(builder *commandBuilder) { processSanitizerFlags(builder) } -func processGomaCCacheFlags(allowCCache bool, builder *commandBuilder) (err error) { - gomaccUsed := false +func processRemoteBuildAndCCacheFlags(allowCCache bool, builder *commandBuilder) (remoteBuildUsed bool, err error) { + remoteBuildUsed = false if !builder.cfg.isHostWrapper { - gomaccUsed, err = processGomaCccFlags(builder) + remoteBuildUsed, err = processRemoteBuildFlags(builder) if err != nil { - return err + return remoteBuildUsed, err } } - if !gomaccUsed && allowCCache { + if !remoteBuildUsed && allowCCache { processCCacheFlag(builder) } - return nil + return remoteBuildUsed, nil } func getAbsWrapperPath(env env, wrapperCmd *command) (string, error) { |