github.com/sdboyer/gps@v0.16.3/rootdata.go (about) 1 package gps 2 3 import ( 4 "sort" 5 6 "github.com/armon/go-radix" 7 "github.com/sdboyer/gps/internal" 8 "github.com/sdboyer/gps/pkgtree" 9 ) 10 11 // rootdata holds static data and constraining rules from the root project for 12 // use in solving. 13 type rootdata struct { 14 // Path to the root of the project on which gps is operating. 15 dir string 16 17 // Map of packages to ignore. 18 ig map[string]bool 19 20 // Map of packages to require. 21 req map[string]bool 22 23 // A ProjectConstraints map containing the validated (guaranteed non-empty) 24 // overrides declared by the root manifest. 25 ovr ProjectConstraints 26 27 // A map of the ProjectRoot (local names) that should be allowed to change 28 chng map[ProjectRoot]struct{} 29 30 // Flag indicating all projects should be allowed to change, without regard 31 // for lock. 32 chngall bool 33 34 // A map of the project names listed in the root's lock. 35 rlm map[ProjectRoot]LockedProject 36 37 // A defensively copied instance of the root manifest. 38 rm SimpleManifest 39 40 // A defensively copied instance of the root lock. 41 rl safeLock 42 43 // A defensively copied instance of params.RootPackageTree 44 rpt pkgtree.PackageTree 45 46 // The ProjectAnalyzer to use for all GetManifestAndLock calls. 47 an ProjectAnalyzer 48 } 49 50 // externalImportList returns a list of the unique imports from the root data. 51 // Ignores and requires are taken into consideration, stdlib is excluded, and 52 // errors within the local set of package are not backpropagated. 53 func (rd rootdata) externalImportList() []string { 54 rm, _ := rd.rpt.ToReachMap(true, true, false, rd.ig) 55 all := rm.Flatten(false) 56 reach := make([]string, 0, len(all)) 57 for _, r := range all { 58 if !internal.IsStdLib(r) { 59 reach = append(reach, r) 60 } 61 } 62 63 // If there are any requires, slide them into the reach list, as well. 64 if len(rd.req) > 0 { 65 // Make a map of imports that are both in the import path list and the 66 // required list to avoid duplication. 67 skip := make(map[string]bool, len(rd.req)) 68 for _, r := range reach { 69 if rd.req[r] { 70 skip[r] = true 71 } 72 } 73 74 for r := range rd.req { 75 if !skip[r] { 76 reach = append(reach, r) 77 } 78 } 79 } 80 81 sort.Strings(reach) 82 return reach 83 } 84 85 func (rd rootdata) getApplicableConstraints() []workingConstraint { 86 // Merge the normal and test constraints together 87 pc := rd.rm.DependencyConstraints().merge(rd.rm.TestDependencyConstraints()) 88 89 // Ensure that overrides which aren't in the combined pc map already make it 90 // in. Doing so makes input hashes equal in more useful cases. 91 for pr, pp := range rd.ovr { 92 if _, has := pc[pr]; !has { 93 cpp := ProjectProperties{ 94 Constraint: pp.Constraint, 95 Source: pp.Source, 96 } 97 if cpp.Constraint == nil { 98 cpp.Constraint = anyConstraint{} 99 } 100 101 pc[pr] = cpp 102 } 103 } 104 105 // Now override them all to produce a consolidated workingConstraint slice 106 combined := rd.ovr.overrideAll(pc) 107 108 type wccount struct { 109 count int 110 wc workingConstraint 111 } 112 xt := radix.New() 113 for _, wc := range combined { 114 xt.Insert(string(wc.Ident.ProjectRoot), wccount{wc: wc}) 115 } 116 117 // Walk all dep import paths we have to consider and mark the corresponding 118 // wc entry in the trie, if any 119 for _, im := range rd.externalImportList() { 120 if internal.IsStdLib(im) { 121 continue 122 } 123 124 if pre, v, match := xt.LongestPrefix(im); match && isPathPrefixOrEqual(pre, im) { 125 wcc := v.(wccount) 126 wcc.count++ 127 xt.Insert(pre, wcc) 128 } 129 } 130 131 var ret []workingConstraint 132 133 xt.Walk(func(s string, v interface{}) bool { 134 wcc := v.(wccount) 135 if wcc.count > 0 { 136 ret = append(ret, wcc.wc) 137 } 138 return false 139 }) 140 141 return ret 142 } 143 144 func (rd rootdata) combineConstraints() []workingConstraint { 145 return rd.ovr.overrideAll(rd.rm.DependencyConstraints().merge(rd.rm.TestDependencyConstraints())) 146 } 147 148 // needVersionListFor indicates whether we need a version list for a given 149 // project root, based solely on general solver inputs (no constraint checking 150 // required). Assuming the argument is not the root project itself, this will be 151 // true if any of the following conditions hold: 152 // 153 // - ChangeAll is on 154 // - The project is not in the lock 155 // - The project is in the lock, but is also in the list of projects to change 156 func (rd rootdata) needVersionsFor(pr ProjectRoot) bool { 157 if rd.isRoot(pr) { 158 return false 159 } 160 161 if rd.chngall { 162 return true 163 } 164 165 if _, has := rd.rlm[pr]; !has { 166 // not in the lock 167 return true 168 } 169 170 if _, has := rd.chng[pr]; has { 171 // in the lock, but marked for change 172 return true 173 } 174 // in the lock, not marked for change 175 return false 176 177 } 178 179 func (rd rootdata) isRoot(pr ProjectRoot) bool { 180 return pr == ProjectRoot(rd.rpt.ImportRoot) 181 } 182 183 // rootAtom creates an atomWithPackages that represents the root project. 184 func (rd rootdata) rootAtom() atomWithPackages { 185 a := atom{ 186 id: ProjectIdentifier{ 187 ProjectRoot: ProjectRoot(rd.rpt.ImportRoot), 188 }, 189 // This is a hack so that the root project doesn't have a nil version. 190 // It's sort of OK because the root never makes it out into the results. 191 // We may need a more elegant solution if we discover other side 192 // effects, though. 193 v: rootRev, 194 } 195 196 list := make([]string, 0, len(rd.rpt.Packages)) 197 for path, pkg := range rd.rpt.Packages { 198 if pkg.Err != nil && !rd.ig[path] { 199 list = append(list, path) 200 } 201 } 202 sort.Strings(list) 203 204 return atomWithPackages{ 205 a: a, 206 pl: list, 207 } 208 }