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(©); 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 }