Introduce infrastructure for calling and testing nested
commands, error messages and exit codes.
Also:
- implements the -Xclang-path= flag as use case of calling
a nested command.
- adds tests for forwarding errors, comparing against the
old wrapper, and exit codes.
- captures the source locations of errors in error messages.
- compares exit codes of new wrapper and old wrapper.
BUG=chromium:773875
TEST=unit test
Change-Id: I919e58091d093d68939809f676f799a68ec7a34e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/1676833
Reviewed-by: George Burgess <gbiv@chromium.org>
Tested-by: Tobias Bosch <tbosch@google.com>
diff --git a/compiler_wrapper/compiler_wrapper.go b/compiler_wrapper/compiler_wrapper.go
index 6034fb0..4ef3039 100644
--- a/compiler_wrapper/compiler_wrapper.go
+++ b/compiler_wrapper/compiler_wrapper.go
@@ -1,21 +1,84 @@
package main
import (
- "log"
+ "fmt"
+ "io"
"path/filepath"
"strings"
+ "syscall"
)
-func calcCompilerCommand(env env, cfg *config, wrapperCmd *command) (*command, error) {
- absWrapperDir, err := getAbsWrapperDir(env, wrapperCmd.path)
+func callCompiler(env env, cfg *config, inputCmd *command) int {
+ exitCode := 0
+ var compilerErr error
+ if shouldForwardToOldWrapper(env, inputCmd) {
+ // TODO: Once this is only checking for bisect, create a command
+ // that directly calls the bisect driver in calcCompilerCommand.
+ exitCode, compilerErr = forwardToOldWrapper(env, cfg, inputCmd)
+ } else if cfg.oldWrapperPath != "" {
+ exitCode, compilerErr = callCompilerWithRunAndCompareToOldWrapper(env, cfg, inputCmd)
+ } else {
+ compilerErr = callCompilerWithExec(env, cfg, inputCmd)
+ }
+ if compilerErr != nil {
+ printCompilerError(env.stderr(), compilerErr)
+ exitCode = 1
+ }
+ return exitCode
+}
+
+func callCompilerWithRunAndCompareToOldWrapper(env env, cfg *config, inputCmd *command) (exitCode int, err error) {
+ recordingEnv := &commandRecordingEnv{
+ env: env,
+ }
+ compilerCmd, err := calcCompilerCommand(recordingEnv, cfg, inputCmd)
+ if err != nil {
+ return exitCode, err
+ }
+ exitCode = 0
+ // Note: we are not using env.exec here so that we can compare the exit code
+ // against the old wrapper too.
+ if err := recordingEnv.run(compilerCmd, env.stdout(), env.stderr()); err != nil {
+ if userErr, ok := getCCacheError(compilerCmd, err); ok {
+ return exitCode, userErr
+ }
+ var ok bool
+ if exitCode, ok = getExitCode(err); !ok {
+ return exitCode, wrapErrorwithSourceLocf(err, "failed to execute %s %s", compilerCmd.path, compilerCmd.args)
+ }
+ }
+ if err := compareToOldWrapper(env, cfg, inputCmd, recordingEnv.cmdResults); err != nil {
+ return exitCode, err
+ }
+ return exitCode, nil
+}
+
+func callCompilerWithExec(env env, cfg *config, inputCmd *command) error {
+ compilerCmd, err := calcCompilerCommand(env, cfg, inputCmd)
+ if err != nil {
+ return err
+ }
+ if err := env.exec(compilerCmd); err != nil {
+ // Note: No need to check for exit code error as exec will
+ // stop this control flow once the command started executing.
+ if userErr, ok := getCCacheError(compilerCmd, err); ok {
+ return userErr
+ }
+ return wrapErrorwithSourceLocf(err, "failed to execute %s %s", compilerCmd.path, compilerCmd.args)
+ }
+ return nil
+}
+
+func calcCompilerCommand(env env, cfg *config, inputCmd *command) (*command, error) {
+ if err := checkUnsupportedFlags(inputCmd); err != nil {
+ return nil, err
+ }
+ absWrapperDir, err := getAbsWrapperDir(env, inputCmd.path)
if err != nil {
return nil, err
}
rootPath := filepath.Join(absWrapperDir, cfg.rootRelPath)
- if err := checkUnsupportedFlags(wrapperCmd); err != nil {
- return nil, err
- }
- builder, err := newCommandBuilder(env, cfg, wrapperCmd)
+ builder, err := newCommandBuilder(env, cfg, inputCmd)
if err != nil {
return nil, err
}
@@ -47,56 +110,34 @@
return builder.build(), nil
}
-func calcCompilerCommandAndCompareToOld(env env, cfg *config, wrapperCmd *command) (*command, error) {
- compilerCmd, err := calcCompilerCommand(env, cfg, wrapperCmd)
- if err != nil {
- return nil, err
- }
- if cfg.oldWrapperPath == "" {
- return compilerCmd, nil
- }
- oldCmds, err := calcOldCompilerCommands(env, cfg, wrapperCmd)
- if err != nil {
- return nil, err
- }
- if err := compilerCmd.verifySimilarTo(oldCmds[0]); err != nil {
- return nil, err
- }
- return compilerCmd, nil
-}
-
func getAbsWrapperDir(env env, wrapperPath string) (string, error) {
if !filepath.IsAbs(wrapperPath) {
wrapperPath = filepath.Join(env.getwd(), wrapperPath)
}
evaledCmdPath, err := filepath.EvalSymlinks(wrapperPath)
if err != nil {
- log.Printf("Unable to EvalSymlinks for %s. Error: %s", evaledCmdPath, err)
- return "", err
+ return "", wrapErrorwithSourceLocf(err, "failed to evaluate symlinks for %s", wrapperPath)
}
return filepath.Dir(evaledCmdPath), nil
}
-// Whether the command should be executed by the old wrapper as we don't
-// support it yet.
-func shouldForwardToOldWrapper(env env, wrapperCmd *command) bool {
- for _, arg := range wrapperCmd.args {
- switch {
- case strings.HasPrefix(arg, "-Xclang-path="):
- fallthrough
- case arg == "-clang-syntax":
- return true
- }
+func getCCacheError(compilerCmd *command, compilerCmdErr error) (ccacheErr userError, ok bool) {
+ if en, ok := compilerCmdErr.(syscall.Errno); ok && en == syscall.ENOENT &&
+ strings.Contains(compilerCmd.path, "ccache") {
+ ccacheErr =
+ newUserErrorf("ccache not found under %s. Please install it",
+ compilerCmd.path)
+ return ccacheErr, ok
}
- switch {
- case env.getenv("WITH_TIDY") != "":
- fallthrough
- case env.getenv("FORCE_DISABLE_WERROR") != "":
- fallthrough
- case env.getenv("GETRUSAGE") != "":
- fallthrough
- case env.getenv("BISECT_STAGE") != "":
- return true
+ return ccacheErr, false
+}
+
+func printCompilerError(writer io.Writer, compilerErr error) {
+ if _, ok := compilerErr.(userError); ok {
+ fmt.Fprintf(writer, "%s\n", compilerErr)
+ } else {
+ fmt.Fprintf(writer,
+ "Internal error. Please report to chromeos-toolchain@google.com.\n%s\n",
+ compilerErr)
}
- return false
}