429 lines
13 KiB
Go
429 lines
13 KiB
Go
// 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
|
|
}
|