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