github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/project_parser.go (about)

     1  package model
     2  
     3  import (
     4  	"bytes"
     5  	"reflect"
     6  
     7  	"github.com/evergreen-ci/evergreen/command"
     8  	"github.com/evergreen-ci/evergreen/util"
     9  	"github.com/mongodb/grip"
    10  	"github.com/pkg/errors"
    11  	"gopkg.in/yaml.v2"
    12  )
    13  
    14  // This file contains the infrastructure for turning a YAML project configuration
    15  // into a usable Project struct. A basic overview of the project parsing process is:
    16  //
    17  // First, the YAML bytes are unmarshalled into an intermediary parserProject.
    18  // The parserProject's internal types define custom YAML unmarshal hooks, allowing
    19  // users to do things like offer a single definition where we expect a list, e.g.
    20  //   `tags: "single_tag"` instead of the more verbose `tags: ["single_tag"]`
    21  // or refer to task by a single selector. Custom YAML handling allows us to
    22  // add other useful features like detecting fatal errors and reporting them
    23  // through the YAML parser's error code, which supplies helpful line number information
    24  // that we would lose during validation against already-parsed data. In the future,
    25  // custom YAML hooks will allow us to add even more helpful features, like alerting users
    26  // when they use fields that aren't actually defined.
    27  //
    28  // Once the intermediary project is created, we crawl it to evaluate tag selectors
    29  // and  matrix definitions. This step recursively crawls variants, tasks, their
    30  // dependencies, and so on, to replace selectors with the tasks they reference and return
    31  // a populated Project type.
    32  //
    33  // Code outside of this file should never have to consider selectors or parser* types
    34  // when handling project code.
    35  
    36  // parserProject serves as an intermediary struct for parsing project
    37  // configuration YAML. It implements the Unmarshaler interface
    38  // to allow for flexible handling.
    39  type parserProject struct {
    40  	Enabled         bool                       `yaml:"enabled"`
    41  	Stepback        bool                       `yaml:"stepback"`
    42  	BatchTime       int                        `yaml:"batchtime"`
    43  	Owner           string                     `yaml:"owner"`
    44  	Repo            string                     `yaml:"repo"`
    45  	RemotePath      string                     `yaml:"remote_path"`
    46  	RepoKind        string                     `yaml:"repokind"`
    47  	Branch          string                     `yaml:"branch"`
    48  	Identifier      string                     `yaml:"identifier"`
    49  	DisplayName     string                     `yaml:"display_name"`
    50  	CommandType     string                     `yaml:"command_type"`
    51  	Ignore          parserStringSlice          `yaml:"ignore"`
    52  	Pre             *YAMLCommandSet            `yaml:"pre"`
    53  	Post            *YAMLCommandSet            `yaml:"post"`
    54  	Timeout         *YAMLCommandSet            `yaml:"timeout"`
    55  	CallbackTimeout int                        `yaml:"callback_timeout_secs"`
    56  	Modules         []Module                   `yaml:"modules"`
    57  	BuildVariants   []parserBV                 `yaml:"buildvariants"`
    58  	Functions       map[string]*YAMLCommandSet `yaml:"functions"`
    59  	Tasks           []parserTask               `yaml:"tasks"`
    60  	ExecTimeoutSecs int                        `yaml:"exec_timeout_secs"`
    61  
    62  	// Matrix code
    63  	Axes []matrixAxis `yaml:"axes"`
    64  }
    65  
    66  // parserTask represents an intermediary state of task definitions.
    67  type parserTask struct {
    68  	Name            string              `yaml:"name"`
    69  	Priority        int64               `yaml:"priority"`
    70  	ExecTimeoutSecs int                 `yaml:"exec_timeout_secs"`
    71  	DisableCleanup  bool                `yaml:"disable_cleanup"`
    72  	DependsOn       parserDependencies  `yaml:"depends_on"`
    73  	Requires        taskSelectors       `yaml:"requires"`
    74  	Commands        []PluginCommandConf `yaml:"commands"`
    75  	Tags            parserStringSlice   `yaml:"tags"`
    76  	Patchable       *bool               `yaml:"patchable"`
    77  	Stepback        *bool               `yaml:"stepback"`
    78  }
    79  
    80  // helper methods for task tag evaluations
    81  func (pt *parserTask) name() string   { return pt.Name }
    82  func (pt *parserTask) tags() []string { return pt.Tags }
    83  
    84  // parserDependency represents the intermediary state for referencing dependencies.
    85  type parserDependency struct {
    86  	taskSelector
    87  	Status        string `yaml:"status"`
    88  	PatchOptional bool   `yaml:"patch_optional"`
    89  }
    90  
    91  // parserDependencies is a type defined for unmarshalling both a single
    92  // dependency or multiple dependencies into a slice.
    93  type parserDependencies []parserDependency
    94  
    95  // UnmarshalYAML reads YAML into an array of parserDependency. It will
    96  // successfully unmarshal arrays of dependency entries or single dependency entry.
    97  func (pds *parserDependencies) UnmarshalYAML(unmarshal func(interface{}) error) error {
    98  	// first check if we are handling a single dep that is not in an array.
    99  	pd := parserDependency{}
   100  	if err := unmarshal(&pd); err == nil {
   101  		*pds = parserDependencies([]parserDependency{pd})
   102  		return nil
   103  	}
   104  	var slice []parserDependency
   105  	if err := unmarshal(&slice); err != nil {
   106  		return err
   107  	}
   108  	*pds = parserDependencies(slice)
   109  	return nil
   110  }
   111  
   112  // UnmarshalYAML reads YAML into a parserDependency. A single selector string
   113  // will be also be accepted.
   114  func (pd *parserDependency) UnmarshalYAML(unmarshal func(interface{}) error) error {
   115  	if err := unmarshal(&pd.taskSelector); err != nil {
   116  		return err
   117  	}
   118  	otherFields := struct {
   119  		Status        string `yaml:"status"`
   120  		PatchOptional bool   `yaml:"patch_optional"`
   121  	}{}
   122  	// ignore any errors here; if we're using a single-string selector, this is expected to fail
   123  	grip.Debug(unmarshal(&otherFields))
   124  	pd.Status = otherFields.Status
   125  	pd.PatchOptional = otherFields.PatchOptional
   126  	return nil
   127  }
   128  
   129  // TaskSelector handles the selection of specific task/variant combinations
   130  // in the context of dependencies and requirements fields. //TODO no export?
   131  type taskSelector struct {
   132  	Name    string           `yaml:"name"`
   133  	Variant *variantSelector `yaml:"variant"`
   134  }
   135  
   136  // TaskSelectors is a helper type for parsing arrays of TaskSelector.
   137  type taskSelectors []taskSelector
   138  
   139  // VariantSelector handles the selection of a variant, either by a id/tag selector
   140  // or by matching against matrix axis values.
   141  type variantSelector struct {
   142  	stringSelector string
   143  	matrixSelector matrixDefinition
   144  }
   145  
   146  // UnmarshalYAML allows variants to be referenced as single selector strings or
   147  // as a matrix definition. This works by first attempting to unmarshal the YAML
   148  // into a string and then falling back to the matrix.
   149  func (vs *variantSelector) UnmarshalYAML(unmarshal func(interface{}) error) error {
   150  	// first, attempt to unmarshal just a selector string
   151  	var onlySelector string
   152  	if err := unmarshal(&onlySelector); err == nil {
   153  		if onlySelector != "" {
   154  			vs.stringSelector = onlySelector
   155  			return nil
   156  		}
   157  	}
   158  
   159  	md := matrixDefinition{}
   160  	if err := unmarshal(&md); err != nil {
   161  		return err
   162  	}
   163  	if len(md) == 0 {
   164  		return errors.New("variant selector must not be empty")
   165  	}
   166  	vs.matrixSelector = md
   167  	return nil
   168  }
   169  
   170  // UnmarshalYAML reads YAML into an array of TaskSelector. It will
   171  // successfully unmarshal arrays of dependency selectors or a single selector.
   172  func (tss *taskSelectors) UnmarshalYAML(unmarshal func(interface{}) error) error {
   173  	// first, attempt to unmarshal a single selector
   174  	var single taskSelector
   175  	if err := unmarshal(&single); err == nil {
   176  		*tss = taskSelectors([]taskSelector{single})
   177  		return nil
   178  	}
   179  	var slice []taskSelector
   180  	if err := unmarshal(&slice); err != nil {
   181  		return err
   182  	}
   183  	*tss = taskSelectors(slice)
   184  	return nil
   185  }
   186  
   187  // UnmarshalYAML allows tasks to be referenced as single selector strings.
   188  // This works by first attempting to unmarshal the YAML into a string
   189  // and then falling back to the TaskSelector struct.
   190  func (ts *taskSelector) UnmarshalYAML(unmarshal func(interface{}) error) error {
   191  	// first, attempt to unmarshal just a selector string
   192  	var onlySelector string
   193  	if err := unmarshal(&onlySelector); err == nil {
   194  		if onlySelector != "" {
   195  			ts.Name = onlySelector
   196  			return nil
   197  		}
   198  	}
   199  	// we define a new type so that we can grab the yaml struct tags without the struct methods,
   200  	// preventing infinite recursion on the UnmarshalYAML() method.
   201  	type copyType taskSelector
   202  	var tsc copyType
   203  	if err := unmarshal(&tsc); err != nil {
   204  		return err
   205  	}
   206  	if tsc.Name == "" {
   207  		return errors.New("task selector must have a name")
   208  	}
   209  	*ts = taskSelector(tsc)
   210  	return nil
   211  }
   212  
   213  // parserBV is a helper type storing intermediary variant definitions.
   214  type parserBV struct {
   215  	Name        string             `yaml:"name"`
   216  	DisplayName string             `yaml:"display_name"`
   217  	Expansions  command.Expansions `yaml:"expansions"`
   218  	Tags        parserStringSlice  `yaml:"tags"`
   219  	Modules     parserStringSlice  `yaml:"modules"`
   220  	Disabled    bool               `yaml:"disabled"`
   221  	Push        bool               `yaml:"push"`
   222  	BatchTime   *int               `yaml:"batchtime"`
   223  	Stepback    *bool              `yaml:"stepback"`
   224  	RunOn       parserStringSlice  `yaml:"run_on"`
   225  	Tasks       parserBVTasks      `yaml:"tasks"`
   226  
   227  	// internal matrix stuff
   228  	matrixId  string
   229  	matrixVal matrixValue
   230  	matrix    *matrix
   231  
   232  	matrixRules []ruleAction
   233  }
   234  
   235  // helper methods for variant tag evaluations
   236  func (pbv *parserBV) name() string   { return pbv.Name }
   237  func (pbv *parserBV) tags() []string { return pbv.Tags }
   238  
   239  func (pbv *parserBV) UnmarshalYAML(unmarshal func(interface{}) error) error {
   240  	// first attempt to unmarshal into a matrix
   241  	m := matrix{}
   242  	merr := unmarshal(&m)
   243  	if merr == nil {
   244  		if m.Id != "" {
   245  			*pbv = parserBV{matrix: &m}
   246  			return nil
   247  		}
   248  	}
   249  	// otherwise use a BV copy type to skip this Unmarshal method
   250  	type copyType parserBV
   251  	var bv copyType
   252  	if err := unmarshal(&bv); err != nil {
   253  		return errors.WithStack(err)
   254  	}
   255  	if bv.Name == "" {
   256  		// if we're here, it's very likely that the user was building a matrix but broke
   257  		// the syntax, so we try and surface the matrix error if they used "matrix_name".
   258  		if m.Id != "" {
   259  			return errors.Wrap(merr, "parsing matrix")
   260  		}
   261  		return errors.New("buildvariant missing name")
   262  	}
   263  	*pbv = parserBV(bv)
   264  	return nil
   265  }
   266  
   267  // parserBVTask is a helper type storing intermediary variant task configurations.
   268  type parserBVTask struct {
   269  	Name            string             `yaml:"name"`
   270  	Patchable       *bool              `yaml:"patchable"`
   271  	Priority        int64              `yaml:"priority"`
   272  	DependsOn       parserDependencies `yaml:"depends_on"`
   273  	Requires        taskSelectors      `yaml:"requires"`
   274  	ExecTimeoutSecs int                `yaml:"exec_timeout_secs"`
   275  	Stepback        *bool              `yaml:"stepback"`
   276  	Distros         parserStringSlice  `yaml:"distros"`
   277  	RunOn           parserStringSlice  `yaml:"run_on"` // Alias for "Distros" TODO: deprecate Distros
   278  }
   279  
   280  // UnmarshalYAML allows the YAML parser to read both a single selector string or
   281  // a fully defined parserBVTask.
   282  func (pbvt *parserBVTask) UnmarshalYAML(unmarshal func(interface{}) error) error {
   283  	// first, attempt to unmarshal just a selector string
   284  	var onlySelector string
   285  	if err := unmarshal(&onlySelector); err == nil {
   286  		if onlySelector != "" {
   287  			pbvt.Name = onlySelector
   288  			return nil
   289  		}
   290  	}
   291  	// we define a new type so that we can grab the YAML struct tags without the struct methods,
   292  	// preventing infinite recursion on the UnmarshalYAML() method.
   293  	type copyType parserBVTask
   294  	var copy copyType
   295  	if err := unmarshal(&copy); err != nil {
   296  		return err
   297  	}
   298  	if copy.Name == "" {
   299  		return errors.New("task selector must have a name")
   300  	}
   301  	// logic for aliasing the "run_on" field to "distros"
   302  	if len(copy.RunOn) > 0 {
   303  		if len(copy.Distros) > 0 {
   304  			return errors.New("cannot use both 'run_on' and 'distros' fields")
   305  		}
   306  		copy.Distros, copy.RunOn = copy.RunOn, nil
   307  	}
   308  	*pbvt = parserBVTask(copy)
   309  	return nil
   310  }
   311  
   312  // parserBVTasks is a helper type for handling arrays of parserBVTask.
   313  type parserBVTasks []parserBVTask
   314  
   315  // UnmarshalYAML allows the YAML parser to read both a single parserBVTask or
   316  // an array of them into a slice.
   317  func (pbvts *parserBVTasks) UnmarshalYAML(unmarshal func(interface{}) error) error {
   318  	// first, attempt to unmarshal just a selector string
   319  	var single parserBVTask
   320  	if err := unmarshal(&single); err == nil {
   321  		*pbvts = parserBVTasks([]parserBVTask{single})
   322  		return nil
   323  	}
   324  	var slice []parserBVTask
   325  	if err := unmarshal(&slice); err != nil {
   326  		return err
   327  	}
   328  	*pbvts = parserBVTasks(slice)
   329  	return nil
   330  }
   331  
   332  // parserStringSlice is YAML helper type that accepts both an array of strings
   333  // or single string value during unmarshalling.
   334  type parserStringSlice []string
   335  
   336  // UnmarshalYAML allows the YAML parser to read both a single string or
   337  // an array of them into a slice.
   338  func (pss *parserStringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
   339  	var single string
   340  	if err := unmarshal(&single); err == nil {
   341  		*pss = []string{single}
   342  		return nil
   343  	}
   344  	var slice []string
   345  	if err := unmarshal(&slice); err != nil {
   346  		return err
   347  	}
   348  	*pss = slice
   349  	return nil
   350  }
   351  
   352  // LoadProjectInto loads the raw data from the config file into project
   353  // and sets the project's identifier field to identifier. Tags are evaluateed.
   354  func LoadProjectInto(data []byte, identifier string, project *Project) error {
   355  	p, errs := projectFromYAML(data) // ignore warnings, for now (TODO)
   356  	if len(errs) > 0 {
   357  		// create a human-readable error list
   358  		buf := bytes.Buffer{}
   359  		for _, e := range errs {
   360  			if len(errs) > 1 {
   361  				buf.WriteString("\n\t") //only newline if we have multiple errs
   362  			}
   363  			buf.WriteString(e.Error())
   364  		}
   365  		if len(errs) > 1 {
   366  			return errors.Errorf("project errors: %v", buf.String())
   367  		}
   368  		return errors.Errorf("project error: %v", buf.String())
   369  	}
   370  	*project = *p
   371  	project.Identifier = identifier
   372  	return nil
   373  }
   374  
   375  // projectFromYAML reads and evaluates project YAML, returning a project and warnings and
   376  // errors encountered during parsing or evaluation.
   377  func projectFromYAML(yml []byte) (*Project, []error) {
   378  	pp, errs := createIntermediateProject(yml)
   379  	if len(errs) > 0 {
   380  		return nil, errs
   381  	}
   382  	p, errs := translateProject(pp)
   383  	return p, errs
   384  }
   385  
   386  // createIntermediateProject marshals the supplied YAML into our
   387  // intermediate project representation (i.e. before selectors or
   388  // matrix logic has been evaluated).
   389  func createIntermediateProject(yml []byte) (*parserProject, []error) {
   390  	p := &parserProject{}
   391  	err := yaml.Unmarshal(yml, p)
   392  	if err != nil {
   393  		return nil, []error{err}
   394  	}
   395  
   396  	return p, nil
   397  }
   398  
   399  // translateProject converts our intermediate project representation into
   400  // the Project type that Evergreen actually uses. Errors are added to
   401  // pp.errors and pp.warnings and must be checked separately.
   402  func translateProject(pp *parserProject) (*Project, []error) {
   403  	// Transfer top level fields
   404  	proj := &Project{
   405  		Enabled:         pp.Enabled,
   406  		Stepback:        pp.Stepback,
   407  		BatchTime:       pp.BatchTime,
   408  		Owner:           pp.Owner,
   409  		Repo:            pp.Repo,
   410  		RemotePath:      pp.RemotePath,
   411  		RepoKind:        pp.RepoKind,
   412  		Branch:          pp.Branch,
   413  		Identifier:      pp.Identifier,
   414  		DisplayName:     pp.DisplayName,
   415  		CommandType:     pp.CommandType,
   416  		Ignore:          pp.Ignore,
   417  		Pre:             pp.Pre,
   418  		Post:            pp.Post,
   419  		Timeout:         pp.Timeout,
   420  		CallbackTimeout: pp.CallbackTimeout,
   421  		Modules:         pp.Modules,
   422  		Functions:       pp.Functions,
   423  		ExecTimeoutSecs: pp.ExecTimeoutSecs,
   424  	}
   425  	tse := NewParserTaskSelectorEvaluator(pp.Tasks)
   426  	ase := NewAxisSelectorEvaluator(pp.Axes)
   427  	regularBVs, matrices := sieveMatrixVariants(pp.BuildVariants)
   428  	var evalErrs, errs []error
   429  	matrixVariants, errs := buildMatrixVariants(pp.Axes, ase, matrices)
   430  	evalErrs = append(evalErrs, errs...)
   431  	pp.BuildVariants = append(regularBVs, matrixVariants...)
   432  	vse := NewVariantSelectorEvaluator(pp.BuildVariants, ase)
   433  	proj.Tasks, errs = evaluateTasks(tse, vse, pp.Tasks)
   434  	evalErrs = append(evalErrs, errs...)
   435  	proj.BuildVariants, errs = evaluateBuildVariants(tse, vse, pp.BuildVariants)
   436  	evalErrs = append(evalErrs, errs...)
   437  	return proj, evalErrs
   438  }
   439  
   440  // sieveMatrixVariants takes a set of parserBVs and groups them into regular
   441  // buildvariant matrix definitions and matrix definitions.
   442  func sieveMatrixVariants(bvs []parserBV) (regular []parserBV, matrices []matrix) {
   443  	for _, bv := range bvs {
   444  		if bv.matrix != nil {
   445  			matrices = append(matrices, *bv.matrix)
   446  		} else {
   447  			regular = append(regular, bv)
   448  		}
   449  	}
   450  	return regular, matrices
   451  }
   452  
   453  // evaluateTasks translates intermediate tasks into true ProjectTask types,
   454  // evaluating any selectors in the DependsOn or Requires fields.
   455  func evaluateTasks(tse *taskSelectorEvaluator, vse *variantSelectorEvaluator,
   456  	pts []parserTask) ([]ProjectTask, []error) {
   457  	tasks := []ProjectTask{}
   458  	var evalErrs, errs []error
   459  	for _, pt := range pts {
   460  		t := ProjectTask{
   461  			Name:            pt.Name,
   462  			Priority:        pt.Priority,
   463  			ExecTimeoutSecs: pt.ExecTimeoutSecs,
   464  			Commands:        pt.Commands,
   465  			Tags:            pt.Tags,
   466  			Patchable:       pt.Patchable,
   467  			Stepback:        pt.Stepback,
   468  		}
   469  		t.DependsOn, errs = evaluateDependsOn(tse, vse, pt.DependsOn)
   470  		evalErrs = append(evalErrs, errs...)
   471  		t.Requires, errs = evaluateRequires(tse, vse, pt.Requires)
   472  		evalErrs = append(evalErrs, errs...)
   473  		tasks = append(tasks, t)
   474  	}
   475  	return tasks, evalErrs
   476  }
   477  
   478  // evaluateBuildsVariants translates intermediate tasks into true BuildVariant types,
   479  // evaluating any selectors in the Tasks fields.
   480  func evaluateBuildVariants(tse *taskSelectorEvaluator, vse *variantSelectorEvaluator,
   481  	pbvs []parserBV) ([]BuildVariant, []error) {
   482  	bvs := []BuildVariant{}
   483  	var evalErrs, errs []error
   484  	for _, pbv := range pbvs {
   485  		bv := BuildVariant{
   486  			DisplayName: pbv.DisplayName,
   487  			Name:        pbv.Name,
   488  			Expansions:  pbv.Expansions,
   489  			Modules:     pbv.Modules,
   490  			Disabled:    pbv.Disabled,
   491  			Push:        pbv.Push,
   492  			BatchTime:   pbv.BatchTime,
   493  			Stepback:    pbv.Stepback,
   494  			RunOn:       pbv.RunOn,
   495  			Tags:        pbv.Tags,
   496  		}
   497  		bv.Tasks, errs = evaluateBVTasks(tse, vse, pbv.Tasks)
   498  		// evaluate any rules passed in during matrix construction
   499  		for _, r := range pbv.matrixRules {
   500  			// remove_tasks removes all tasks with matching names
   501  			if len(r.RemoveTasks) > 0 {
   502  				prunedTasks := []BuildVariantTask{}
   503  				toRemove := []string{}
   504  				for _, t := range r.RemoveTasks {
   505  					removed, err := tse.evalSelector(ParseSelector(t))
   506  					if err != nil {
   507  						evalErrs = append(evalErrs, errors.Wrap(err, "remove rule"))
   508  						continue
   509  					}
   510  					toRemove = append(toRemove, removed...)
   511  				}
   512  				for _, t := range bv.Tasks {
   513  					if !util.SliceContains(toRemove, t.Name) {
   514  						prunedTasks = append(prunedTasks, t)
   515  					}
   516  				}
   517  				bv.Tasks = prunedTasks
   518  			}
   519  			// add_tasks adds the given BuildVariantTasks, returning errors for any collisions
   520  			if len(r.AddTasks) > 0 {
   521  				// cache existing tasks so we can check for duplicates
   522  				existing := map[string]*BuildVariantTask{}
   523  				for i, t := range bv.Tasks {
   524  					existing[t.Name] = &bv.Tasks[i]
   525  				}
   526  
   527  				var added []BuildVariantTask
   528  				added, errs = evaluateBVTasks(tse, vse, r.AddTasks)
   529  				evalErrs = append(evalErrs, errs...)
   530  				// check for conflicting duplicates
   531  				for _, t := range added {
   532  					if old, ok := existing[t.Name]; ok {
   533  						if !reflect.DeepEqual(t, *old) {
   534  							evalErrs = append(evalErrs, errors.Errorf(
   535  								"conflicting definitions of added tasks '%v': %v != %v", t.Name, t, old))
   536  						}
   537  					} else {
   538  						bv.Tasks = append(bv.Tasks, t)
   539  						existing[t.Name] = &t
   540  					}
   541  				}
   542  			}
   543  		}
   544  		evalErrs = append(evalErrs, errs...)
   545  		bvs = append(bvs, bv)
   546  	}
   547  	return bvs, evalErrs
   548  }
   549  
   550  // evaluateBVTasks translates intermediate tasks into true BuildVariantTask types,
   551  // evaluating any selectors referencing tasks, and further evaluating any selectors
   552  // in the DependsOn or Requires fields of those tasks.
   553  func evaluateBVTasks(tse *taskSelectorEvaluator, vse *variantSelectorEvaluator,
   554  	pbvts []parserBVTask) ([]BuildVariantTask, []error) {
   555  	var evalErrs, errs []error
   556  	ts := []BuildVariantTask{}
   557  	tasksByName := map[string]BuildVariantTask{}
   558  	for _, pt := range pbvts {
   559  		names, err := tse.evalSelector(ParseSelector(pt.Name))
   560  		if err != nil {
   561  			evalErrs = append(evalErrs, err)
   562  			continue
   563  		}
   564  		// create new task definitions--duplicates must have the same status requirements
   565  		for _, name := range names {
   566  			// create a new task by copying the task that selected it,
   567  			// so we can preserve the "Variant" and "Status" field.
   568  			t := BuildVariantTask{
   569  				Name:            name,
   570  				Patchable:       pt.Patchable,
   571  				Priority:        pt.Priority,
   572  				ExecTimeoutSecs: pt.ExecTimeoutSecs,
   573  				Stepback:        pt.Stepback,
   574  				Distros:         pt.Distros,
   575  			}
   576  			t.DependsOn, errs = evaluateDependsOn(tse, vse, pt.DependsOn)
   577  			evalErrs = append(evalErrs, errs...)
   578  			t.Requires, errs = evaluateRequires(tse, vse, pt.Requires)
   579  			evalErrs = append(evalErrs, errs...)
   580  
   581  			// add the new task if it doesn't already exists (we must avoid conflicting status fields)
   582  			if old, ok := tasksByName[t.Name]; !ok {
   583  				ts = append(ts, t)
   584  				tasksByName[t.Name] = t
   585  			} else {
   586  				// it's already in the new list, so we check to make sure the status definitions match.
   587  				if !reflect.DeepEqual(t, old) {
   588  					evalErrs = append(evalErrs, errors.Errorf(
   589  						"conflicting definitions of build variant tasks '%v': %v != %v", name, t, old))
   590  					continue
   591  				}
   592  			}
   593  		}
   594  	}
   595  	return ts, evalErrs
   596  }
   597  
   598  // evaluateDependsOn expands any selectors in a dependency definition.
   599  func evaluateDependsOn(tse *taskSelectorEvaluator, vse *variantSelectorEvaluator,
   600  	deps []parserDependency) ([]TaskDependency, []error) {
   601  	var evalErrs []error
   602  	var err error
   603  	newDeps := []TaskDependency{}
   604  	newDepsByNameAndVariant := map[TVPair]TaskDependency{}
   605  	for _, d := range deps {
   606  		var names []string
   607  
   608  		if d.Name == AllDependencies {
   609  			// * is a special case for dependencies, so don't eval it
   610  			names = []string{AllDependencies}
   611  		} else {
   612  			names, err = tse.evalSelector(ParseSelector(d.Name))
   613  			if err != nil {
   614  				evalErrs = append(evalErrs, err)
   615  				continue
   616  			}
   617  		}
   618  		// we default to handle the empty variant, but expand the list of variants
   619  		// if the variant field is set.
   620  		variants := []string{""}
   621  		if d.Variant != nil {
   622  			variants, err = vse.evalSelector(d.Variant)
   623  			if err != nil {
   624  				evalErrs = append(evalErrs, err)
   625  				continue
   626  			}
   627  		}
   628  		// create new dependency definitions--duplicates must have the same status requirements
   629  		for _, name := range names {
   630  			for _, variant := range variants {
   631  				// create a newDep by copying the dep that selected it,
   632  				// so we can preserve the "Status" and "PatchOptional" field.
   633  				newDep := TaskDependency{
   634  					Name:          name,
   635  					Variant:       variant,
   636  					Status:        d.Status,
   637  					PatchOptional: d.PatchOptional,
   638  				}
   639  				// add the new dep if it doesn't already exists (we must avoid conflicting status fields)
   640  				if oldDep, ok := newDepsByNameAndVariant[TVPair{newDep.Variant, newDep.Name}]; !ok {
   641  					newDeps = append(newDeps, newDep)
   642  					newDepsByNameAndVariant[TVPair{newDep.Variant, newDep.Name}] = newDep
   643  				} else {
   644  					// it's already in the new list, so we check to make sure the status definitions match.
   645  					if !reflect.DeepEqual(newDep, oldDep) {
   646  						evalErrs = append(evalErrs, errors.Errorf(
   647  							"conflicting definitions of dependency '%v': %v != %v", name, newDep, oldDep))
   648  						continue
   649  					}
   650  				}
   651  			}
   652  		}
   653  	}
   654  	return newDeps, evalErrs
   655  }
   656  
   657  // evaluateRequires expands any selectors in a requirement definition.
   658  func evaluateRequires(tse *taskSelectorEvaluator, vse *variantSelectorEvaluator,
   659  	reqs []taskSelector) ([]TaskRequirement, []error) {
   660  	var evalErrs []error
   661  	newReqs := []TaskRequirement{}
   662  	newReqsByNameAndVariant := map[TVPair]struct{}{}
   663  	for _, r := range reqs {
   664  		names, err := tse.evalSelector(ParseSelector(r.Name))
   665  		if err != nil {
   666  			evalErrs = append(evalErrs, err)
   667  			continue
   668  		}
   669  		// we default to handle the empty variant, but expand the list of variants
   670  		// if the variant field is set.
   671  		variants := []string{""}
   672  		if r.Variant != nil {
   673  			variants, err = vse.evalSelector(r.Variant)
   674  			if err != nil {
   675  				evalErrs = append(evalErrs, err)
   676  				continue
   677  			}
   678  		}
   679  		for _, name := range names {
   680  			for _, variant := range variants {
   681  				newReq := TaskRequirement{Name: name, Variant: variant}
   682  				newReq.Name = name
   683  				// add the new req if it doesn't already exists (we must avoid duplicates)
   684  				if _, ok := newReqsByNameAndVariant[TVPair{newReq.Variant, newReq.Name}]; !ok {
   685  					newReqs = append(newReqs, newReq)
   686  					newReqsByNameAndVariant[TVPair{newReq.Variant, newReq.Name}] = struct{}{}
   687  				}
   688  			}
   689  		}
   690  	}
   691  	return newReqs, evalErrs
   692  }