aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChih-Hung Hsieh <chh@google.com>2021-09-15 10:58:06 -0700
committerCommit Bot <commit-bot@chromium.org>2021-09-15 21:14:55 +0000
commit8c582e59426fe05fa275ed44bf7d73ab4070b146 (patch)
treedc62927598d3e9d9b35ca8982c1a37c9488469ac
parent297b3d6fcec5871fc431408e6e6f0edc28a3306a (diff)
downloadtoolchain-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.go22
-rw-r--r--compiler_wrapper/compiler_wrapper.go39
-rw-r--r--compiler_wrapper/env.go15
-rw-r--r--compiler_wrapper/env_test.go46
-rw-r--r--compiler_wrapper/testutil_test.go5
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