diff options
Diffstat (limited to 'cli/internal/fs/turbo_json.go')
| -rw-r--r-- | cli/internal/fs/turbo_json.go | 741 |
1 files changed, 0 insertions, 741 deletions
diff --git a/cli/internal/fs/turbo_json.go b/cli/internal/fs/turbo_json.go deleted file mode 100644 index 71ef29d..0000000 --- a/cli/internal/fs/turbo_json.go +++ /dev/null @@ -1,741 +0,0 @@ -package fs - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "sort" - "strings" - - "github.com/muhammadmuzzammil1998/jsonc" - "github.com/pkg/errors" - "github.com/vercel/turbo/cli/internal/turbopath" - "github.com/vercel/turbo/cli/internal/util" -) - -const ( - configFile = "turbo.json" - envPipelineDelimiter = "$" - topologicalPipelineDelimiter = "^" -) - -type rawTurboJSON struct { - // Global root filesystem dependencies - GlobalDependencies []string `json:"globalDependencies,omitempty"` - // Global env - GlobalEnv []string `json:"globalEnv,omitempty"` - - // Global passthrough env - GlobalPassthroughEnv []string `json:"experimentalGlobalPassThroughEnv,omitempty"` - - // Pipeline is a map of Turbo pipeline entries which define the task graph - // and cache behavior on a per task or per package-task basis. - Pipeline Pipeline `json:"pipeline"` - // Configuration options when interfacing with the remote cache - RemoteCacheOptions RemoteCacheOptions `json:"remoteCache,omitempty"` - - // Extends can be the name of another workspace - Extends []string `json:"extends,omitempty"` -} - -// pristineTurboJSON is used when marshaling a TurboJSON object into a turbo.json string -// Notably, it includes a PristinePipeline instead of the regular Pipeline. (i.e. TaskDefinition -// instead of BookkeepingTaskDefinition.) -type pristineTurboJSON struct { - GlobalDependencies []string `json:"globalDependencies,omitempty"` - GlobalEnv []string `json:"globalEnv,omitempty"` - GlobalPassthroughEnv []string `json:"experimentalGlobalPassThroughEnv,omitempty"` - Pipeline PristinePipeline `json:"pipeline"` - RemoteCacheOptions RemoteCacheOptions `json:"remoteCache,omitempty"` - Extends []string `json:"extends,omitempty"` -} - -// TurboJSON represents a turbo.json configuration file -type TurboJSON struct { - GlobalDeps []string - GlobalEnv []string - GlobalPassthroughEnv []string - Pipeline Pipeline - RemoteCacheOptions RemoteCacheOptions - - // A list of Workspace names - Extends []string -} - -// RemoteCacheOptions is a struct for deserializing .remoteCache of configFile -type RemoteCacheOptions struct { - TeamID string `json:"teamId,omitempty"` - Signature bool `json:"signature,omitempty"` -} - -// rawTaskWithDefaults exists to Marshal (i.e. turn a TaskDefinition into json). -// We use this for printing ResolvedTaskConfiguration, because we _want_ to show -// the user the default values for key they have not configured. -type rawTaskWithDefaults struct { - Outputs []string `json:"outputs"` - Cache *bool `json:"cache"` - DependsOn []string `json:"dependsOn"` - Inputs []string `json:"inputs"` - OutputMode util.TaskOutputMode `json:"outputMode"` - PassthroughEnv []string `json:"experimentalPassThroughEnv,omitempty"` - Env []string `json:"env"` - Persistent bool `json:"persistent"` -} - -// rawTask exists to Unmarshal from json. When fields are omitted, we _want_ -// them to be missing, so that we can distinguish missing from empty value. -type rawTask struct { - Outputs []string `json:"outputs,omitempty"` - Cache *bool `json:"cache,omitempty"` - DependsOn []string `json:"dependsOn,omitempty"` - Inputs []string `json:"inputs,omitempty"` - OutputMode *util.TaskOutputMode `json:"outputMode,omitempty"` - Env []string `json:"env,omitempty"` - PassthroughEnv []string `json:"experimentalPassthroughEnv,omitempty"` - Persistent *bool `json:"persistent,omitempty"` -} - -// taskDefinitionHashable exists as a definition for PristinePipeline, which is used down -// stream for calculating the global hash. We want to exclude experimental fields here -// because we don't want experimental fields to be part of the global hash. -type taskDefinitionHashable struct { - Outputs TaskOutputs - ShouldCache bool - EnvVarDependencies []string - TopologicalDependencies []string - TaskDependencies []string - Inputs []string - OutputMode util.TaskOutputMode - Persistent bool -} - -// taskDefinitionExperiments is a list of config fields in a task definition that are considered -// experimental. We keep these separated so we can compute a global hash without these. -type taskDefinitionExperiments struct { - PassthroughEnv []string -} - -// PristinePipeline is a map of task names to TaskDefinition or taskDefinitionHashable. -// Depending on whether any experimental fields are defined, we will use either struct. -// The purpose is to omit experimental fields when making a pristine version, so that -// it doesn't show up in --dry/--summarize output or affect the global hash. -type PristinePipeline map[string]interface{} - -// Pipeline is a struct for deserializing .pipeline in configFile -type Pipeline map[string]BookkeepingTaskDefinition - -// BookkeepingTaskDefinition holds the underlying TaskDefinition and some bookkeeping data -// about the TaskDefinition. This wrapper struct allows us to leave TaskDefinition untouched. -type BookkeepingTaskDefinition struct { - definedFields util.Set - experimentalFields util.Set - experimental taskDefinitionExperiments - TaskDefinition taskDefinitionHashable -} - -// TaskDefinition is a representation of the configFile pipeline for further computation. -type TaskDefinition struct { - Outputs TaskOutputs - ShouldCache bool - - // This field is custom-marshalled from rawTask.Env and rawTask.DependsOn - EnvVarDependencies []string - - // rawTask.PassthroughEnv - PassthroughEnv []string - - // TopologicalDependencies are tasks from package dependencies. - // E.g. "build" is a topological dependency in: - // dependsOn: ['^build']. - // This field is custom-marshalled from rawTask.DependsOn - TopologicalDependencies []string - - // TaskDependencies are anything that is not a topological dependency - // E.g. both something and //whatever are TaskDependencies in: - // dependsOn: ['something', '//whatever'] - // This field is custom-marshalled from rawTask.DependsOn - TaskDependencies []string - - // Inputs indicate the list of files this Task depends on. If any of those files change - // we can conclude that any cached outputs or logs for this Task should be invalidated. - Inputs []string - - // OutputMode determins how we should log the output. - OutputMode util.TaskOutputMode - - // Persistent indicates whether the Task is expected to exit or not - // Tasks marked Persistent do not exit (e.g. --watch mode or dev servers) - Persistent bool -} - -// GetTask returns a TaskDefinition based on the ID (package#task format) or name (e.g. "build") -func (pc Pipeline) GetTask(taskID string, taskName string) (*BookkeepingTaskDefinition, error) { - // first check for package-tasks - taskDefinition, ok := pc[taskID] - if !ok { - // then check for regular tasks - fallbackTaskDefinition, notcool := pc[taskName] - // if neither, then bail - if !notcool { - // Return an empty TaskDefinition - return nil, fmt.Errorf("Could not find task \"%s\" in pipeline", taskID) - } - - // override if we need to... - taskDefinition = fallbackTaskDefinition - } - - return &taskDefinition, nil -} - -// LoadTurboConfig loads, or optionally, synthesizes a TurboJSON instance -func LoadTurboConfig(dir turbopath.AbsoluteSystemPath, rootPackageJSON *PackageJSON, includeSynthesizedFromRootPackageJSON bool) (*TurboJSON, error) { - // If the root package.json stil has a `turbo` key, log a warning and remove it. - if rootPackageJSON.LegacyTurboConfig != nil { - log.Printf("[WARNING] \"turbo\" in package.json is no longer supported. Migrate to %s by running \"npx @turbo/codemod create-turbo-config\"\n", configFile) - rootPackageJSON.LegacyTurboConfig = nil - } - - var turboJSON *TurboJSON - turboFromFiles, err := readTurboConfig(dir.UntypedJoin(configFile)) - - if !includeSynthesizedFromRootPackageJSON && err != nil { - // If the file didn't exist, throw a custom error here instead of propagating - if errors.Is(err, os.ErrNotExist) { - return nil, errors.Wrap(err, fmt.Sprintf("Could not find %s. Follow directions at https://turbo.build/repo/docs to create one", configFile)) - - } - - // There was an error, and we don't have any chance of recovering - // because we aren't synthesizing anything - return nil, err - } else if !includeSynthesizedFromRootPackageJSON { - // We're not synthesizing anything and there was no error, we're done - return turboFromFiles, nil - } else if errors.Is(err, os.ErrNotExist) { - // turbo.json doesn't exist, but we're going try to synthesize something - turboJSON = &TurboJSON{ - Pipeline: make(Pipeline), - } - } else if err != nil { - // some other happened, we can't recover - return nil, err - } else { - // we're synthesizing, but we have a starting point - // Note: this will have to change to support task inference in a monorepo - // for now, we're going to error on any "root" tasks and turn non-root tasks into root tasks - pipeline := make(Pipeline) - for taskID, taskDefinition := range turboFromFiles.Pipeline { - if util.IsPackageTask(taskID) { - return nil, fmt.Errorf("Package tasks (<package>#<task>) are not allowed in single-package repositories: found %v", taskID) - } - pipeline[util.RootTaskID(taskID)] = taskDefinition - } - turboJSON = turboFromFiles - turboJSON.Pipeline = pipeline - } - - for scriptName := range rootPackageJSON.Scripts { - if !turboJSON.Pipeline.HasTask(scriptName) { - taskName := util.RootTaskID(scriptName) - // Explicitly set ShouldCache to false in this definition and add the bookkeeping fields - // so downstream we can pretend that it was set on purpose (as if read from a config file) - // rather than defaulting to the 0-value of a boolean field. - turboJSON.Pipeline[taskName] = BookkeepingTaskDefinition{ - definedFields: util.SetFromStrings([]string{"ShouldCache"}), - TaskDefinition: taskDefinitionHashable{ - ShouldCache: false, - }, - } - } - } - return turboJSON, nil -} - -// TurboJSONValidation is the signature for a validation function passed to Validate() -type TurboJSONValidation func(*TurboJSON) []error - -// Validate calls an array of validation functions on the TurboJSON struct. -// The validations can be customized by the caller. -func (tj *TurboJSON) Validate(validations []TurboJSONValidation) []error { - allErrors := []error{} - for _, validation := range validations { - errors := validation(tj) - allErrors = append(allErrors, errors...) - } - - return allErrors -} - -// TaskOutputs represents the patterns for including and excluding files from outputs -type TaskOutputs struct { - Inclusions []string - Exclusions []string -} - -// Sort contents of task outputs -func (to TaskOutputs) Sort() TaskOutputs { - var inclusions []string - var exclusions []string - copy(inclusions, to.Inclusions) - copy(exclusions, to.Exclusions) - sort.Strings(inclusions) - sort.Strings(exclusions) - return TaskOutputs{Inclusions: inclusions, Exclusions: exclusions} -} - -// readTurboConfig reads turbo.json from a provided path -func readTurboConfig(turboJSONPath turbopath.AbsoluteSystemPath) (*TurboJSON, error) { - // If the configFile exists, use that - if turboJSONPath.FileExists() { - turboJSON, err := readTurboJSON(turboJSONPath) - if err != nil { - return nil, fmt.Errorf("%s: %w", configFile, err) - } - - return turboJSON, nil - } - - // If there's no turbo.json, return an error. - return nil, os.ErrNotExist -} - -// readTurboJSON reads the configFile in to a struct -func readTurboJSON(path turbopath.AbsoluteSystemPath) (*TurboJSON, error) { - file, err := path.Open() - if err != nil { - return nil, err - } - var turboJSON *TurboJSON - data, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - - err = jsonc.Unmarshal(data, &turboJSON) - - if err != nil { - return nil, err - } - - return turboJSON, nil -} - -// GetTaskDefinition returns a TaskDefinition from a serialized definition in configFile -func (pc Pipeline) GetTaskDefinition(taskID string) (TaskDefinition, bool) { - if entry, ok := pc[taskID]; ok { - return entry.GetTaskDefinition(), true - } - _, task := util.GetPackageTaskFromId(taskID) - entry, ok := pc[task] - return entry.GetTaskDefinition(), ok -} - -// HasTask returns true if the given task is defined in the pipeline, either directly or -// via a package task (`pkg#task`) -func (pc Pipeline) HasTask(task string) bool { - for key := range pc { - if key == task { - return true - } - if util.IsPackageTask(key) { - _, taskName := util.GetPackageTaskFromId(key) - if taskName == task { - return true - } - } - } - return false -} - -// Pristine returns a PristinePipeline, this is used for printing to console and pruning -func (pc Pipeline) Pristine() PristinePipeline { - pristine := PristinePipeline{} - for taskName, taskDef := range pc { - // If there are any experimental fields, we will include them with 0-values - // if there aren't, we will omit them entirely - if taskDef.hasExperimentalFields() { - pristine[taskName] = taskDef.GetTaskDefinition() // merges experimental fields in - } else { - pristine[taskName] = taskDef.TaskDefinition // has no experimental fields - } - } - return pristine -} - -// hasField checks the internal bookkeeping definedFields field to -// see whether a field was actually in the underlying turbo.json -// or whether it was initialized with its 0-value. -func (btd BookkeepingTaskDefinition) hasField(fieldName string) bool { - return btd.definedFields.Includes(fieldName) || btd.experimentalFields.Includes(fieldName) -} - -// hasExperimentalFields keeps track of whether any experimental fields were found -func (btd BookkeepingTaskDefinition) hasExperimentalFields() bool { - return len(btd.experimentalFields) > 0 -} - -// GetTaskDefinition gets a TaskDefinition by merging the experimental and non-experimental fields -// into a single representation to use downstream. -func (btd BookkeepingTaskDefinition) GetTaskDefinition() TaskDefinition { - return TaskDefinition{ - Outputs: btd.TaskDefinition.Outputs, - ShouldCache: btd.TaskDefinition.ShouldCache, - EnvVarDependencies: btd.TaskDefinition.EnvVarDependencies, - TopologicalDependencies: btd.TaskDefinition.TopologicalDependencies, - TaskDependencies: btd.TaskDefinition.TaskDependencies, - Inputs: btd.TaskDefinition.Inputs, - OutputMode: btd.TaskDefinition.OutputMode, - Persistent: btd.TaskDefinition.Persistent, - // From experimental fields - PassthroughEnv: btd.experimental.PassthroughEnv, - } -} - -// MergeTaskDefinitions accepts an array of BookkeepingTaskDefinitions and merges them into -// a single TaskDefinition. It uses the bookkeeping definedFields to determine which fields should -// be overwritten and when 0-values should be respected. -func MergeTaskDefinitions(taskDefinitions []BookkeepingTaskDefinition) (*TaskDefinition, error) { - // Start with an empty definition - mergedTaskDefinition := &TaskDefinition{} - - // Set the default, because the 0-value will be false, and if no turbo.jsons had - // this field set for this task, we want it to be true. - mergedTaskDefinition.ShouldCache = true - - // For each of the TaskDefinitions we know of, merge them in - for _, bookkeepingTaskDef := range taskDefinitions { - taskDef := bookkeepingTaskDef.GetTaskDefinition() - - if bookkeepingTaskDef.hasField("Outputs") { - mergedTaskDefinition.Outputs = taskDef.Outputs - } - - if bookkeepingTaskDef.hasField("ShouldCache") { - mergedTaskDefinition.ShouldCache = taskDef.ShouldCache - } - - if bookkeepingTaskDef.hasField("EnvVarDependencies") { - mergedTaskDefinition.EnvVarDependencies = taskDef.EnvVarDependencies - } - - if bookkeepingTaskDef.hasField("PassthroughEnv") { - mergedTaskDefinition.PassthroughEnv = taskDef.PassthroughEnv - } - - if bookkeepingTaskDef.hasField("DependsOn") { - mergedTaskDefinition.TopologicalDependencies = taskDef.TopologicalDependencies - } - - if bookkeepingTaskDef.hasField("DependsOn") { - mergedTaskDefinition.TaskDependencies = taskDef.TaskDependencies - } - - if bookkeepingTaskDef.hasField("Inputs") { - mergedTaskDefinition.Inputs = taskDef.Inputs - } - - if bookkeepingTaskDef.hasField("OutputMode") { - mergedTaskDefinition.OutputMode = taskDef.OutputMode - } - if bookkeepingTaskDef.hasField("Persistent") { - mergedTaskDefinition.Persistent = taskDef.Persistent - } - } - - return mergedTaskDefinition, nil -} - -// UnmarshalJSON deserializes a single task definition from -// turbo.json into a TaskDefinition struct -func (btd *BookkeepingTaskDefinition) UnmarshalJSON(data []byte) error { - task := rawTask{} - if err := json.Unmarshal(data, &task); err != nil { - return err - } - - btd.definedFields = util.Set{} - btd.experimentalFields = util.Set{} - - if task.Outputs != nil { - var inclusions []string - var exclusions []string - // Assign a bookkeeping field so we know that there really were - // outputs configured in the underlying config file. - btd.definedFields.Add("Outputs") - - for _, glob := range task.Outputs { - if strings.HasPrefix(glob, "!") { - if filepath.IsAbs(glob[1:]) { - log.Printf("[WARNING] Using an absolute path in \"outputs\" (%v) will not work and will be an error in a future version", glob) - } - exclusions = append(exclusions, glob[1:]) - } else { - if filepath.IsAbs(glob) { - log.Printf("[WARNING] Using an absolute path in \"outputs\" (%v) will not work and will be an error in a future version", glob) - } - inclusions = append(inclusions, glob) - } - } - - btd.TaskDefinition.Outputs = TaskOutputs{ - Inclusions: inclusions, - Exclusions: exclusions, - } - - sort.Strings(btd.TaskDefinition.Outputs.Inclusions) - sort.Strings(btd.TaskDefinition.Outputs.Exclusions) - } - - if task.Cache == nil { - btd.TaskDefinition.ShouldCache = true - } else { - btd.definedFields.Add("ShouldCache") - btd.TaskDefinition.ShouldCache = *task.Cache - } - - envVarDependencies := make(util.Set) - envVarPassthroughs := make(util.Set) - - btd.TaskDefinition.TopologicalDependencies = []string{} // TODO @mehulkar: this should be a set - btd.TaskDefinition.TaskDependencies = []string{} // TODO @mehulkar: this should be a set - - // If there was a dependsOn field, add the bookkeeping - // we don't care what's in the field, just that it was there - // We'll use this marker to overwrite while merging TaskDefinitions. - if task.DependsOn != nil { - btd.definedFields.Add("DependsOn") - } - - for _, dependency := range task.DependsOn { - if strings.HasPrefix(dependency, envPipelineDelimiter) { - log.Printf("[DEPRECATED] Declaring an environment variable in \"dependsOn\" is deprecated, found %s. Use the \"env\" key or use `npx @turbo/codemod migrate-env-var-dependencies`.\n", dependency) - envVarDependencies.Add(strings.TrimPrefix(dependency, envPipelineDelimiter)) - } else if strings.HasPrefix(dependency, topologicalPipelineDelimiter) { - // Note: This will get assigned multiple times in the loop, but we only care that it's true - btd.TaskDefinition.TopologicalDependencies = append(btd.TaskDefinition.TopologicalDependencies, strings.TrimPrefix(dependency, topologicalPipelineDelimiter)) - } else { - btd.TaskDefinition.TaskDependencies = append(btd.TaskDefinition.TaskDependencies, dependency) - } - } - - sort.Strings(btd.TaskDefinition.TaskDependencies) - sort.Strings(btd.TaskDefinition.TopologicalDependencies) - - // Append env key into EnvVarDependencies - if task.Env != nil { - btd.definedFields.Add("EnvVarDependencies") - if err := gatherEnvVars(task.Env, "env", &envVarDependencies); err != nil { - return err - } - } - - btd.TaskDefinition.EnvVarDependencies = envVarDependencies.UnsafeListOfStrings() - - sort.Strings(btd.TaskDefinition.EnvVarDependencies) - - if task.PassthroughEnv != nil { - btd.experimentalFields.Add("PassthroughEnv") - if err := gatherEnvVars(task.PassthroughEnv, "passthrougEnv", &envVarPassthroughs); err != nil { - return err - } - } - - btd.experimental.PassthroughEnv = envVarPassthroughs.UnsafeListOfStrings() - sort.Strings(btd.experimental.PassthroughEnv) - - if task.Inputs != nil { - // Note that we don't require Inputs to be sorted, we're going to - // hash the resulting files and sort that instead - btd.definedFields.Add("Inputs") - // TODO: during rust port, this should be moved to a post-parse validation step - for _, input := range task.Inputs { - if filepath.IsAbs(input) { - log.Printf("[WARNING] Using an absolute path in \"inputs\" (%v) will not work and will be an error in a future version", input) - } - } - btd.TaskDefinition.Inputs = task.Inputs - } - - if task.OutputMode != nil { - btd.definedFields.Add("OutputMode") - btd.TaskDefinition.OutputMode = *task.OutputMode - } - - if task.Persistent != nil { - btd.definedFields.Add("Persistent") - btd.TaskDefinition.Persistent = *task.Persistent - } else { - btd.TaskDefinition.Persistent = false - } - return nil -} - -// MarshalJSON serializes taskDefinitionHashable struct into json -func (c taskDefinitionHashable) MarshalJSON() ([]byte, error) { - task := makeRawTask( - c.Persistent, - c.ShouldCache, - c.OutputMode, - c.Inputs, - c.Outputs, - c.EnvVarDependencies, - c.TaskDependencies, - c.TopologicalDependencies, - ) - return json.Marshal(task) -} - -// MarshalJSON serializes TaskDefinition struct into json -func (c TaskDefinition) MarshalJSON() ([]byte, error) { - task := makeRawTask( - c.Persistent, - c.ShouldCache, - c.OutputMode, - c.Inputs, - c.Outputs, - c.EnvVarDependencies, - c.TaskDependencies, - c.TopologicalDependencies, - ) - - if len(c.PassthroughEnv) > 0 { - task.PassthroughEnv = append(task.PassthroughEnv, c.PassthroughEnv...) - } - sort.Strings(task.PassthroughEnv) - - return json.Marshal(task) -} - -// UnmarshalJSON deserializes the contents of turbo.json into a TurboJSON struct -func (c *TurboJSON) UnmarshalJSON(data []byte) error { - raw := &rawTurboJSON{} - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - envVarDependencies := make(util.Set) - envVarPassthroughs := make(util.Set) - globalFileDependencies := make(util.Set) - - if err := gatherEnvVars(raw.GlobalEnv, "globalEnv", &envVarDependencies); err != nil { - return err - } - if err := gatherEnvVars(raw.GlobalPassthroughEnv, "experimentalGlobalPassThroughEnv", &envVarPassthroughs); err != nil { - return err - } - - // TODO: In the rust port, warnings should be refactored to a post-parse validation step - for _, value := range raw.GlobalDependencies { - if strings.HasPrefix(value, envPipelineDelimiter) { - log.Printf("[DEPRECATED] Declaring an environment variable in \"globalDependencies\" is deprecated, found %s. Use the \"globalEnv\" key or use `npx @turbo/codemod migrate-env-var-dependencies`.\n", value) - envVarDependencies.Add(strings.TrimPrefix(value, envPipelineDelimiter)) - } else { - if filepath.IsAbs(value) { - log.Printf("[WARNING] Using an absolute path in \"globalDependencies\" (%v) will not work and will be an error in a future version", value) - } - globalFileDependencies.Add(value) - } - } - - // turn the set into an array and assign to the TurboJSON struct fields. - c.GlobalEnv = envVarDependencies.UnsafeListOfStrings() - sort.Strings(c.GlobalEnv) - - if raw.GlobalPassthroughEnv != nil { - c.GlobalPassthroughEnv = envVarPassthroughs.UnsafeListOfStrings() - sort.Strings(c.GlobalPassthroughEnv) - } - - c.GlobalDeps = globalFileDependencies.UnsafeListOfStrings() - sort.Strings(c.GlobalDeps) - - // copy these over, we don't need any changes here. - c.Pipeline = raw.Pipeline - c.RemoteCacheOptions = raw.RemoteCacheOptions - c.Extends = raw.Extends - - return nil -} - -// MarshalJSON converts a TurboJSON into the equivalent json object in bytes -// note: we go via rawTurboJSON so that the output format is correct. -// This is used by `turbo prune` to generate a pruned turbo.json -// and also by --summarize & --dry=json to serialize the known config -// into something we can print to screen -func (c *TurboJSON) MarshalJSON() ([]byte, error) { - raw := pristineTurboJSON{} - raw.GlobalDependencies = c.GlobalDeps - raw.GlobalEnv = c.GlobalEnv - raw.GlobalPassthroughEnv = c.GlobalPassthroughEnv - raw.Pipeline = c.Pipeline.Pristine() - raw.RemoteCacheOptions = c.RemoteCacheOptions - - return json.Marshal(&raw) -} - -func makeRawTask(persistent bool, shouldCache bool, outputMode util.TaskOutputMode, inputs []string, outputs TaskOutputs, envVarDependencies []string, taskDependencies []string, topologicalDependencies []string) *rawTaskWithDefaults { - // Initialize with empty arrays, so we get empty arrays serialized into JSON - task := &rawTaskWithDefaults{ - Outputs: []string{}, - Inputs: []string{}, - Env: []string{}, - PassthroughEnv: []string{}, - DependsOn: []string{}, - } - - task.Persistent = persistent - task.Cache = &shouldCache - task.OutputMode = outputMode - - if len(inputs) > 0 { - task.Inputs = inputs - } - - if len(envVarDependencies) > 0 { - task.Env = append(task.Env, envVarDependencies...) - } - - if len(outputs.Inclusions) > 0 { - task.Outputs = append(task.Outputs, outputs.Inclusions...) - } - - for _, i := range outputs.Exclusions { - task.Outputs = append(task.Outputs, "!"+i) - } - - if len(taskDependencies) > 0 { - task.DependsOn = append(task.DependsOn, taskDependencies...) - } - - for _, i := range topologicalDependencies { - task.DependsOn = append(task.DependsOn, "^"+i) - } - - // These _should_ already be sorted when the TaskDefinition struct was unmarshaled, - // but we want to ensure they're sorted on the way out also, just in case something - // in the middle mutates the items. - sort.Strings(task.DependsOn) - sort.Strings(task.Outputs) - sort.Strings(task.Env) - sort.Strings(task.Inputs) - return task -} - -// gatherEnvVars puts env vars into the provided set as long as they don't have an invalid value. -func gatherEnvVars(vars []string, key string, into *util.Set) error { - for _, value := range vars { - if strings.HasPrefix(value, envPipelineDelimiter) { - // Hard error to help people specify this correctly during migration. - // TODO: Remove this error after we have run summary. - return fmt.Errorf("You specified \"%s\" in the \"%s\" key. You should not prefix your environment variables with \"%s\"", value, key, envPipelineDelimiter) - } - - into.Add(value) - } - - return nil -} |
