diff options
Diffstat (limited to 'compiler_wrapper/disable_werror_flag.go')
-rw-r--r-- | compiler_wrapper/disable_werror_flag.go | 209 |
1 files changed, 177 insertions, 32 deletions
diff --git a/compiler_wrapper/disable_werror_flag.go b/compiler_wrapper/disable_werror_flag.go index 864397dd..8f20b6f3 100644 --- a/compiler_wrapper/disable_werror_flag.go +++ b/compiler_wrapper/disable_werror_flag.go @@ -7,17 +7,55 @@ package main import ( "bytes" "encoding/json" + "fmt" + "io" "io/ioutil" "os" + "path" + "strconv" "strings" "syscall" ) -func shouldForceDisableWError(env env) bool { +const numWErrorEstimate = 30 + +func shouldForceDisableWerror(env env, cfg *config) bool { + if cfg.isAndroidWrapper { + return cfg.useLlvmNext + } value, _ := env.getenv("FORCE_DISABLE_WERROR") return value != "" } +func disableWerrorFlags(originalArgs []string) []string { + extraArgs := []string{"-Wno-error"} + newArgs := make([]string, 0, len(originalArgs)+numWErrorEstimate) + for _, flag := range originalArgs { + if strings.HasPrefix(flag, "-Werror=") { + extraArgs = append(extraArgs, strings.Replace(flag, "-Werror", "-Wno-error", 1)) + } + if !strings.Contains(flag, "-warnings-as-errors") { + newArgs = append(newArgs, flag) + } + } + return append(newArgs, extraArgs...) +} + +func isLikelyAConfTest(cfg *config, cmd *command) bool { + // Android doesn't do mid-build `configure`s, so we don't need to worry about this there. + if cfg.isAndroidWrapper { + return false + } + + for _, a := range cmd.Args { + // The kernel, for example, will do configure tests with /dev/null as a source file. + if a == "/dev/null" || strings.HasPrefix(a, "conftest.c") { + return true + } + } + return false +} + func doubleBuildWithWNoError(env env, cfg *config, originalCmd *command) (exitCode int, err error) { originalStdoutBuffer := &bytes.Buffer{} originalStderrBuffer := &bytes.Buffer{} @@ -26,15 +64,27 @@ func doubleBuildWithWNoError(env env, cfg *config, originalCmd *command) (exitCo if originalCmd.Path == "/usr/bin/ccache" { originalCmd.Path = "ccache" } - originalStdinBuffer := &bytes.Buffer{} + + getStdin, err := prebufferStdinIfNeeded(env, originalCmd) + if err != nil { + return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin: %v", err) + } + originalExitCode, err := wrapSubprocessErrorWithSourceLoc(originalCmd, - env.run(originalCmd, teeStdinIfNeeded(env, originalCmd, originalStdinBuffer), originalStdoutBuffer, originalStderrBuffer)) + env.run(originalCmd, getStdin(), originalStdoutBuffer, originalStderrBuffer)) if err != nil { return 0, err } + // The only way we can do anything useful is if it looks like the failure // was -Werror-related. - if originalExitCode == 0 || !strings.Contains(originalStderrBuffer.String(), "-Werror") { + originalStdoutBufferBytes := originalStdoutBuffer.Bytes() + shouldRetry := originalExitCode != 0 && + !isLikelyAConfTest(cfg, originalCmd) && + (bytes.Contains(originalStderrBuffer.Bytes(), []byte("-Werror")) || + bytes.Contains(originalStdoutBufferBytes, []byte("warnings-as-errors")) || + bytes.Contains(originalStdoutBufferBytes, []byte("clang-diagnostic-"))) + if !shouldRetry { originalStdoutBuffer.WriteTo(env.stdout()) originalStderrBuffer.WriteTo(env.stderr()) return originalExitCode, nil @@ -44,23 +94,55 @@ func doubleBuildWithWNoError(env env, cfg *config, originalCmd *command) (exitCo retryStderrBuffer := &bytes.Buffer{} retryCommand := &command{ Path: originalCmd.Path, - Args: append(originalCmd.Args, "-Wno-error"), + Args: disableWerrorFlags(originalCmd.Args), EnvUpdates: originalCmd.EnvUpdates, } retryExitCode, err := wrapSubprocessErrorWithSourceLoc(retryCommand, - env.run(retryCommand, bytes.NewReader(originalStdinBuffer.Bytes()), retryStdoutBuffer, retryStderrBuffer)) + env.run(retryCommand, getStdin(), retryStdoutBuffer, retryStderrBuffer)) if err != nil { return 0, err } - // If -Wno-error fixed us, pretend that we never ran without -Wno-error. - // Otherwise, pretend that we never ran the second invocation. Since -Werror - // is an issue, log in either case. - if retryExitCode == 0 { - retryStdoutBuffer.WriteTo(env.stdout()) - retryStderrBuffer.WriteTo(env.stderr()) - } else { + // If -Wno-error fixed us, pretend that we never ran without -Wno-error. Otherwise, pretend + // that we never ran the second invocation. + if retryExitCode != 0 { originalStdoutBuffer.WriteTo(env.stdout()) originalStderrBuffer.WriteTo(env.stderr()) + return originalExitCode, nil + } + + retryStdoutBuffer.WriteTo(env.stdout()) + retryStderrBuffer.WriteTo(env.stderr()) + + lines := []string{} + if originalStderrBuffer.Len() > 0 { + lines = append(lines, originalStderrBuffer.String()) + } + if originalStdoutBuffer.Len() > 0 { + lines = append(lines, originalStdoutBuffer.String()) + } + outputToLog := strings.Join(lines, "\n") + + // Ignore the error here; we can't do anything about it. The result is always valid (though + // perhaps incomplete) even if this returns an error. + parentProcesses, _ := collectAllParentProcesses() + jsonData := warningsJSONData{ + Cwd: env.getwd(), + Command: append([]string{originalCmd.Path}, originalCmd.Args...), + Stdout: outputToLog, + ParentProcesses: parentProcesses, + } + + // Write warning report to stdout for Android. On Android, + // double-build can be requested on remote builds as well, where there + // is no canonical place to write the warnings report. + if cfg.isAndroidWrapper { + stdout := env.stdout() + io.WriteString(stdout, "<LLVM_NEXT_ERROR_REPORT>") + if err := json.NewEncoder(stdout).Encode(jsonData); err != nil { + return 0, wrapErrorwithSourceLocf(err, "error in json.Marshal") + } + io.WriteString(stdout, "</LLVM_NEXT_ERROR_REPORT>") + return retryExitCode, nil } // All of the below is basically logging. If we fail at any point, it's @@ -94,20 +176,6 @@ func doubleBuildWithWNoError(env env, cfg *config, originalCmd *command) (exitCo return 0, wrapErrorwithSourceLocf(err, "error chmoding the file to be world-readable/writeable") } - lines := []string{} - if originalStderrBuffer.Len() > 0 { - lines = append(lines, originalStderrBuffer.String()) - } - if originalStdoutBuffer.Len() > 0 { - lines = append(lines, originalStdoutBuffer.String()) - } - outputToLog := strings.Join(lines, "\n") - - jsonData := warningsJSONData{ - Cwd: env.getwd(), - Command: append([]string{originalCmd.Path}, originalCmd.Args...), - Stdout: outputToLog, - } enc := json.NewEncoder(tmpFile) if err := enc.Encode(jsonData); err != nil { _ = tmpFile.Close() @@ -125,10 +193,87 @@ func doubleBuildWithWNoError(env env, cfg *config, originalCmd *command) (exitCo return retryExitCode, nil } -// Struct used to write JSON. Fileds have to be uppercase for the json -// encoder to read them. +func parseParentPidFromPidStat(pidStatContents string) (parentPid int, ok bool) { + // The parent's pid is the fourth field of /proc/[pid]/stat. Sadly, the second field can + // have spaces in it. It ends at the last ')' in the contents of /proc/[pid]/stat. + lastParen := strings.LastIndex(pidStatContents, ")") + if lastParen == -1 { + return 0, false + } + + thirdFieldAndBeyond := strings.TrimSpace(pidStatContents[lastParen+1:]) + fields := strings.Fields(thirdFieldAndBeyond) + if len(fields) < 2 { + return 0, false + } + + fourthField := fields[1] + parentPid, err := strconv.Atoi(fourthField) + if err != nil { + return 0, false + } + return parentPid, true +} + +func collectProcessData(pid int) (args, env []string, parentPid int, err error) { + procDir := fmt.Sprintf("/proc/%d", pid) + + readFile := func(fileName string) (string, error) { + s, err := ioutil.ReadFile(path.Join(procDir, fileName)) + if err != nil { + return "", fmt.Errorf("reading %s: %v", fileName, err) + } + return string(s), nil + } + + statStr, err := readFile("stat") + if err != nil { + return nil, nil, 0, err + } + + parentPid, ok := parseParentPidFromPidStat(statStr) + if !ok { + return nil, nil, 0, fmt.Errorf("no parseable parent PID found in %q", statStr) + } + + argsStr, err := readFile("cmdline") + if err != nil { + return nil, nil, 0, err + } + args = strings.Split(argsStr, "\x00") + + envStr, err := readFile("environ") + if err != nil { + return nil, nil, 0, err + } + env = strings.Split(envStr, "\x00") + return args, env, parentPid, nil +} + +// The returned []processData is valid even if this returns an error. The error is just the first we +// encountered when trying to collect parent process data. +func collectAllParentProcesses() ([]processData, error) { + results := []processData{} + for parent := os.Getppid(); parent != 1; { + args, env, p, err := collectProcessData(parent) + if err != nil { + return results, fmt.Errorf("inspecting parent %d: %v", parent, err) + } + results = append(results, processData{Args: args, Env: env}) + parent = p + } + return results, nil +} + +type processData struct { + Args []string `json:"invocation"` + Env []string `json:"env"` +} + +// Struct used to write JSON. Fields have to be uppercase for the json encoder to read them. type warningsJSONData struct { - Cwd string `json:"cwd"` - Command []string `json:"command"` - Stdout string `json:"stdout"` + Cwd string `json:"cwd"` + Command []string `json:"command"` + Stdout string `json:"stdout"` + ParentProcesses []processData `json:"parent_process_data"` } |