github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/pkg/mvs/mvs.go (about) 1 package mvs 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/tooploox/oya/pkg/errors" 8 "github.com/tooploox/oya/pkg/mvs/internal" 9 "github.com/tooploox/oya/pkg/pack" 10 "github.com/tooploox/oya/pkg/types" 11 ) 12 13 type ErrResolvingReqs struct { 14 } 15 16 func (e ErrResolvingReqs) Error() string { 17 return "problem getting requirements" 18 } 19 20 type Reqs interface { 21 Reqs(pack pack.Pack) ([]pack.Pack, error) 22 } 23 24 func max(p1, p2 pack.Pack) pack.Pack { 25 if p1.Version().LessThan(p2.Version()) { 26 return p2 27 } else { 28 return p1 29 } 30 } 31 32 func Hash(pack pack.Pack) string { 33 return fmt.Sprintf("%v@%v", pack.ImportPath(), pack.Version()) 34 } 35 36 type Job struct { 37 Trace []errors.Location 38 Pack pack.Pack 39 } 40 41 func (j Job) Payload() interface{} { 42 return j.Pack 43 } 44 45 func (j Job) ID() interface{} { 46 return Hash(j.Pack) 47 } 48 49 // List creates a list of requirements based on initial list of required packs, taking inter-pack requirements into account. 50 func List(required []pack.Pack, reqs Reqs) ([]pack.Pack, error) { 51 mtx := sync.Mutex{} 52 latest := make(map[types.ImportPath]pack.Pack) 53 queue := internal.Work{} 54 for _, r := range required { 55 queue.Add(Job{nil, r}) 56 } 57 var firstErr error 58 queue.Do(10, 59 func(j internal.Job) { 60 if firstErr != nil { 61 return 62 } 63 job, ok := j.(Job) 64 if !ok { 65 panic("Internal error: expected pack.Pack passed to work queue") 66 } 67 crnt := job.Pack 68 trace := duplicate(job.Trace) 69 mtx.Lock() 70 if l, ok := latest[crnt.ImportPath()]; !ok || Hash(max(l, crnt)) != Hash(l) { 71 latest[crnt.ImportPath()] = crnt 72 } 73 mtx.Unlock() 74 75 reqs, err := reqs.Reqs(crnt) 76 if err != nil { 77 mtx.Lock() 78 firstErr = errors.Wrap( 79 err, 80 ErrResolvingReqs{}, 81 trace..., 82 ) 83 mtx.Unlock() 84 return 85 } 86 87 for _, req := range reqs { 88 queue.Add(Job{append(trace, toLocation(crnt)), req}) 89 } 90 }) 91 92 if firstErr != nil { 93 return nil, firstErr 94 } 95 96 packs := make([]pack.Pack, 0) 97 for _, pack := range latest { 98 packs = append(packs, pack) 99 } 100 return packs, nil 101 } 102 103 func duplicate(trace []errors.Location) []errors.Location { 104 dup := make([]errors.Location, len(trace)) 105 for i, t := range trace { 106 dup[i] = t 107 } 108 return dup 109 } 110 111 func toLocation(pack pack.Pack) errors.Location { 112 importPath := pack.ImportPath() 113 return errors.Location{ 114 Name: importPath.String(), 115 VerboseName: fmt.Sprintf("required by %v", importPath), 116 } 117 }