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  }