aboutsummaryrefslogtreecommitdiff
path: root/compiler_wrapper/disable_werror_flag.go
diff options
context:
space:
mode:
Diffstat (limited to 'compiler_wrapper/disable_werror_flag.go')
-rw-r--r--compiler_wrapper/disable_werror_flag.go209
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"`
}