diff options
author | Chih-Hung Hsieh <chh@google.com> | 2021-09-15 10:58:06 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-09-15 21:14:55 +0000 |
commit | 8c582e59426fe05fa275ed44bf7d73ab4070b146 (patch) | |
tree | dc62927598d3e9d9b35ca8982c1a37c9488469ac | |
parent | 297b3d6fcec5871fc431408e6e6f0edc28a3306a (diff) | |
download | toolchain-utils-8c582e59426fe05fa275ed44bf7d73ab4070b146.tar.gz |
compiler_wrapper: Handle TIDY_TIMEOUT for Android clang-tidy
* To check in upstream chromeos tools.
* In Android mode, for clang-tidy, call
runAndroidClangTidy to handle TIDY_TIMEOUT
* Add runWithTimeout to env.go and runCmdWithTimeouit to command.go.
BUG=b:199451930
TEST=Use a fake Android clang-tidy.real binary,
TEST=to dump passed environment variables and run for required time.
Change-Id: I629ae372251c034595011ef70559f8b12ed8568c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3162668
Reviewed-by: Chih-Hung Hsieh <chh@google.com>
Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
Tested-by: George Burgess <gbiv@chromium.org>
Commit-Queue: George Burgess <gbiv@chromium.org>
-rw-r--r-- | compiler_wrapper/command.go | 22 | ||||
-rw-r--r-- | compiler_wrapper/compiler_wrapper.go | 39 | ||||
-rw-r--r-- | compiler_wrapper/env.go | 15 | ||||
-rw-r--r-- | compiler_wrapper/env_test.go | 46 | ||||
-rw-r--r-- | compiler_wrapper/testutil_test.go | 5 |
5 files changed, 127 insertions, 0 deletions
diff --git a/compiler_wrapper/command.go b/compiler_wrapper/command.go index 95fce7e7..eb040b25 100644 --- a/compiler_wrapper/command.go +++ b/compiler_wrapper/command.go @@ -5,12 +5,14 @@ package main import ( + "context" "fmt" "io" "os" "os/exec" "path/filepath" "strings" + "time" ) type command struct { @@ -63,6 +65,26 @@ func runCmd(env env, cmd *command, stdin io.Reader, stdout io.Writer, stderr io. return execCmd.Run() } +func runCmdWithTimeout(env env, cmd *command, t time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), t) + defer cancel() + cmdCtx := exec.CommandContext(ctx, cmd.Path, cmd.Args...) + cmdCtx.Env = mergeEnvValues(env.environ(), cmd.EnvUpdates) + cmdCtx.Dir = env.getwd() + cmdCtx.Stdin = env.stdin() + cmdCtx.Stdout = env.stdout() + cmdCtx.Stderr = env.stderr() + + if err := cmdCtx.Start(); err != nil { + return newErrorwithSourceLocf("exec error: %v", err) + } + err := cmdCtx.Wait() + if ctx.Err() == nil { + return err + } + return ctx.Err() +} + func resolveAgainstPathEnv(env env, cmd string) (string, error) { path, _ := env.getenv("PATH") for _, path := range strings.Split(path, ":") { diff --git a/compiler_wrapper/compiler_wrapper.go b/compiler_wrapper/compiler_wrapper.go index 6d29ff0e..7d949d34 100644 --- a/compiler_wrapper/compiler_wrapper.go +++ b/compiler_wrapper/compiler_wrapper.go @@ -6,11 +6,14 @@ package main import ( "bytes" + "context" "errors" "fmt" "io" "path/filepath" + "strconv" "strings" + "time" ) func callCompiler(env env, cfg *config, inputCmd *command) int { @@ -62,6 +65,39 @@ func calculateAndroidWrapperPath(mainBuilderPath string, absWrapperPath string) 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) + } + err = env.runWithTimeout(cmd, time.Duration(seconds)*time.Second) + if !errors.Is(err, context.DeadlineExceeded) { + return err + } + // When DeadllineExceeded, print warning messages. + // 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 + } + warning := "%s:1:1: warning: clang-tidy aborted after %d seconds.\n" + fmt.Fprintf(env.stdout(), warning, sourceFile, 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 @@ -192,6 +228,9 @@ func callCompilerInternal(env env, cfg *config, inputCmd *command) (exitCode int 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. diff --git a/compiler_wrapper/env.go b/compiler_wrapper/env.go index 3fc547c6..c8f6ceb3 100644 --- a/compiler_wrapper/env.go +++ b/compiler_wrapper/env.go @@ -11,6 +11,7 @@ import ( "os" "strings" "syscall" + "time" ) type env interface { @@ -22,6 +23,7 @@ type env interface { stdout() io.Writer stderr() io.Writer run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error + runWithTimeout(cmd *command, duration time.Duration) error exec(cmd *command) error } @@ -86,6 +88,10 @@ func (env *processEnv) exec(cmd *command) error { return execCmd(env, cmd) } +func (env *processEnv) runWithTimeout(cmd *command, duration time.Duration) error { + return runCmdWithTimeout(env, cmd, duration) +} + func (env *processEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return runCmd(env, cmd, stdin, stdout, stderr) } @@ -114,6 +120,10 @@ func (env *commandRecordingEnv) exec(cmd *command) error { return env.run(cmd, env.stdin(), env.stdout(), env.stderr()) } +func (env *commandRecordingEnv) runWithTimeout(cmd *command, duration time.Duration) error { + return env.runWithTimeout(cmd, duration) +} + func (env *commandRecordingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { stdoutBuffer := &bytes.Buffer{} stderrBuffer := &bytes.Buffer{} @@ -140,6 +150,11 @@ func (env *printingEnv) exec(cmd *command) error { return env.env.exec(cmd) } +func (env *printingEnv) runWithTimeout(cmd *command, duration time.Duration) error { + printCmd(env, cmd) + return env.env.runWithTimeout(cmd, duration) +} + func (env *printingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { printCmd(env, cmd) return env.env.run(cmd, stdin, stdout, stderr) diff --git a/compiler_wrapper/env_test.go b/compiler_wrapper/env_test.go index e03d60a8..b5bf65a3 100644 --- a/compiler_wrapper/env_test.go +++ b/compiler_wrapper/env_test.go @@ -6,13 +6,17 @@ package main import ( "bytes" + "context" + "errors" "flag" "io/ioutil" "os" "os/exec" + "path" "path/filepath" "strings" "testing" + "time" ) // Attention: The tests in this file execute the test binary again with the `-run` flag. @@ -164,6 +168,48 @@ func TestProcessEnvRunCmdDeleteEnv(t *testing.T) { }) } +func TestRunWithTimeoutRunsTheGivenProcess(t *testing.T) { + withTestContext(t, func(ctx *testContext) { + env, err := newProcessEnv() + if err != nil { + t.Fatalf("Unexpected error making new process env: %v", err) + } + + tempFile := path.Join(ctx.tempDir, "some_file") + cmd := &command{ + Path: "touch", + Args: []string{tempFile}, + } + if err := env.runWithTimeout(cmd, time.Second*120); err != nil { + t.Fatalf("Unexpected error touch'ing %q: %v", tempFile, err) + } + + // This should be fine, since `touch` should've created the file. + if _, err := os.Stat(tempFile); err != nil { + t.Errorf("Stat'ing temp file at %q failed: %v", tempFile, err) + } + }) +} + +func TestRunWithTimeoutReturnsErrorOnTimeout(t *testing.T) { + withTestContext(t, func(ctx *testContext) { + env, err := newProcessEnv() + if err != nil { + t.Fatalf("Unexpected error making new process env: %v", err) + } + + cmd := &command{ + Path: "sleep", + Args: []string{"30"}, + } + + err = env.runWithTimeout(cmd, 100*time.Millisecond) + if !errors.Is(err, context.DeadlineExceeded) { + t.Errorf("Expected context.DeadlineExceeded after `sleep` timed out; got error: %v", err) + } + }) +} + func TestNewProcessEnvResolvesPwdAwayProperly(t *testing.T) { // This test cannot be t.Parallel(), since it modifies our environment. const envPwd = "PWD" diff --git a/compiler_wrapper/testutil_test.go b/compiler_wrapper/testutil_test.go index 67081207..035f2373 100644 --- a/compiler_wrapper/testutil_test.go +++ b/compiler_wrapper/testutil_test.go @@ -17,6 +17,7 @@ import ( "sync" "syscall" "testing" + "time" ) const ( @@ -160,6 +161,10 @@ func (ctx *testContext) run(cmd *command, stdin io.Reader, stdout io.Writer, std return nil } +func (ctx *testContext) runWithTimeout(cmd *command, duration time.Duration) error { + return ctx.exec(cmd) +} + func (ctx *testContext) exec(cmd *command) error { ctx.cmdCount++ ctx.lastCmd = cmd |