in newt/deprepo/deprepo.go [212:350]
func ResolveRepoDeps(dg DepGraph) (VersionMap, []Conflict) {
// Represents an entry in the working set.
type WSNode struct {
highPrio bool // If true, always "wins" without conflict.
dependent RVPair // Repo that expresses this dependency.
dependee RVPair // Repo that is depended on.
}
ws := map[string]WSNode{} // Working set (key=dependent).
cm := map[string]*Conflict{} // Conflict map (key=dependee).
visited := map[string]struct{}{} // Tracks which nodes have been visited.
// Updates (or inserts a new) conflict object into the conflict map (cm).
addConflict := func(dependent RVPair, dependee RVPair) {
c := cm[dependee.Name]
if c == nil {
wsn := ws[dependee.Name]
c = &Conflict{
DependeeName: repoNameString(dependee.Name),
Entries: []ConflictEntry{
ConflictEntry{
Dependent: RVPair{
Name: repoNameString(wsn.dependent.Name),
Ver: wsn.dependent.Ver,
},
DependeeVer: wsn.dependee.Ver,
},
},
}
cm[dependee.Name] = c
}
c.Entries = append(c.Entries, ConflictEntry{
Dependent: dependent,
DependeeVer: dependee.Ver,
})
}
// Attempts to add a single node to the working set. In case of a
// conflict, the conflict map is updated instead.
addWsNode := func(dependent RVPair, dependee RVPair) {
old, ok := ws[dependee.Name]
if !ok {
// This is the first time we've seen this repo.
ws[dependee.Name] = WSNode{
highPrio: false,
dependent: dependent,
dependee: dependee,
}
return
}
if newtutil.CompareRepoVersions(old.dependee.Ver, dependee.Ver) == 0 {
// We have already seen this exact dependency. Ignore the
// duplicate.
return
}
// There is already a dependency for a different version of this repo.
// Handle the conflict.
if old.highPrio {
// Root commit dependencies take priority over all others.
log.Debugf("discarding repo dependency in favor "+
"of root override: dep=%s->%s root=%s->%s",
dependent.String(), dependee.String(),
old.dependent.String(), old.dependee.String())
} else {
addConflict(dependent, dependee)
}
}
// Adds one entry to the working set (ws) for each of the given repo's
// dependencies.
visit := func(rvp RVPair) {
nodes := dg[rvp]
for _, node := range nodes {
addWsNode(rvp, node)
}
visited[rvp.Name] = struct{}{}
}
// Insert all the root dependencies (dependencies in `project.yml`) into
// the working set. A root dependency is "high priority" if it points to a
// git commit rather than a version number.
rootDeps := dg[RVPair{}]
for _, dep := range rootDeps {
ws[dep.Name] = WSNode{
highPrio: dep.Ver.Commit != "",
dependent: RVPair{Name: rootDependencyName},
dependee: dep,
}
}
// Repeatedly iterate through the working set, visiting each unvisited
// node. Stop looping when an iteration produces no new nodes.
for len(visited) < len(ws) {
// Iterate the working set in a consistent order. The order doesn't
// matter for well-defined projects. For invalid projects, different
// iteration orders can result in different conflicts being reported.
names := make([]string, 0, len(ws))
for name, _ := range ws {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
wsn := ws[name]
if _, ok := visited[name]; !ok {
visit(wsn.dependee)
}
}
}
// It is an error if any conflicts were detected.
if len(cm) > 0 {
var cs []Conflict
for _, c := range cm {
c.SortEntries()
cs = append(cs, *c)
}
sort.Slice(cs, func(i int, j int) bool {
return strings.Compare(cs[i].DependeeName, cs[j].DependeeName) < 0
})
return nil, cs
}
// The working set now contains the target version of each repo in the
// project.
vm := VersionMap{}
for name, wsn := range ws {
vm[name] = wsn.dependee.Ver
}
return vm, nil
}