blob: 557b2cecedf91117c6aa3f2620b5c8413f1b9239 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// deprepo: Package for resolving repo dependencies.
//
// Vocabulary:
// * Dependee: A repo that is depended on.
// * Dependent: A repo that depends on others.
package deprepo
import (
"fmt"
"sort"
"strings"
"mynewt.apache.org/newt/newt/newtutil"
"mynewt.apache.org/newt/util"
)
// Describes a repo that depends on other repos.
type Dependent struct {
Name string
Ver newtutil.RepoVersion
}
const rootDependencyName = ""
const rootRepoName = "project.yml"
// Represents a top-level repo dependency (i.e., a repo specified in
// `project.yml`).
var rootDependent = Dependent{Name: rootDependencyName}
// A single node in a repo dependency graph.
type DepGraphNode struct {
// Name of depended-on repo.
Name string
// Expresses the versions of the repo that satisfy this dependency.
VerReqs []newtutil.RepoVersionReq
}
// A repo dependency graph.
// Key: A repo with dependencies.
// Value: The corresponding list of dependencies.
type DepGraph map[Dependent][]DepGraphNode
// A single node in a repo reverse dependency graph.
type RevdepGraphNode struct {
// The name of the dependent repo.
Name string
// The version of the dependent repo.
Ver newtutil.RepoVersion
// The dependent's version requirements that apply to the graph key.
VerReqs []newtutil.RepoVersionReq
}
// A repo reverse dependency graph.
// Key: A depended-on repo.
// Value: The corresponding list of dependencies.
type RevdepGraph map[string][]RevdepGraphNode
func repoNameVerString(repoName string, ver newtutil.RepoVersion) string {
if repoName == rootDependencyName {
return rootRepoName
} else {
return fmt.Sprintf("%s-%s", repoName, ver.String())
}
}
func (dep *Dependent) String() string {
return repoNameVerString(dep.Name, dep.Ver)
}
func (dgn *DepGraphNode) String() string {
return fmt.Sprintf("%s,%s", dgn.Name,
newtutil.RepoVerReqsString(dgn.VerReqs))
}
func (dg DepGraph) String() string {
lines := make([]string, 0, len(dg))
for dependent, nodes := range dg {
line := fmt.Sprintf("%s:", dependent.String())
for _, node := range nodes {
line += fmt.Sprintf(" (%s)", node.String())
}
lines = append(lines, line)
}
sort.Strings(lines)
return strings.Join(lines, "\n")
}
func (rgn *RevdepGraphNode) String() string {
return fmt.Sprintf("%s,%s", repoNameVerString(rgn.Name, rgn.Ver),
newtutil.RepoVerReqsString(rgn.VerReqs))
}
func (rg RevdepGraph) String() string {
lines := make([]string, 0, len(rg))
for repoName, nodes := range rg {
line := fmt.Sprintf("%s:", repoName)
for _, node := range nodes {
line += fmt.Sprintf(" (%s)", node.String())
}
lines = append(lines, line)
}
sort.Strings(lines)
return strings.Join(lines, "\n")
}
// Adds all dependencies expressed by a single version of a repo.
//
// @param repoName The name of the dependent repo.
// @param repoVer The version of the dependent repo.
// @param reqMap The dependency requirements of the specified
// repo version.
func (dg DepGraph) AddRepoVer(repoName string, repoVer newtutil.RepoVersion,
reqMap RequirementMap) error {
dep := Dependent{
Name: repoName,
Ver: repoVer,
}
if _, ok := dg[dep]; ok {
return util.FmtNewtError(
"Duplicate repo-version-pair in repo dependency graph: %s,%s",
repoName, repoVer.String())
}
for depName, depReqs := range reqMap {
dg[dep] = append(dg[dep], DepGraphNode{
Name: depName,
VerReqs: depReqs,
})
}
return nil
}
// Adds a root dependency (i.e., required repo specified in `project.yml`).
func (dg DepGraph) AddRootDep(repoName string,
verReqs []newtutil.RepoVersionReq) error {
rootDeps := dg[rootDependent]
for _, d := range rootDeps {
if d.Name == repoName {
return util.FmtNewtError(
"Duplicate root dependency repo dependency graph: %s",
repoName)
}
}
dg[rootDependent] = append(dg[rootDependent], DepGraphNode{
Name: repoName,
VerReqs: verReqs,
})
return nil
}
// Reverses a dependency graph, forming a reverse dependency graph.
//
// A normal dependency graph expresses the following relationship:
// [dependent] => depended-on
//
// A reverse dependency graph expresses the following relationship:
// [depended-on] => dependent
func (dg DepGraph) Reverse() RevdepGraph {
rg := RevdepGraph{}
for dependent, nodes := range dg {
for _, node := range nodes {
// Nothing depends on project.yml (""), so exclude it from the result.
if node.Name != "" {
rg[node.Name] = append(rg[node.Name], RevdepGraphNode{
Name: dependent.Name,
Ver: dependent.Ver,
VerReqs: node.VerReqs,
})
}
}
}
return rg
}
// Identifies repos which cannot satisfy all their dependents. For example, if
// `project.yml` requires X1 and Y2, but Y2 requires X2, then X is a
// conflicting repo (no overlap in requirement sets).
//
// Returns the names of all repos that cannot be included in the build. For
// example, if:
// * X depends on Z 1.0.0
// * Y depends on Z 2.0.0
// then Z is included in the returned slice because it can't satisfy all its
// dependents. X and Y are *not* included (unless they also have depedents
// that cannot be satisfied).
func (dg DepGraph) conflictingRepos(vm VersionMap) []string {
badRepoMap := map[string]struct{}{}
// Create a reverse dependency graph.
rg := dg.Reverse()
for dependeeName, nodes := range rg {
dependeeVer, ok := vm[dependeeName]
if !ok {
// This version was pruned from the matrix. It is unusable.
badRepoMap[dependeeName] = struct{}{}
continue
}
// Dependee is the repo being depended on. We want to determine if it
// can be included in the project without violating any dependency
// requirements.
//
// For each repo that depends on dependee (i.e., for each dependent):
// 1. Determine which of the dependent's requirements apply to the
// dependee (e.g., if we are evaluating v2 of the dependent, then
// v1's requirements do not apply).
// 2. Check if the dependee satisfies all applicable requirements.
for _, node := range nodes {
var nodeApplies bool
if node.Name == rootDependencyName {
// project.yml requirements are always applicable.
nodeApplies = true
} else {
// Repo dependency requirements only apply if they are
// associated with the version of the dependent that we are
// evaluating.
dependentVer, ok := vm[node.Name]
if ok {
nodeApplies = newtutil.CompareRepoVersions(
dependentVer, node.Ver) == 0
}
}
if nodeApplies {
if !dependeeVer.SatisfiesAll(node.VerReqs) {
badRepoMap[dependeeName] = struct{}{}
break
}
}
}
}
badRepoSlice := make([]string, 0, len(badRepoMap))
for repoName, _ := range badRepoMap {
badRepoSlice = append(badRepoSlice, repoName)
}
sort.Strings(badRepoSlice)
return badRepoSlice
}