aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/run/global_hash.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/run/global_hash.go')
-rw-r--r--cli/internal/run/global_hash.go164
1 files changed, 164 insertions, 0 deletions
diff --git a/cli/internal/run/global_hash.go b/cli/internal/run/global_hash.go
new file mode 100644
index 0000000..2ebf642
--- /dev/null
+++ b/cli/internal/run/global_hash.go
@@ -0,0 +1,164 @@
+package run
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/hashicorp/go-hclog"
+ "github.com/mitchellh/cli"
+ "github.com/vercel/turbo/cli/internal/env"
+ "github.com/vercel/turbo/cli/internal/fs"
+ "github.com/vercel/turbo/cli/internal/globby"
+ "github.com/vercel/turbo/cli/internal/hashing"
+ "github.com/vercel/turbo/cli/internal/lockfile"
+ "github.com/vercel/turbo/cli/internal/packagemanager"
+ "github.com/vercel/turbo/cli/internal/turbopath"
+ "github.com/vercel/turbo/cli/internal/util"
+)
+
+const _globalCacheKey = "Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo"
+
+// Variables that we always include
+var _defaultEnvVars = []string{
+ "VERCEL_ANALYTICS_ID",
+}
+
+// GlobalHashable represents all the things that we use to create the global hash
+type GlobalHashable struct {
+ globalFileHashMap map[turbopath.AnchoredUnixPath]string
+ rootExternalDepsHash string
+ envVars env.DetailedMap
+ globalCacheKey string
+ pipeline fs.PristinePipeline
+ envVarPassthroughs []string
+ envMode util.EnvMode
+}
+
+// This exists because the global hash used to have different fields. Changing
+// to a new struct layout changes the global hash. We can remove this converter
+// when we are going to have to update the global hash for something else.
+type oldGlobalHashable struct {
+ globalFileHashMap map[turbopath.AnchoredUnixPath]string
+ rootExternalDepsHash string
+ envVars env.EnvironmentVariablePairs
+ globalCacheKey string
+ pipeline fs.PristinePipeline
+}
+
+// calculateGlobalHashFromHashable returns a hash string from the globalHashable
+func calculateGlobalHashFromHashable(full GlobalHashable) (string, error) {
+ switch full.envMode {
+ case util.Infer:
+ if full.envVarPassthroughs != nil {
+ // In infer mode, if there is any passThru config (even if it is an empty array)
+ // we'll hash the whole object, so we can detect changes to that config
+ // Further, resolve the envMode to the concrete value.
+ full.envMode = util.Strict
+ return fs.HashObject(full)
+ }
+
+ // If we're in infer mode, and there is no global pass through config,
+ // we use the old struct layout. this will be true for everyone not using the strict env
+ // feature, and we don't want to break their cache.
+ return fs.HashObject(oldGlobalHashable{
+ globalFileHashMap: full.globalFileHashMap,
+ rootExternalDepsHash: full.rootExternalDepsHash,
+ envVars: full.envVars.All.ToHashable(),
+ globalCacheKey: full.globalCacheKey,
+ pipeline: full.pipeline,
+ })
+ case util.Loose:
+ // Remove the passthroughs from hash consideration if we're explicitly loose.
+ full.envVarPassthroughs = nil
+ return fs.HashObject(full)
+ case util.Strict:
+ // Collapse `nil` and `[]` in strict mode.
+ if full.envVarPassthroughs == nil {
+ full.envVarPassthroughs = make([]string, 0)
+ }
+ return fs.HashObject(full)
+ default:
+ panic("unimplemented environment mode")
+ }
+}
+
+func calculateGlobalHash(
+ rootpath turbopath.AbsoluteSystemPath,
+ rootPackageJSON *fs.PackageJSON,
+ pipeline fs.Pipeline,
+ envVarDependencies []string,
+ globalFileDependencies []string,
+ packageManager *packagemanager.PackageManager,
+ lockFile lockfile.Lockfile,
+ envVarPassthroughs []string,
+ envMode util.EnvMode,
+ logger hclog.Logger,
+ ui cli.Ui,
+ isStructuredOutput bool,
+) (GlobalHashable, error) {
+ // Calculate env var dependencies
+ envVars := []string{}
+ envVars = append(envVars, envVarDependencies...)
+ envVars = append(envVars, _defaultEnvVars...)
+ globalHashableEnvVars, err := env.GetHashableEnvVars(envVars, []string{".*THASH.*"}, "")
+ if err != nil {
+ return GlobalHashable{}, err
+ }
+
+ // The only way we can add env vars into the hash via matching is via THASH,
+ // so we only do a simple check here for entries in `BySource.Matching`.
+ // If we enable globalEnv to accept wildcard characters, we'll need to update this
+ // check.
+ if !isStructuredOutput && len(globalHashableEnvVars.BySource.Matching) > 0 {
+ ui.Warn(fmt.Sprintf("[DEPRECATED] Using .*THASH.* to specify an environment variable for inclusion into the hash is deprecated. You specified: %s.", strings.Join(globalHashableEnvVars.BySource.Matching.Names(), ", ")))
+ }
+
+ logger.Debug("global hash env vars", "vars", globalHashableEnvVars.All.Names())
+
+ // Calculate global file dependencies
+ globalDeps := make(util.Set)
+ if len(globalFileDependencies) > 0 {
+ ignores, err := packageManager.GetWorkspaceIgnores(rootpath)
+ if err != nil {
+ return GlobalHashable{}, err
+ }
+
+ f, err := globby.GlobFiles(rootpath.ToStringDuringMigration(), globalFileDependencies, ignores)
+ if err != nil {
+ return GlobalHashable{}, err
+ }
+
+ for _, val := range f {
+ globalDeps.Add(val)
+ }
+ }
+
+ if lockFile == nil {
+ // If we don't have lockfile information available, add the specfile and lockfile to global deps
+ globalDeps.Add(filepath.Join(rootpath.ToStringDuringMigration(), packageManager.Specfile))
+ globalDeps.Add(filepath.Join(rootpath.ToStringDuringMigration(), packageManager.Lockfile))
+ }
+
+ // No prefix, global deps already have full paths
+ globalDepsArray := globalDeps.UnsafeListOfStrings()
+ globalDepsPaths := make([]turbopath.AbsoluteSystemPath, len(globalDepsArray))
+ for i, path := range globalDepsArray {
+ globalDepsPaths[i] = turbopath.AbsoluteSystemPathFromUpstream(path)
+ }
+
+ globalFileHashMap, err := hashing.GetHashableDeps(rootpath, globalDepsPaths)
+ if err != nil {
+ return GlobalHashable{}, fmt.Errorf("error hashing files: %w", err)
+ }
+
+ return GlobalHashable{
+ globalFileHashMap: globalFileHashMap,
+ rootExternalDepsHash: rootPackageJSON.ExternalDepsHash,
+ envVars: globalHashableEnvVars,
+ globalCacheKey: _globalCacheKey,
+ pipeline: pipeline.Pristine(),
+ envVarPassthroughs: envVarPassthroughs,
+ envMode: envMode,
+ }, nil
+}