github.com/tiagovtristao/plz@v13.4.0+incompatible/src/core/graph.go (about) 1 // Representation of the build graph. 2 // The graph of build targets forms a DAG which we discover from the top 3 // down and then build bottom-up. 4 5 package core 6 7 import ( 8 "sort" 9 "sync" 10 ) 11 12 // A BuildGraph contains all the loaded targets and packages and maintains their 13 // relationships, especially reverse dependencies which are calculated here. 14 // It also arbitrates access to a lot of things via its builtin mutex which 15 // is probably our most overused lock :( 16 type BuildGraph struct { 17 // Map of all currently known targets by their label. 18 targets map[BuildLabel]*BuildTarget 19 // Map of all currently known packages. 20 packages map[packageKey]*Package 21 // Reverse dependencies that are pending on targets actually being added to the graph. 22 pendingRevDeps map[BuildLabel]map[BuildLabel]*BuildTarget 23 // Actual reverse dependencies 24 revDeps map[BuildLabel][]*BuildTarget 25 // Registered subrepos, as a map of their name to their root. 26 subrepos map[string]*Subrepo 27 // Used to arbitrate access to the graph. We parallelise most build operations 28 // and Go maps aren't natively threadsafe so this is needed. 29 mutex sync.RWMutex 30 } 31 32 // AddTarget adds a new target to the graph. 33 func (graph *BuildGraph) AddTarget(target *BuildTarget) *BuildTarget { 34 graph.mutex.Lock() 35 defer graph.mutex.Unlock() 36 if _, present := graph.targets[target.Label]; present { 37 panic("Attempted to re-add existing target to build graph: " + target.Label.String()) 38 } 39 graph.targets[target.Label] = target 40 // Check these reverse deps which may have already been added against this target. 41 revdeps, present := graph.pendingRevDeps[target.Label] 42 if present { 43 for revdep, originalTarget := range revdeps { 44 if originalTarget != nil { 45 graph.linkDependencies(graph.targets[revdep], originalTarget) 46 } else { 47 graph.linkDependencies(graph.targets[revdep], target) 48 } 49 } 50 delete(graph.pendingRevDeps, target.Label) // Don't need any more 51 } 52 return target 53 } 54 55 // AddPackage adds a new package to the graph with given name. 56 func (graph *BuildGraph) AddPackage(pkg *Package) { 57 key := packageKey{Name: pkg.Name, Subrepo: pkg.SubrepoName} 58 graph.mutex.Lock() 59 defer graph.mutex.Unlock() 60 if _, present := graph.packages[key]; present { 61 panic("Attempt to readd existing package: " + key.String()) 62 } 63 graph.packages[key] = pkg 64 } 65 66 // Target retrieves a target from the graph by label 67 func (graph *BuildGraph) Target(label BuildLabel) *BuildTarget { 68 graph.mutex.RLock() 69 defer graph.mutex.RUnlock() 70 return graph.targets[label] 71 } 72 73 // TargetOrDie retrieves a target from the graph by label. Dies if the target doesn't exist. 74 func (graph *BuildGraph) TargetOrDie(label BuildLabel) *BuildTarget { 75 target := graph.Target(label) 76 if target == nil { 77 log.Fatalf("Target %s not found in build graph\n", label) 78 } 79 return target 80 } 81 82 // PackageByLabel retrieves a package from the graph using the appropriate parts of the given label. 83 // The Name entry is ignored. 84 func (graph *BuildGraph) PackageByLabel(label BuildLabel) *Package { 85 return graph.Package(label.PackageName, label.Subrepo) 86 } 87 88 // Package retrieves a package from the graph by name & subrepo, or nil if it can't be found. 89 func (graph *BuildGraph) Package(name, subrepo string) *Package { 90 graph.mutex.RLock() 91 defer graph.mutex.RUnlock() 92 return graph.packages[packageKey{Name: name, Subrepo: subrepo}] 93 } 94 95 // PackageOrDie retrieves a package by label, and dies if it can't be found. 96 func (graph *BuildGraph) PackageOrDie(label BuildLabel) *Package { 97 pkg := graph.PackageByLabel(label) 98 if pkg == nil { 99 log.Fatalf("Package %s doesn't exist in graph", packageKey{Name: label.PackageName, Subrepo: label.Subrepo}) 100 } 101 return pkg 102 } 103 104 // AddSubrepo adds a new subrepo to the graph. It dies if one is already registered by this name. 105 func (graph *BuildGraph) AddSubrepo(subrepo *Subrepo) { 106 graph.mutex.Lock() 107 defer graph.mutex.Unlock() 108 if _, present := graph.subrepos[subrepo.Name]; present { 109 log.Fatalf("Subrepo %s is already registered", subrepo.Name) 110 } 111 graph.subrepos[subrepo.Name] = subrepo 112 } 113 114 // MaybeAddSubrepo adds the given subrepo to the graph, or returns the existing one if one is already registered. 115 func (graph *BuildGraph) MaybeAddSubrepo(subrepo *Subrepo) *Subrepo { 116 graph.mutex.Lock() 117 defer graph.mutex.Unlock() 118 if s, present := graph.subrepos[subrepo.Name]; present { 119 return s 120 } 121 graph.subrepos[subrepo.Name] = subrepo 122 return subrepo 123 } 124 125 // Subrepo returns the subrepo with this name. It returns nil if one isn't registered. 126 func (graph *BuildGraph) Subrepo(name string) *Subrepo { 127 graph.mutex.RLock() 128 defer graph.mutex.RUnlock() 129 return graph.subrepos[name] 130 } 131 132 // SubrepoOrDie returns the subrepo with this name, dying if it doesn't exist. 133 func (graph *BuildGraph) SubrepoOrDie(name string) *Subrepo { 134 subrepo := graph.Subrepo(name) 135 if subrepo == nil { 136 log.Fatalf("No registered subrepo by the name %s", name) 137 } 138 return subrepo 139 } 140 141 // Len returns the number of targets currently in the graph. 142 func (graph *BuildGraph) Len() int { 143 graph.mutex.RLock() 144 defer graph.mutex.RUnlock() 145 return len(graph.targets) 146 } 147 148 // AllTargets returns a consistently ordered slice of all the targets in the graph. 149 func (graph *BuildGraph) AllTargets() BuildTargets { 150 graph.mutex.RLock() 151 defer graph.mutex.RUnlock() 152 targets := make(BuildTargets, 0, len(graph.targets)) 153 for _, target := range graph.targets { 154 targets = append(targets, target) 155 } 156 sort.Sort(targets) 157 return targets 158 } 159 160 // PackageMap returns a copy of the graph's internal map of name to package. 161 func (graph *BuildGraph) PackageMap() map[string]*Package { 162 graph.mutex.RLock() 163 defer graph.mutex.RUnlock() 164 packages := make(map[string]*Package, len(graph.packages)) 165 for k, v := range graph.packages { 166 packages[k.String()] = v 167 } 168 return packages 169 } 170 171 // AddDependency adds a dependency between two build targets. 172 // The 'to' target doesn't necessarily have to exist in the graph yet (but 'from' must). 173 func (graph *BuildGraph) AddDependency(from BuildLabel, to BuildLabel) { 174 graph.mutex.Lock() 175 defer graph.mutex.Unlock() 176 fromTarget := graph.targets[from] 177 // We might have done this already; do a quick check here first. 178 if fromTarget.hasResolvedDependency(to) { 179 return 180 } 181 toTarget, present := graph.targets[to] 182 // The dependency may not exist yet if we haven't parsed its package. 183 // In that case we stash it away for later. 184 if !present { 185 graph.addPendingRevDep(from, to, nil) 186 } else { 187 graph.linkDependencies(fromTarget, toTarget) 188 } 189 } 190 191 // NewGraph constructs and returns a new BuildGraph. 192 // Users should not attempt to construct one themselves. 193 func NewGraph() *BuildGraph { 194 return &BuildGraph{ 195 targets: map[BuildLabel]*BuildTarget{}, 196 packages: map[packageKey]*Package{}, 197 pendingRevDeps: map[BuildLabel]map[BuildLabel]*BuildTarget{}, 198 revDeps: map[BuildLabel][]*BuildTarget{}, 199 subrepos: map[string]*Subrepo{}, 200 } 201 } 202 203 // ReverseDependencies returns the set of revdeps on the given target. 204 func (graph *BuildGraph) ReverseDependencies(target *BuildTarget) []*BuildTarget { 205 graph.mutex.RLock() 206 defer graph.mutex.RUnlock() 207 if revdeps, present := graph.revDeps[target.Label]; present { 208 return revdeps[:] 209 } 210 return []*BuildTarget{} 211 } 212 213 // AllDepsBuilt returns true if all the dependencies of a target are built. 214 func (graph *BuildGraph) AllDepsBuilt(target *BuildTarget) bool { 215 graph.mutex.RLock() 216 defer graph.mutex.RUnlock() 217 return target.allDepsBuilt() 218 } 219 220 // AllDependenciesResolved returns true once all the dependencies of a target have been 221 // parsed and resolved to real targets. 222 func (graph *BuildGraph) AllDependenciesResolved(target *BuildTarget) bool { 223 graph.mutex.RLock() 224 defer graph.mutex.RUnlock() 225 return target.allDependenciesResolved() 226 } 227 228 // linkDependencies adds the dependency of fromTarget on toTarget and the corresponding 229 // reverse dependency in the other direction. 230 // This is complicated somewhat by the require/provide mechanism which is resolved at this 231 // point, but some of the dependencies may not yet exist. 232 func (graph *BuildGraph) linkDependencies(fromTarget, toTarget *BuildTarget) { 233 for _, label := range toTarget.ProvideFor(fromTarget) { 234 target, present := graph.targets[label] 235 if present { 236 fromTarget.resolveDependency(toTarget.Label, target) 237 graph.revDeps[label] = append(graph.revDeps[label], fromTarget) 238 } else { 239 graph.addPendingRevDep(fromTarget.Label, label, toTarget) 240 } 241 } 242 } 243 244 func (graph *BuildGraph) addPendingRevDep(from, to BuildLabel, orig *BuildTarget) { 245 if deps, present := graph.pendingRevDeps[to]; present { 246 deps[from] = orig 247 } else { 248 graph.pendingRevDeps[to] = map[BuildLabel]*BuildTarget{from: orig} 249 } 250 } 251 252 // DependentTargets returns the labels that 'from' should actually depend on when it declared a dependency on 'to'. 253 // This is normally just 'to' but could be otherwise given require/provide shenanigans. 254 func (graph *BuildGraph) DependentTargets(from, to BuildLabel) []BuildLabel { 255 fromTarget := graph.Target(from) 256 if toTarget := graph.Target(to); fromTarget != nil && toTarget != nil { 257 graph.mutex.Lock() 258 defer graph.mutex.Unlock() 259 return toTarget.ProvideFor(fromTarget) 260 } 261 return []BuildLabel{to} 262 }