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 19const ( 20 clangCrashArtifactsSubdir = "toolchain/clang_crash_diagnostics" 21 crosArtifactsEnvVar = "CROS_ARTIFACTS_TMP_DIR" 22) 23 24func callCompiler(env env, cfg *config, inputCmd *command) int { 25 var compilerErr error 26 27 if !filepath.IsAbs(inputCmd.Path) && !strings.HasPrefix(inputCmd.Path, ".") && 28 !strings.ContainsRune(inputCmd.Path, filepath.Separator) { 29 if resolvedPath, err := resolveAgainstPathEnv(env, inputCmd.Path); err == nil { 30 inputCmd = &command{ 31 Path: resolvedPath, 32 Args: inputCmd.Args, 33 EnvUpdates: inputCmd.EnvUpdates, 34 } 35 } else { 36 compilerErr = err 37 } 38 } 39 exitCode := 0 40 if compilerErr == nil { 41 exitCode, compilerErr = callCompilerInternal(env, cfg, inputCmd) 42 } 43 if compilerErr != nil { 44 printCompilerError(env.stderr(), compilerErr) 45 exitCode = 1 46 } 47 return exitCode 48} 49 50// Given the main builder path and the absolute path to our wrapper, returns the path to the 51// 'real' compiler we should invoke. 52func calculateAndroidWrapperPath(mainBuilderPath string, absWrapperPath string) string { 53 // FIXME: This combination of using the directory of the symlink but the basename of the 54 // link target is strange but is the logic that old android wrapper uses. Change this to use 55 // directory and basename either from the absWrapperPath or from the builder.path, but don't 56 // mix anymore. 57 58 // We need to be careful here: path.Join Clean()s its result, so `./foo` will get 59 // transformed to `foo`, which isn't good since we're passing this path to exec. 60 basePart := filepath.Base(absWrapperPath) + ".real" 61 if !strings.ContainsRune(mainBuilderPath, filepath.Separator) { 62 return basePart 63 } 64 65 dirPart := filepath.Dir(mainBuilderPath) 66 if cleanResult := filepath.Join(dirPart, basePart); strings.ContainsRune(cleanResult, filepath.Separator) { 67 return cleanResult 68 } 69 70 return "." + string(filepath.Separator) + basePart 71} 72 73func runAndroidClangTidy(env env, cmd *command) error { 74 timeout, found := env.getenv("TIDY_TIMEOUT") 75 if !found { 76 return env.exec(cmd) 77 } 78 seconds, err := strconv.Atoi(timeout) 79 if err != nil || seconds == 0 { 80 return env.exec(cmd) 81 } 82 getSourceFile := func() string { 83 // Note: This depends on Android build system's clang-tidy command line format. 84 // Last non-flag before "--" in cmd.Args is used as the source file name. 85 sourceFile := "unknown_file" 86 for _, arg := range cmd.Args { 87 if arg == "--" { 88 break 89 } 90 if strings.HasPrefix(arg, "-") { 91 continue 92 } 93 sourceFile = arg 94 } 95 return sourceFile 96 } 97 startTime := time.Now() 98 err = env.runWithTimeout(cmd, time.Duration(seconds)*time.Second) 99 if !errors.Is(err, context.DeadlineExceeded) { 100 // When used time is over half of TIDY_TIMEOUT, give a warning. 101 // These warnings allow users to fix slow jobs before they get worse. 102 usedSeconds := int(time.Since(startTime) / time.Second) 103 if usedSeconds > seconds/2 { 104 warning := "%s:1:1: warning: clang-tidy used %d seconds.\n" 105 fmt.Fprintf(env.stdout(), warning, getSourceFile(), usedSeconds) 106 } 107 return err 108 } 109 // When DeadllineExceeded, print warning messages. 110 warning := "%s:1:1: warning: clang-tidy aborted after %d seconds.\n" 111 fmt.Fprintf(env.stdout(), warning, getSourceFile(), seconds) 112 fmt.Fprintf(env.stdout(), "TIMEOUT: %s %s\n", cmd.Path, strings.Join(cmd.Args, " ")) 113 // Do not stop Android build. Just give a warning and return no error. 114 return nil 115} 116 117func detectCrashArtifactsDir(env env, cfg *config) string { 118 if cfg.isAndroidWrapper { 119 return "" 120 } 121 122 tmpdir, ok := env.getenv(crosArtifactsEnvVar) 123 if !ok { 124 return "" 125 } 126 return filepath.Join(tmpdir, clangCrashArtifactsSubdir) 127} 128 129func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int, err error) { 130 if err := checkUnsupportedFlags(inputCmd); err != nil { 131 return 0, err 132 } 133 mainBuilder, err := newCommandBuilder(env, cfg, inputCmd) 134 if err != nil { 135 return 0, err 136 } 137 processPrintConfigFlag(mainBuilder) 138 processPrintCmdlineFlag(mainBuilder) 139 env = mainBuilder.env 140 var compilerCmd *command 141 disableWerrorConfig := processForceDisableWerrorFlag(env, cfg, mainBuilder) 142 clangSyntax := processClangSyntaxFlag(mainBuilder) 143 144 rusageEnabled := isRusageEnabled(env) 145 146 // Disable CCache for rusage logs 147 // Note: Disabling Goma causes timeout related INFRA_FAILUREs in builders 148 allowCCache := !rusageEnabled 149 remoteBuildUsed := false 150 151 workAroundKernelBugWithRetries := false 152 if cfg.isAndroidWrapper { 153 mainBuilder.path = calculateAndroidWrapperPath(mainBuilder.path, mainBuilder.absWrapperPath) 154 switch mainBuilder.target.compilerType { 155 case clangType: 156 mainBuilder.addPreUserArgs(mainBuilder.cfg.clangFlags...) 157 mainBuilder.addPreUserArgs(mainBuilder.cfg.commonFlags...) 158 mainBuilder.addPostUserArgs(mainBuilder.cfg.clangPostFlags...) 159 inheritGomaFromEnv := true 160 // Android doesn't support rewrapper; don't try to use it. 161 if remoteBuildUsed, err = processGomaCccFlags(mainBuilder, inheritGomaFromEnv); err != nil { 162 return 0, err 163 } 164 compilerCmd = mainBuilder.build() 165 case clangTidyType: 166 compilerCmd = mainBuilder.build() 167 default: 168 return 0, newErrorwithSourceLocf("unsupported compiler: %s", mainBuilder.target.compiler) 169 } 170 } else { 171 cSrcFile, tidyFlags, tidyMode := processClangTidyFlags(mainBuilder) 172 crashArtifactsDir := detectCrashArtifactsDir(env, cfg) 173 if mainBuilder.target.compilerType == clangType { 174 err := prepareClangCommand(crashArtifactsDir, mainBuilder) 175 if err != nil { 176 return 0, err 177 } 178 if tidyMode != tidyModeNone { 179 allowCCache = false 180 // Remove and ignore goma flags. 181 _, err := removeOneUserCmdlineFlagWithValue(mainBuilder, "--gomacc-path") 182 if err != nil && err != errNoSuchCmdlineArg { 183 return 0, err 184 } 185 186 clangCmdWithoutRemoteBuildAndCCache := mainBuilder.build() 187 188 switch tidyMode { 189 case tidyModeTricium: 190 err = runClangTidyForTricium(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags, crashArtifactsDir) 191 case tidyModeAll: 192 err = runClangTidy(env, clangCmdWithoutRemoteBuildAndCCache, cSrcFile, tidyFlags) 193 default: 194 panic(fmt.Sprintf("Unknown tidy mode: %v", tidyMode)) 195 } 196 197 if err != nil { 198 return 0, err 199 } 200 } 201 202 if remoteBuildUsed, err = processRemoteBuildAndCCacheFlags(allowCCache, mainBuilder); err != nil { 203 return 0, err 204 } 205 compilerCmd = mainBuilder.build() 206 } else { 207 if clangSyntax { 208 allowCCache = false 209 _, clangCmd, err := calcClangCommand(crashArtifactsDir, allowCCache, mainBuilder.clone()) 210 if err != nil { 211 return 0, err 212 } 213 _, gccCmd, err := calcGccCommand(rusageEnabled, mainBuilder) 214 if err != nil { 215 return 0, err 216 } 217 return checkClangSyntax(env, clangCmd, gccCmd) 218 } 219 remoteBuildUsed, compilerCmd, err = calcGccCommand(rusageEnabled, mainBuilder) 220 if err != nil { 221 return 0, err 222 } 223 workAroundKernelBugWithRetries = true 224 } 225 } 226 227 // If builds matching some heuristic should crash, crash them. Since this is purely a 228 // debugging tool, don't offer any nice features with it (e.g., rusage, ...). 229 if shouldUseCrashBuildsHeuristic && mainBuilder.target.compilerType == clangType { 230 return buildWithAutocrash(env, cfg, compilerCmd) 231 } 232 233 bisectStage := getBisectStage(env) 234 235 if rusageEnabled { 236 compilerCmd = removeRusageFromCommand(compilerCmd) 237 } 238 239 if disableWerrorConfig.enabled { 240 if bisectStage != "" { 241 return 0, newUserErrorf("BISECT_STAGE is meaningless with FORCE_DISABLE_WERROR") 242 } 243 return doubleBuildWithWNoError(env, cfg, compilerCmd, disableWerrorConfig) 244 } 245 if shouldCompileWithFallback(env) { 246 if rusageEnabled { 247 return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH") 248 } 249 if bisectStage != "" { 250 return 0, newUserErrorf("BISECT_STAGE is meaningless with ANDROID_LLVM_PREBUILT_COMPILER_PATH") 251 } 252 return compileWithFallback(env, cfg, compilerCmd, mainBuilder.absWrapperPath) 253 } 254 if bisectStage != "" { 255 if rusageEnabled { 256 return 0, newUserErrorf("TOOLCHAIN_RUSAGE_OUTPUT is meaningless with BISECT_STAGE") 257 } 258 compilerCmd, err = calcBisectCommand(env, cfg, bisectStage, compilerCmd) 259 if err != nil { 260 return 0, err 261 } 262 } 263 264 errRetryCompilation := errors.New("compilation retry requested") 265 var runCompiler func(willLogRusage bool) (int, error) 266 if !workAroundKernelBugWithRetries { 267 runCompiler = func(willLogRusage bool) (int, error) { 268 var err error 269 if willLogRusage { 270 err = env.run(compilerCmd, env.stdin(), env.stdout(), env.stderr()) 271 } else if cfg.isAndroidWrapper && mainBuilder.target.compilerType == clangTidyType { 272 // Only clang-tidy has timeout feature now. 273 err = runAndroidClangTidy(env, compilerCmd) 274 } else { 275 // Note: We return from this in non-fatal circumstances only if the 276 // underlying env is not really doing an exec, e.g. commandRecordingEnv. 277 err = env.exec(compilerCmd) 278 } 279 return wrapSubprocessErrorWithSourceLoc(compilerCmd, err) 280 } 281 } else { 282 getStdin, err := prebufferStdinIfNeeded(env, compilerCmd) 283 if err != nil { 284 return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin: %v", err) 285 } 286 287 stdoutBuffer := &bytes.Buffer{} 288 stderrBuffer := &bytes.Buffer{} 289 retryAttempt := 0 290 runCompiler = func(willLogRusage bool) (int, error) { 291 retryAttempt++ 292 stdoutBuffer.Reset() 293 stderrBuffer.Reset() 294 295 exitCode, compilerErr := wrapSubprocessErrorWithSourceLoc(compilerCmd, 296 env.run(compilerCmd, getStdin(), stdoutBuffer, stderrBuffer)) 297 298 if compilerErr != nil || exitCode != 0 { 299 if retryAttempt < kernelBugRetryLimit && (errorContainsTracesOfKernelBug(compilerErr) || containsTracesOfKernelBug(stdoutBuffer.Bytes()) || containsTracesOfKernelBug(stderrBuffer.Bytes())) { 300 return exitCode, errRetryCompilation 301 } 302 } 303 _, stdoutErr := stdoutBuffer.WriteTo(env.stdout()) 304 _, stderrErr := stderrBuffer.WriteTo(env.stderr()) 305 if stdoutErr != nil { 306 return exitCode, wrapErrorwithSourceLocf(err, "writing stdout: %v", stdoutErr) 307 } 308 if stderrErr != nil { 309 return exitCode, wrapErrorwithSourceLocf(err, "writing stderr: %v", stderrErr) 310 } 311 return exitCode, compilerErr 312 } 313 } 314 315 for { 316 var exitCode int 317 commitRusage, err := maybeCaptureRusage(env, compilerCmd, func(willLogRusage bool) error { 318 var err error 319 exitCode, err = runCompiler(willLogRusage) 320 return err 321 }) 322 323 switch { 324 case err == errRetryCompilation: 325 // Loop around again. 326 case err != nil: 327 return exitCode, err 328 default: 329 if !remoteBuildUsed { 330 if err := commitRusage(exitCode); err != nil { 331 return exitCode, fmt.Errorf("commiting rusage: %v", err) 332 } 333 } 334 return exitCode, err 335 } 336 } 337} 338 339func hasUserArg(argName string, builder *commandBuilder) bool { 340 for _, argValue := range builder.args { 341 if strings.Contains(argValue.value, argName) && argValue.fromUser { 342 return true 343 } 344 } 345 return false 346} 347 348func prepareClangCommand(crashArtifactsDir string, builder *commandBuilder) (err error) { 349 if !builder.cfg.isHostWrapper { 350 processSysrootFlag(builder) 351 } 352 builder.addPreUserArgs(builder.cfg.clangFlags...) 353 354 var crashDiagFlagName = "-fcrash-diagnostics-dir" 355 if crashArtifactsDir != "" && 356 !hasUserArg(crashDiagFlagName, builder) { 357 builder.addPreUserArgs(crashDiagFlagName + "=" + crashArtifactsDir) 358 } 359 360 builder.addPostUserArgs(builder.cfg.clangPostFlags...) 361 calcCommonPreUserArgs(builder) 362 return processClangFlags(builder) 363} 364 365func calcClangCommand(crashArtifactsDir string, allowCCache bool, builder *commandBuilder) (bool, *command, error) { 366 err := prepareClangCommand(crashArtifactsDir, builder) 367 if err != nil { 368 return false, nil, err 369 } 370 remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(allowCCache, builder) 371 if err != nil { 372 return remoteBuildUsed, nil, err 373 } 374 return remoteBuildUsed, builder.build(), nil 375} 376 377func calcGccCommand(enableRusage bool, builder *commandBuilder) (bool, *command, error) { 378 if !builder.cfg.isHostWrapper { 379 processSysrootFlag(builder) 380 } 381 builder.addPreUserArgs(builder.cfg.gccFlags...) 382 calcCommonPreUserArgs(builder) 383 processGccFlags(builder) 384 385 remoteBuildUsed, err := processRemoteBuildAndCCacheFlags(!enableRusage, builder) 386 if err != nil { 387 return remoteBuildUsed, nil, err 388 } 389 return remoteBuildUsed, builder.build(), nil 390} 391 392func calcCommonPreUserArgs(builder *commandBuilder) { 393 builder.addPreUserArgs(builder.cfg.commonFlags...) 394 if !builder.cfg.isHostWrapper { 395 processLibGCCFlags(builder) 396 processThumbCodeFlags(builder) 397 processStackProtectorFlags(builder) 398 processX86Flags(builder) 399 } 400 processSanitizerFlags(builder) 401} 402 403func processRemoteBuildAndCCacheFlags(allowCCache bool, builder *commandBuilder) (remoteBuildUsed bool, err error) { 404 remoteBuildUsed, err = processRemoteBuildFlags(builder) 405 if err != nil { 406 return remoteBuildUsed, err 407 } 408 if !remoteBuildUsed && allowCCache { 409 processCCacheFlag(builder) 410 } 411 return remoteBuildUsed, nil 412} 413 414func getAbsWrapperPath(env env, wrapperCmd *command) (string, error) { 415 wrapperPath := getAbsCmdPath(env, wrapperCmd) 416 evaledCmdPath, err := filepath.EvalSymlinks(wrapperPath) 417 if err != nil { 418 return "", wrapErrorwithSourceLocf(err, "failed to evaluate symlinks for %s", wrapperPath) 419 } 420 return evaledCmdPath, nil 421} 422 423func printCompilerError(writer io.Writer, compilerErr error) { 424 if _, ok := compilerErr.(userError); ok { 425 fmt.Fprintf(writer, "%s\n", compilerErr) 426 } else { 427 emailAccount := "chromeos-toolchain" 428 if isAndroidConfig() { 429 emailAccount = "android-llvm" 430 } 431 fmt.Fprintf(writer, 432 "Internal error. Please report to %[email protected].\n%s\n", 433 emailAccount, compilerErr) 434 } 435} 436 437func needStdinTee(inputCmd *command) bool { 438 lastArg := "" 439 for _, arg := range inputCmd.Args { 440 if arg == "-" && lastArg != "-o" { 441 return true 442 } 443 lastArg = arg 444 } 445 return false 446} 447 448func prebufferStdinIfNeeded(env env, inputCmd *command) (getStdin func() io.Reader, err error) { 449 // We pre-buffer the entirety of stdin, since the compiler may exit mid-invocation with an 450 // error, which may leave stdin partially read. 451 if !needStdinTee(inputCmd) { 452 // This won't produce deterministic input to the compiler, but stdin shouldn't 453 // matter in this case, so... 454 return env.stdin, nil 455 } 456 457 stdinBuffer := &bytes.Buffer{} 458 if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil { 459 return nil, wrapErrorwithSourceLocf(err, "prebuffering stdin") 460 } 461 462 return func() io.Reader { return bytes.NewReader(stdinBuffer.Bytes()) }, nil 463} 464