diff --git a/README.md b/README.md index 58d084a..434134e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A fairly simple wrapper application around the clang-tidy executable. It will at ## Configuration -In order to keep the wrapper reasonably clean, the user will have to write a configuration file at the following location: +By default, the wrapper will look for the `clang-tidy` executable on the path. This can be changed by setting the `CLANG_TIDY_CACHE_BINARY` environment variable, or by writing a configuration file at the following location: `~/.ctcache/config.json` diff --git a/caches/cache.go b/caches/cache.go index 74a574b..110aea2 100644 --- a/caches/cache.go +++ b/caches/cache.go @@ -6,6 +6,7 @@ import ( "github.com/ejfitzgerald/clang-tidy-cache/utils" "io" "os" + "os/exec" ) type Cacher interface { @@ -15,15 +16,8 @@ type Cacher interface { SaveEntry(digest []byte, content []byte) error } -func computeDigestForConfigFile(projectRoot string) ([]byte, error) { - configFilePath, err := utils.FindInParents(projectRoot, ".clang-tidy") - if err != nil { - return nil, err - } - - // compute the SHA of the configuration file - // read the contents of the file am hash it - f, err := os.Open(configFilePath) +func computeFileDigest(path string) ([]byte, error) { + f, err := os.Open(path) if err != nil { return nil, err } @@ -40,7 +34,26 @@ func computeDigestForConfigFile(projectRoot string) ([]byte, error) { return digest, nil } -func ComputeFingerPrint(invocation *clang.TidyInvocation, wd string, args []string) ([]byte, error) { +func computeDigestForConfigFile(projectRoot string) ([]byte, error) { + configFilePath, err := utils.FindInParents(projectRoot, ".clang-tidy") + if err != nil { + return nil, err + } + + return computeFileDigest(configFilePath) +} + +func computeDigestForClangTidyBinary(clangTidyPath string) ([]byte, error) { + // resolve to a full path: e.g. `clang-tidy` -> `/usr/local/bin/clang-tidy` + path, err := exec.LookPath(clangTidyPath) + if err != nil { + return nil, err + } + + return computeFileDigest(path) +} + +func ComputeFingerPrint(clangTidyPath string, invocation *clang.TidyInvocation, wd string, args []string) ([]byte, error) { // extract the compilation target command flags from the database targetFlags, err := clang.ExtractCompilationTarget(invocation.DatabaseRoot, invocation.TargetPath) @@ -66,10 +79,17 @@ func ComputeFingerPrint(invocation *clang.TidyInvocation, wd string, args []stri return nil, err } + // we also need to include the clang-tidy binary since different version have different output + binaryDigest, err := computeDigestForClangTidyBinary(clangTidyPath) + if err != nil { + return nil, err + } + // combine all the digests to generate a unique fingerprint hasher := sha256.New() hasher.Write(preProcessedDigest) hasher.Write(configDigest) + hasher.Write(binaryDigest) fingerPrint := hasher.Sum(nil) return fingerPrint, nil diff --git a/main.go b/main.go index 6da12a6..8bf7f0d 100644 --- a/main.go +++ b/main.go @@ -20,19 +20,24 @@ type Configuration struct { GcsConfig *caches.GcsConfiguration `json:"gcs,omitempty"` } -func loadConfiguration() (*Configuration, error) { +func readConfigFile(cfg *Configuration) error { usr, err := user.Current() if err != nil { - return nil, err + return err } // define the configuration path configPath := path.Join(usr.HomeDir, ".ctcache", "config.json") + // missing config file is fine: we simply use the defaults or env vars + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return nil + } + // open the configuration file jsonFile, err := os.Open(configPath) if err != nil { - return nil, err + return err } // defer the closing of our jsonFile so that we can parse it later on @@ -40,13 +45,37 @@ func loadConfiguration() (*Configuration, error) { // read the contents bytes, err := ioutil.ReadAll(jsonFile) + if err != nil { + return err + } - var cfg Configuration - err = json.Unmarshal(bytes, &cfg) + err = json.Unmarshal(bytes, cfg) + if err != nil { + return err + } + + return nil +} + +func readConfigEnv(cfg *Configuration) { + if envPath := os.Getenv("CLANG_TIDY_CACHE_BINARY"); len(envPath) > 0 { + cfg.ClangTidyPath = envPath + } +} + +func loadConfiguration() (*Configuration, error) { + // lowest priority: built-in defaults + cfg := Configuration{ClangTidyPath: "clang-tidy"} + + // higher priority: config file + err := readConfigFile(&cfg) if err != nil { return nil, err } + // highest priority: environment variables + readConfigEnv(&cfg) + return &cfg, nil } @@ -123,7 +152,7 @@ func evaluateTidyCommand(cfg *Configuration, wd string, args []string, cache cac invocation = other // compute the finger print for the file - computedFingerPrint, err := caches.ComputeFingerPrint(invocation, wd, args) + computedFingerPrint, err := caches.ComputeFingerPrint(cfg.ClangTidyPath, invocation, wd, args) if err != nil { return err }