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