sterlinghughes closed pull request #140: newt - Repo dependencies
URL: https://github.com/apache/mynewt-newt/pull/140
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/newt/cli/project_cmds.go b/newt/cli/project_cmds.go
index 3ec2f2f0..c11933b3 100644
--- a/newt/cli/project_cmds.go
+++ b/newt/cli/project_cmds.go
@@ -81,8 +81,11 @@ func newRunCmd(cmd *cobra.Command, args []string) {
// are specified, the resulting function selects all non-local repos.
// Otherwise, the function selects each non-local repo whose name is specified.
func makeRepoPredicate(repoNames []string) func(r *repo.Repo) bool {
+ // If the user didn't specify any repo names, apply the operation to all
+ // repos in `project.yml`.
if len(repoNames) == 0 {
- return func(r *repo.Repo) bool { return !r.IsLocal() }
+ proj := project.GetProject()
+ return func(r *repo.Repo) bool { return
proj.RepoIsRoot(r.Name()) }
}
return func(r *repo.Repo) bool {
@@ -102,7 +105,9 @@ func installRunCmd(cmd *cobra.Command, args []string) {
interfaces.SetProject(proj)
pred := makeRepoPredicate(args)
- if err := proj.InstallIf(false, newtutil.NewtForce, pred); err != nil {
+ if err := proj.InstallIf(
+ false, newtutil.NewtForce, newtutil.NewtAsk, pred); err != nil {
+
NewtUsage(nil, err)
}
}
@@ -112,7 +117,9 @@ func upgradeRunCmd(cmd *cobra.Command, args []string) {
interfaces.SetProject(proj)
pred := makeRepoPredicate(args)
- if err := proj.InstallIf(true, newtutil.NewtForce, pred); err != nil {
+ if err := proj.InstallIf(
+ true, newtutil.NewtForce, newtutil.NewtAsk, pred); err != nil {
+
NewtUsage(nil, err)
}
}
@@ -178,35 +185,10 @@ func infoRunCmd(cmd *cobra.Command, args []string) {
func syncRunCmd(cmd *cobra.Command, args []string) {
proj := TryGetProject()
pred := makeRepoPredicate(args)
- repos := proj.SelectRepos(pred)
- ps, err := project.LoadProjectState()
- if err != nil {
- NewtUsage(nil, err)
- }
+ if err := proj.SyncIf(
+ newtutil.NewtForce, newtutil.NewtAsk, pred); err != nil {
- var anyFails bool
- for _, repo := range repos {
- vers := ps.GetInstalledVersion(repo.Name())
- if vers == nil {
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "No installed version of %s found, skipping\n",
- repo.Name())
- } else {
- if _, err := repo.Sync(vers, newtutil.NewtForce); err
!= nil {
- util.StatusMessage(util.VERBOSITY_QUIET,
- "Failed to sync repo \"%s\": %s\n",
- repo.Name(), err.Error())
- anyFails = true
- }
- }
- }
- if anyFails {
- var forceMsg string
- if !newtutil.NewtForce {
- forceMsg = ". To force resync, add the -f (force)
option."
- }
- err := util.FmtNewtError("Failed to sync%s", forceMsg)
NewtUsage(nil, err)
}
}
@@ -228,6 +210,8 @@ func AddProjectCommands(cmd *cobra.Command) {
"force", "f", false,
"Force install of the repositories in project, regardless of
what "+
"exists in repos directory")
+ installCmd.PersistentFlags().BoolVarP(&newtutil.NewtAsk,
+ "ask", "a", false, "Prompt user before installing any repos")
cmd.AddCommand(installCmd)
@@ -246,6 +230,8 @@ func AddProjectCommands(cmd *cobra.Command) {
upgradeCmd.PersistentFlags().BoolVarP(&newtutil.NewtForce,
"force", "f", false,
"Force upgrade of the repositories to latest state in
project.yml")
+ upgradeCmd.PersistentFlags().BoolVarP(&newtutil.NewtAsk,
+ "ask", "a", false, "Prompt user before upgrading any repos")
cmd.AddCommand(upgradeCmd)
@@ -264,6 +250,8 @@ func AddProjectCommands(cmd *cobra.Command) {
syncCmd.PersistentFlags().BoolVarP(&newtutil.NewtForce,
"force", "f", false,
"Force overwrite of existing remote repositories.")
+ syncCmd.PersistentFlags().BoolVarP(&newtutil.NewtAsk,
+ "ask", "a", false, "Prompt user before syncing any repos")
cmd.AddCommand(syncCmd)
newHelpText := ""
diff --git a/newt/deprepo/deprepo.go b/newt/deprepo/deprepo.go
new file mode 100644
index 00000000..7299c373
--- /dev/null
+++ b/newt/deprepo/deprepo.go
@@ -0,0 +1,324 @@
+// deprepo: Package for resolving repo dependencies.
+package deprepo
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "mynewt.apache.org/newt/newt/newtutil"
+ "mynewt.apache.org/newt/newt/repo"
+ "mynewt.apache.org/newt/util"
+)
+
+// [repo-name] => repo
+type RepoMap map[string]*repo.Repo
+
+// [repo-name] => repo-version
+type VersionMap map[string]newtutil.RepoVersion
+
+// [repo-name] => requirements-for-key-repo
+type RequirementMap map[string][]newtutil.RepoVersionReq
+
+// Indicates an inability to find an acceptable version of a particular repo.
+type Conflict struct {
+ RepoName string
+ Filters []Filter
+}
+
+// Returns a slice of all constituent repos, sorted by name.
+func (rm RepoMap) Sorted() []*repo.Repo {
+ names := make([]string, 0, len(rm))
+ for n, _ := range rm {
+ names = append(names, n)
+ }
+ sort.Strings(names)
+
+ repos := make([]*repo.Repo, len(names))
+ for i, n := range names {
+ repos[i] = rm[n]
+ }
+
+ return repos
+}
+
+func (vm VersionMap) String() string {
+ s := ""
+
+ for repoName, ver := range vm {
+ if len(s) > 0 {
+ s += "\n"
+ }
+ s += fmt.Sprintf("%s:%s", repoName, ver.String())
+ }
+ return s
+}
+
+// Constructs a version matrix from the specified repos. Each row in the
+// resulting matrix corresponds to a repo in the supplied slice. Each row node
+// represents a single version of the repo.
+func BuildMatrix(repos []*repo.Repo, vm VersionMap) (Matrix, error) {
+ m := Matrix{}
+
+ for _, r := range repos {
+ if !r.IsLocal() {
+ vers, err := r.NormalizedVersions()
+ if err != nil {
+ return m, err
+ }
+ if err := m.AddRow(r.Name(), vers); err != nil {
+ return m, err
+ }
+ }
+ }
+
+ return m, nil
+}
+
+// Builds a repo dependency graph from the repo requirements expressed in the
+// `project.yml` file.
+func BuildDepGraph(repos RepoMap, rootReqs RequirementMap) (DepGraph, error) {
+ dg := DepGraph{}
+
+ // First, add the hard dependencies expressed in `project.yml`.
+ for repoName, verReqs := range rootReqs {
+ repo := repos[repoName]
+ normalizedReqs, err := repo.NormalizeVerReqs(verReqs)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := dg.AddRootDep(repoName, normalizedReqs); err != nil {
+ return nil, err
+ }
+ }
+
+ // Add inter-repo dependencies to the graph.
+ for _, r := range repos.Sorted() {
+ nvers, err := r.NormalizedVersions()
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range nvers {
+ deps := r.DepsForVersion(v)
+ reqMap := RequirementMap{}
+ for _, d := range deps {
+ depRepo := repos[d.Name]
+ verReqs, err :=
depRepo.NormalizeVerReqs(d.VerReqs)
+ if err != nil {
+ return nil, err
+ }
+ reqMap[d.Name] = verReqs
+ }
+ if err := dg.AddRepoVer(r.Name(), v, reqMap); err !=
nil {
+ return nil, err
+ }
+ }
+ }
+
+ return dg, nil
+}
+
+// Prunes unusable repo versions from the specified matrix.
+func PruneMatrix(m *Matrix, repos RepoMap, rootReqs RequirementMap) error {
+ pruned := map[*repo.RepoDependency]struct{}{}
+
+ // Removes versions of the depended-on package that fail to satisfy the
+ // dependent's requirements. Each of the depended-on package's
+ // dependencies are then recursively visited.
+ var recurse func(dependentName string, dep *repo.RepoDependency) error
+ recurse = func(dependentName string, dep *repo.RepoDependency) error {
+ // Don't prune the same dependency twice; prevents infinite
+ // recursion.
+ if _, ok := pruned[dep]; ok {
+ return nil
+ }
+ pruned[dep] = struct{}{}
+
+ // Remove versions of this depended-on package that don't
satisfy the
+ // dependency's version requirements.
+ r := repos[dep.Name]
+ normalizedReqs, err := r.NormalizeVerReqs(dep.VerReqs)
+ if err != nil {
+ return err
+ }
+ filter := Filter{
+ Name: dependentName,
+ Reqs: normalizedReqs,
+ }
+ m.ApplyFilter(dep.Name, filter)
+
+ // If there is only one version of the depended-on package left
in the
+ // matrix, we can recursively call this function on all its
+ // dependencies.
+ //
+ // We don't do it, but it is possible to prune when there is
more than
+ // one version remaining. To accomplish this, we would collect
the
+ // requirements from all versions, find their union, and remove
+ // depended-on packages that satisfy none of the requirements
in the
+ // union. The actual implementation (only prune when one
version
+ // remains) is a simplified implementation of this general
procedure.
+ // In exchange for simplicity, some unusable versions remain in
the
+ // matrix that could have been pruned. These unsuable versions
must be
+ // evaluated unnecessarily when the matrix is being searched
for an
+ // acceptable version set.
+ row := m.FindRow(r.Name())
+ if row != nil && len(row.Vers) == 1 {
+ ver := row.Vers[0]
+ branch, err := r.BranchFromVer(ver)
+ if err != nil {
+ return err
+ }
+ depRepo := repos[dep.Name]
+ for _, ddep := range depRepo.BranchDepMap()[branch] {
+ name := fmt.Sprintf("%s,%s", depRepo.Name(),
ver.String())
+ if err := recurse(name, ddep); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+ }
+
+ // Prune versions that are guaranteed to be unusable. Any repo version
+ // which doesn't satisfy a requirement in `project.yml` is a
+ // known-bad-version and can be removed. These repos' dependencies can
+ // then be pruned in turn.
+ for repoName, reqs := range rootReqs {
+ if len(reqs) > 0 {
+ dep := &repo.RepoDependency{
+ Name: repoName,
+ VerReqs: reqs,
+ }
+
+ if err := recurse("project.yml", dep); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+// Produces an error describing the specified set of repo conflicts.
+func ConflictError(conflicts []Conflict) error {
+ s := ""
+
+ for _, c := range conflicts {
+ if s != "" {
+ s += "\n\n"
+ }
+
+ s += fmt.Sprintf(" Installation of repo \"%s\" is blocked:",
+ c.RepoName)
+
+ lines := []string{}
+ for _, f := range c.Filters {
+ lines = append(lines, fmt.Sprintf("\n %30s requires
%s %s",
+ f.Name, c.RepoName,
newtutil.RepoVerReqsString(f.Reqs)))
+ }
+ sort.Strings(lines)
+ s += strings.Join(lines, "")
+ }
+
+ return util.NewNewtError("Repository conflicts:\n" + s)
+}
+
+// Searches a version matrix for a set of acceptable repo versions. If there
+// isn't an acceptable set of versions, the set with the fewest conflicts is
+// returned.
+//
+// @param m Matrix containing all unpruned repo versions.
+// @param dg The repo dependency graph.
+//
+// @return VersionMap The first perfect set of repo versions, or the
+// closest match if there is no perfect set.
+// @return []string nil if a perfect set was found, else the names
+// of the repos that lack a suitable version
+// in the returned version map.
+func findClosestMatch(m Matrix, dg DepGraph) (VersionMap, []string) {
+ // Tracks the best match seen so far.
+ type Best struct {
+ vm VersionMap
+ failures []string
+ }
+ var best Best
+
+ for {
+ vm := m.CurVersions()
+ badRepos := dg.conflictingRepos(vm)
+ if len(badRepos) == 0 {
+ // Found a perfect match. Return it.
+ return vm, nil
+ }
+
+ if best.failures == nil || len(badRepos) < len(best.failures) {
+ best.vm = vm
+ best.failures = badRepos
+ }
+
+ // Evaluate the next set of versions on the following iteration.
+ if !m.Increment() {
+ // All version sets evaluated. Return the best match.
+ return best.vm, best.failures
+ }
+ }
+}
+
+// Finds the first set of repo versions which satisfies the dependency graph.
+// If there is no acceptable set, a slice of conflicts is returned instead.
+//
+// @param m Matrix containing all unpruned repo versions.
+// @param dg The repo dependency graph.
+// @return VersionMap The first perfect set of repo versions, or nil
+// if there is no perfect set.
+// @return []Conflict nil if a perfect set was found, else the set of
+// conflicts preventing a perfect match from
+// being returned.
+func FindAcceptableVersions(m Matrix, dg DepGraph) (VersionMap, []Conflict) {
+ vm, failures := findClosestMatch(m, dg)
+ if len(failures) == 0 {
+ // No failures implies a perfect match was found. Return it.
+ return vm, nil
+ }
+
+ // A perfect version set doesn't exist. Generate the set of relevant
+ // conflicts and return it.
+ conflicts := make([]Conflict, len(failures))
+
+ // Build a reverse dependency graph. This will make it easy to
determine
+ // which version requirements are relevant to the failure.
+ rg := dg.Reverse()
+ for i, f := range failures {
+ conflict := Conflict{
+ RepoName: f,
+ }
+ for _, node := range rg[f] {
+ // Determine if this filter is responsible for any
conflicts.
+ // Record the name of the filter if it applies.
+ var filterName string
+ if node.Name == rootDependencyName {
+ filterName = "project.yml"
+ } else {
+ // If the version of the repo in the
closest-match version map
+ // is the same one that imposes this version
requirement,
+ // include it in the conflict object.
+ if newtutil.CompareRepoVersions(vm[node.Name],
node.Ver) == 0 {
+ filterName = fmt.Sprintf(
+ "%s,%s", node.Name,
node.Ver.String())
+ }
+ }
+
+ if filterName != "" {
+ conflict.Filters = append(conflict.Filters,
Filter{
+ Name: filterName,
+ Reqs: node.VerReqs,
+ })
+ }
+ }
+ conflicts[i] = conflict
+ }
+
+ return nil, conflicts
+}
diff --git a/newt/deprepo/graph.go b/newt/deprepo/graph.go
new file mode 100644
index 00000000..648c874d
--- /dev/null
+++ b/newt/deprepo/graph.go
@@ -0,0 +1,185 @@
+// deprepo: Package for resolving repo dependencies.
+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 = ""
+
+// 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 (dep *Dependent) String() string {
+ if dep.Name == rootDependencyName {
+ return "project.yml"
+ } else {
+ return fmt.Sprintf("%s-%s", dep.Name, dep.Ver.String())
+ }
+}
+
+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")
+}
+
+// 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 {
+ 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).
+func (dg DepGraph) conflictingRepos(vm VersionMap) []string {
+ repoNames := make([]string, 0, len(vm))
+ for name, _ := range vm {
+ repoNames = append(repoNames, name)
+ }
+ sort.Strings(repoNames)
+
+ badRepoMap := map[string]struct{}{}
+ for _, repoName := range repoNames {
+ dependentVer := vm[repoName]
+ dependent := Dependent{repoName, dependentVer}
+ for _, node := range dg[dependent] {
+ dependeeVer := vm[node.Name]
+ if !dependeeVer.SatisfiesAll(node.VerReqs) {
+ badRepoMap[node.Name] = struct{}{}
+ }
+ }
+ }
+
+ badRepoSlice := make([]string, 0, len(badRepoMap))
+ for repoName, _ := range badRepoMap {
+ badRepoSlice = append(badRepoSlice, repoName)
+ }
+ sort.Strings(badRepoSlice)
+
+ return badRepoSlice
+}
diff --git a/newt/deprepo/matrix.go b/newt/deprepo/matrix.go
new file mode 100644
index 00000000..fe490aa6
--- /dev/null
+++ b/newt/deprepo/matrix.go
@@ -0,0 +1,153 @@
+// deprepo: Package for resolving repo dependencies.
+package deprepo
+
+import (
+ "fmt"
+ "strings"
+
+ "mynewt.apache.org/newt/newt/newtutil"
+ "mynewt.apache.org/newt/util"
+)
+
+// Eliminates non-matching version numbers when applied to a matrix row.
+type Filter struct {
+ Name string
+ Reqs []newtutil.RepoVersionReq
+}
+
+// Contains all versions of a single repo. These version numbers are read from
+// the repo's `repository.yml` file. Only normalized versions are included.
+type MatrixRow struct {
+ // The name of the repo that the row corresponds to.
+ RepoName string
+
+ // All normalized versions of the repo.
+ Vers []newtutil.RepoVersion
+
+ // Indicates the version of this repo currently being evaluated for
+ // conflicts.
+ VerIdx int
+
+ // All filters that have been applied to this row. This is only used
+ // during reporting.
+ Filters []Filter
+}
+
+// Contains all versions of a set of repos. Each row correponds to a single
+// repo. Each element within a row represents a single version of the repo.
+//
+// The Matrix type serves two purposes:
+//
+// 1. Simple lookup: Provides a convenient means of determining whether a
+// specific version of a repo exists.
+//
+// 2. Requirements matching: The client can cycle through all combinations of
+// repo versions via the `Increment()` function. Each combination can be
+// exported as a version map via the `CurVersions()` function. By evaluating
+// each version map against a set of requirements, the client can find the set
+// of repo versions to upgrade to.
+type Matrix struct {
+ rows []MatrixRow
+}
+
+func (m *Matrix) String() string {
+ lines := make([]string, len(m.rows))
+
+ for i, row := range m.rows {
+ line := fmt.Sprintf("%s:", row.RepoName)
+ for _, v := range row.Vers {
+ line += fmt.Sprintf(" %s", v.String())
+ }
+
+ lines[i] = line
+ }
+
+ return strings.Join(lines, "\n")
+}
+
+// Adjusts the matrix to point to the next possible set of repo versions.
+//
+// @return bool true if the matrix points to a new set;
+// false if the matrix wrapped around to the first
+// set.
+func (m *Matrix) Increment() bool {
+ for i := range m.rows {
+ row := &m.rows[i]
+
+ row.VerIdx++
+ if row.VerIdx < len(row.Vers) {
+ return true
+ }
+
+ // No more versions left for this repo; proceed to next.
+ row.VerIdx = 0
+ }
+
+ // All version combinations evaluated.
+ return false
+}
+
+func (m *Matrix) findRowIdx(repoName string) int {
+ for i, row := range m.rows {
+ if row.RepoName == repoName {
+ return i
+ }
+ }
+
+ return -1
+}
+
+func (m *Matrix) FindRow(repoName string) *MatrixRow {
+ idx := m.findRowIdx(repoName)
+ if idx == -1 {
+ return nil
+ }
+ return &m.rows[idx]
+}
+
+func (m *Matrix) AddRow(repoName string,
+ vers []newtutil.RepoVersion) error {
+
+ if m.findRowIdx(repoName) != -1 {
+ return util.FmtNewtError("Duplicate repo \"%s\" in repo matrix",
+ repoName)
+ }
+
+ m.rows = append(m.rows, MatrixRow{
+ RepoName: repoName,
+ Vers: newtutil.SortedVersionsDesc(vers),
+ })
+
+ return nil
+}
+
+// Removes all non-matching versions of the specified repo from the matrix.
+func (m *Matrix) ApplyFilter(repoName string, filter Filter) {
+ rowIdx := m.findRowIdx(repoName)
+ if rowIdx == -1 {
+ return
+ }
+ row := &m.rows[rowIdx]
+
+ goodVers := []newtutil.RepoVersion{}
+ for _, v := range row.Vers {
+ if v.SatisfiesAll(filter.Reqs) {
+ goodVers = append(goodVers, v)
+ }
+ }
+
+ row.Vers = goodVers
+ row.Filters = append(row.Filters, filter)
+}
+
+// Constructs a version map from the matrix's current state.
+func (m *Matrix) CurVersions() VersionMap {
+ vm := make(VersionMap, len(m.rows))
+ for _, row := range m.rows {
+ if len(row.Vers) > 0 {
+ vm[row.RepoName] = row.Vers[row.VerIdx]
+ }
+ }
+
+ return vm
+}
diff --git a/newt/downloader/downloader.go b/newt/downloader/downloader.go
index eaa1b88c..45f5618e 100644
--- a/newt/downloader/downloader.go
+++ b/newt/downloader/downloader.go
@@ -316,8 +316,8 @@ func (gd *GenericDownloader) cachedFetch(fn func() error)
error {
func (gd *GithubDownloader) fetch(repoDir string) error {
return gd.cachedFetch(func() error {
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "Fetching new remote branches/tags\n")
+ util.StatusMessage(util.VERBOSITY_VERBOSE, "Fetching repo %s\n",
+ gd.Repo)
_, err := gd.authenticatedCommand(repoDir, []string{"fetch",
"--tags"})
return err
@@ -481,9 +481,9 @@ func (gd *GithubDownloader) DownloadRepo(commit string,
dstPath string) error {
url, publicUrl := gd.remoteUrls()
- util.StatusMessage(util.VERBOSITY_VERBOSE, "Downloading "+
- "repository %s (branch: %s; commit: %s) at %s\n", gd.Repo,
branch,
- commit, publicUrl)
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Downloading repository %s (commit: %s) from %s\n",
+ gd.Repo, commit, publicUrl)
gp, err := gitPath()
if err != nil {
@@ -525,8 +525,8 @@ func NewGithubDownloader() *GithubDownloader {
func (gd *GitDownloader) fetch(repoDir string) error {
return gd.cachedFetch(func() error {
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "Fetching new remote branches/tags\n")
+ util.StatusMessage(util.VERBOSITY_VERBOSE, "Fetching repo %s\n",
+ gd.Url)
_, err := executeGitCommand(repoDir, []string{"fetch",
"--tags"}, true)
return err
})
@@ -607,8 +607,8 @@ func (gd *GitDownloader) DownloadRepo(commit string,
dstPath string) error {
// Currently only the master branch is supported.
branch := "master"
- util.StatusMessage(util.VERBOSITY_VERBOSE, "Downloading "+
- "repository %s (branch: %s; commit: %s)\n", gd.Url, branch,
commit)
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Downloading repository %s (commit: %s)\n", gd.Url, commit)
gp, err := gitPath()
if err != nil {
@@ -667,8 +667,8 @@ func (ld *LocalDownloader) CurrentBranch(path string)
(string, error) {
}
func (ld *LocalDownloader) UpdateRepo(path string, branchName string) error {
- // Nothing to update in a local repo.
- return nil
+ os.RemoveAll(path)
+ return ld.DownloadRepo(branchName, path)
}
func (ld *LocalDownloader) CleanupRepo(path string, branchName string) error {
@@ -692,7 +692,7 @@ func (ld *LocalDownloader) AreChanges(path string) (bool,
error) {
}
func (ld *LocalDownloader) DownloadRepo(commit string, dstPath string) error {
- util.StatusMessage(util.VERBOSITY_VERBOSE,
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
"Downloading local repository %s\n", ld.Path)
if err := util.CopyDir(ld.Path, dstPath); err != nil {
diff --git a/newt/interfaces/interfaces.go b/newt/interfaces/interfaces.go
index cfbbbe64..e84d7a2c 100644
--- a/newt/interfaces/interfaces.go
+++ b/newt/interfaces/interfaces.go
@@ -35,23 +35,6 @@ type RepoInterface interface {
Path() string
}
-type VersionReqInterface interface {
- CompareType() string
- Version() VersionInterface
- String() string
-}
-
-type VersionInterface interface {
- SatisfiesVersion(versReq []VersionReqInterface) bool
- CompareVersions(vers1 VersionInterface, vers2 VersionInterface) int64
- Major() int64
- Minor() int64
- Revision() int64
- Stability() string
- Tag() string
- String() string
-}
-
type PackageList map[string]*map[string]PackageInterface
type DependencyInterface interface {
diff --git a/newt/newtutil/newtutil.go b/newt/newtutil/newtutil.go
index d40719a5..a4e1d6b2 100644
--- a/newt/newtutil/newtutil.go
+++ b/newt/newtutil/newtutil.go
@@ -22,7 +22,6 @@ package newtutil
import (
"fmt"
"io/ioutil"
- "path/filepath"
"strconv"
"strings"
@@ -37,6 +36,7 @@ var NewtVersionStr string = "Apache Newt version: 1.3.0"
var NewtBlinkyTag string = "mynewt_1_3_0_tag"
var NewtNumJobs int
var NewtForce bool
+var NewtAsk bool
const CORE_REPO_NAME string = "apache-mynewt-core"
const ARDUINO_ZERO_REPO_NAME string = "mynewt_arduino_zero"
@@ -178,15 +178,18 @@ func MakeTempRepoDir() (string, error) {
// Read in the configuration file specified by name, in path
// return a new viper config object if successful, and error if not
func ReadConfig(path string, name string) (ycfg.YCfg, error) {
- file, err := ioutil.ReadFile(path + "/" + name + ".yml")
+ fullPath := path + "/" + name + ".yml"
+
+ file, err := ioutil.ReadFile(fullPath)
if err != nil {
- return nil, util.NewNewtError(fmt.Sprintf("Error reading
%s.yml: %s",
- filepath.Join(path, name), err.Error()))
+ return nil, util.NewNewtError(fmt.Sprintf("Error reading %s:
%s",
+ fullPath, err.Error()))
}
settings := map[string]interface{}{}
if err := yaml.Unmarshal(file, &settings); err != nil {
- return nil, err
+ return nil, util.FmtNewtError("Failure parsing \"%s\": %s",
+ fullPath, err.Error())
}
return ycfg.NewYCfg(settings)
diff --git a/newt/newtutil/repo_version.go b/newt/newtutil/repo_version.go
new file mode 100644
index 00000000..7f7f6d9c
--- /dev/null
+++ b/newt/newtutil/repo_version.go
@@ -0,0 +1,323 @@
+/**
+ * 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.
+ */
+
+package newtutil
+
+import (
+ "fmt"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+
+ "mynewt.apache.org/newt/util"
+
+ log "github.com/Sirupsen/logrus"
+)
+
+const (
+ VERSION_STABILITY_NONE = "none"
+ VERSION_STABILITY_STABLE = "stable"
+ VERSION_STABILITY_DEV = "dev"
+ VERSION_STABILITY_LATEST = "latest"
+ VERSION_STABILITY_TAG = "tag"
+)
+
+// Represents an unspecified part in a version. For example, in "1-latest",
+// the minor and revision parts are floating.
+const VERSION_FLOATING = -1
+
+type RepoVersionReq struct {
+ CompareType string
+ Ver RepoVersion
+}
+
+type RepoVersion struct {
+ Major int64
+ Minor int64
+ Revision int64
+ Stability string
+ Tag string
+}
+
+func (vm *RepoVersionReq) String() string {
+ return vm.CompareType + vm.Ver.String()
+}
+
+func CompareRepoVersions(v1 RepoVersion, v2 RepoVersion) int64 {
+ if r := v1.Major - v2.Major; r != 0 {
+ return r
+ }
+
+ if r := v1.Minor - v2.Minor; r != 0 {
+ return r
+ }
+
+ if r := v1.Revision - v2.Revision; r != 0 {
+ return r
+ }
+
+ if v1.Tag != v2.Tag {
+ return 1
+ }
+
+ return 0
+}
+
+func (v *RepoVersion) Satisfies(verReq RepoVersionReq) bool {
+ if verReq.Ver.Tag != "" && verReq.CompareType != "==" {
+ log.Warningf("RepoVersion comparison with a tag %s %s %s",
+ verReq.Ver, verReq.CompareType, v)
+ }
+ r := CompareRepoVersions(verReq.Ver, *v)
+ switch verReq.CompareType {
+ case "<":
+ if r <= 0 {
+ return false
+ }
+ case "<=":
+ if r < 0 {
+ return false
+ }
+ case ">":
+ if r >= 0 {
+ return false
+ }
+ case ">=":
+ if r > 0 {
+ return false
+ }
+ case "==":
+ if r != 0 {
+ return false
+ }
+ }
+
+ if verReq.Ver.Stability != v.Stability {
+ return false
+ }
+
+ return true
+}
+
+func (v *RepoVersion) SatisfiesAll(verReqs []RepoVersionReq) bool {
+ for _, r := range verReqs {
+ if !v.Satisfies(r) {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (ver *RepoVersion) String() string {
+ if ver.Tag != "" {
+ return fmt.Sprintf("%s-tag", ver.Tag)
+ }
+
+ s := fmt.Sprintf("%d", ver.Major)
+ if ver.Minor != VERSION_FLOATING {
+ s += fmt.Sprintf(".%d", ver.Minor)
+ }
+ if ver.Revision != VERSION_FLOATING {
+ s += fmt.Sprintf(".%d", ver.Revision)
+ }
+
+ if ver.Stability != VERSION_STABILITY_NONE {
+ s += fmt.Sprintf("-%s", ver.Stability)
+ }
+
+ return s
+}
+
+func (ver *RepoVersion) ToNuVersion() Version {
+ return Version{
+ Major: ver.Major,
+ Minor: ver.Minor,
+ Revision: ver.Revision,
+ }
+}
+
+func ParseRepoVersion(verStr string) (RepoVersion, error) {
+ var err error
+
+ // Split to get stability level first
+ sparts := strings.Split(verStr, "-")
+ stability := VERSION_STABILITY_NONE
+ if len(sparts) > 1 {
+ stability = strings.Trim(sparts[1], " ")
+ switch stability {
+ case VERSION_STABILITY_TAG:
+ return NewTag(strings.Trim(sparts[0], " ")), nil
+ case VERSION_STABILITY_STABLE:
+ fallthrough
+ case VERSION_STABILITY_DEV:
+ fallthrough
+ case VERSION_STABILITY_LATEST:
+ default:
+ return RepoVersion{}, util.FmtNewtError(
+ "Unknown stability (%s) in verion %s",
stability, verStr)
+ }
+ }
+ parts := strings.Split(sparts[0], ".")
+ if len(parts) > 3 {
+ return RepoVersion{},
+ util.FmtNewtError("Invalid verion string: %s", verStr)
+ }
+
+ if strings.Trim(parts[0], " ") == "" ||
+ strings.Trim(parts[0], " ") == "none" {
+
+ return RepoVersion{}, nil
+ }
+
+ // Assume no parts of the verion are specified.
+ ver := RepoVersion{
+ Major: VERSION_FLOATING,
+ Minor: VERSION_FLOATING,
+ Revision: VERSION_FLOATING,
+ Stability: stability,
+ }
+
+ // convert first string to an int
+ if ver.Major, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
+ return RepoVersion{}, util.NewNewtError(err.Error())
+ }
+ if len(parts) >= 2 {
+ if ver.Minor, err = strconv.ParseInt(parts[1], 10, 64); err !=
nil {
+ return RepoVersion{}, util.NewNewtError(err.Error())
+ }
+ }
+ if len(parts) == 3 {
+ if ver.Revision, err = strconv.ParseInt(parts[2], 10, 64); err
!= nil {
+ return RepoVersion{}, util.NewNewtError(err.Error())
+ }
+ }
+
+ return ver, nil
+}
+
+func NewTag(tag string) RepoVersion {
+ return RepoVersion{
+ Tag: tag,
+ Stability: VERSION_STABILITY_NONE,
+ }
+}
+
+// Parse a set of version string constraints on a dependency.
+// This function
+// The version string contains a list of version constraints in the following
format:
+// - <comparison><version>
+// Where <comparison> can be any one of the following comparison
+// operators: <=, <, >, >=, ==
+// And <version> is specified in the form: X.Y.Z where X, Y and Z are all
+// int64 types in decimal form
+func ParseRepoVersionReqs(versStr string) ([]RepoVersionReq, error) {
+ var err error
+
+ verReqs := []RepoVersionReq{}
+
+ re, err := regexp.Compile(`(<=|>=|==|>|<)([\d\.]+)`)
+ if err != nil {
+ return nil, err
+ }
+
+ matches := re.FindAllStringSubmatch(versStr, -1)
+ if matches != nil {
+ for _, match := range matches {
+ vm := RepoVersionReq{}
+ vm.CompareType = match[1]
+ if vm.Ver, err = ParseRepoVersion(match[2]); err != nil
{
+ return nil, err
+ }
+
+ verReqs = append(verReqs, vm)
+ }
+ } else {
+ vm := RepoVersionReq{}
+ vm.CompareType = "=="
+ if vm.Ver, err = ParseRepoVersion(versStr); err != nil {
+ return nil, err
+ }
+
+ verReqs = append(verReqs, vm)
+ }
+
+ if len(verReqs) == 0 {
+ verReqs = nil
+ }
+
+ return verReqs, nil
+}
+
+func RepoVerReqsString(verReqs []RepoVersionReq) string {
+ s := ""
+ for i, r := range verReqs {
+ if i != 0 {
+ s += " "
+ }
+ s += r.String()
+ }
+
+ return s
+}
+
+type verSorter struct {
+ vers []RepoVersion
+}
+
+func (v verSorter) Len() int {
+ return len(v.vers)
+}
+func (v verSorter) Swap(i, j int) {
+ v.vers[i], v.vers[j] = v.vers[j], v.vers[i]
+}
+func (v verSorter) Less(i, j int) bool {
+ a := v.vers[i]
+ b := v.vers[j]
+
+ return CompareRepoVersions(a, b) < 0
+}
+
+func SortVersions(vers []RepoVersion) {
+ sorter := verSorter{
+ vers: vers,
+ }
+
+ sort.Sort(sorter)
+}
+
+func SortedVersions(vers []RepoVersion) []RepoVersion {
+ clone := make([]RepoVersion, len(vers))
+ copy(clone, vers)
+
+ SortVersions(clone)
+ return clone
+}
+
+func SortedVersionsDesc(vers []RepoVersion) []RepoVersion {
+ slice := SortedVersions(vers)
+ size := len(slice)
+ for i := 0; i < size/2; i++ {
+ j := size - 1 - i
+ slice[i], slice[j] = slice[j], slice[i]
+ }
+
+ return slice
+}
diff --git a/newt/project/project.go b/newt/project/project.go
index ae37329c..48b4c17a 100644
--- a/newt/project/project.go
+++ b/newt/project/project.go
@@ -31,6 +31,7 @@ import (
log "github.com/Sirupsen/logrus"
"mynewt.apache.org/newt/newt/compat"
+ "mynewt.apache.org/newt/newt/deprepo"
"mynewt.apache.org/newt/newt/downloader"
"mynewt.apache.org/newt/newt/interfaces"
"mynewt.apache.org/newt/newt/newtutil"
@@ -60,15 +61,36 @@ type Project struct {
projState *ProjectState
- // Repositories configured on this project
- repos map[string]*repo.Repo
- warnings []string
-
+ // Contains all the repos that form this project. Each repo is in one
of
+ // two states:
+ // * description: Only the repo's basic description fields have been
+ // read from `project.yml` or from a dependent repo's
+ // `repository.yml` file. This repo's
`repository.yml`
+ // file still needs to be read.
+ // * complete: The repo's `repository.yml` file exists and has been
+ // read.
+ repos deprepo.RepoMap
+
+ // The local repository at the top-level of the project. This repo is
+ // excluded from most repo operations.
localRepo *repo.Repo
+ // Required versions of installed repos, as read from `project.yml`.
+ rootRepoReqs deprepo.RequirementMap
+
+ warnings []string
+
yc ycfg.YCfg
}
+type installOp int
+
+const (
+ INSTALL_OP_INSTALL installOp = iota
+ INSTALL_OP_UPGRADE
+ INSTALL_OP_SYNC
+)
+
func initProject(dir string) error {
var err error
@@ -152,21 +174,6 @@ func (proj *Project) Repos() map[string]*repo.Repo {
return proj.repos
}
-func (proj *Project) SortedRepos() []*repo.Repo {
- names := make([]string, 0, len(proj.repos))
- for n, _ := range proj.repos {
- names = append(names, n)
- }
- sort.Strings(names)
-
- repos := make([]*repo.Repo, len(names))
- for i, n := range names {
- repos[i] = proj.repos[n]
- }
-
- return repos
-}
-
func (proj *Project) FindRepo(rname string) *repo.Repo {
if rname == repo.REPO_NAME_LOCAL {
return proj.LocalRepo()
@@ -190,6 +197,10 @@ func (proj *Project) RepoIsInstalled(rname string) bool {
return proj.projState.GetInstalledVersion(rname) != nil
}
+func (proj *Project) RepoIsRoot(rname string) bool {
+ return proj.rootRepoReqs[rname] != nil
+}
+
func (proj *Project) LocalRepo() *repo.Repo {
return proj.localRepo
}
@@ -198,177 +209,350 @@ func (proj *Project) Warnings() []string {
return proj.warnings
}
-// @return bool True if upgrade should be skipped;
-// False if upgrade should occur.
-func (proj *Project) upgradeCheck(r *repo.Repo, vers *repo.Version,
- force bool) (bool, error) {
+// Selects repositories from the global state that satisfy the specified
+// predicate.
+func (proj *Project) SelectRepos(pred func(r *repo.Repo) bool) []*repo.Repo {
+ all := proj.repos.Sorted()
+ var filtered []*repo.Repo
- rdesc, err := r.GetRepoDesc()
- if err != nil {
- return false, err
+ for _, r := range all {
+ if pred(r) {
+ filtered = append(filtered, r)
+ }
}
- branch, newVers, _ := rdesc.Match(r)
- if newVers == nil {
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "No matching version to upgrade to "+
- "found for %s. Please check your project
requirements.",
- r.Name())
- return false,
- util.FmtNewtError("Cannot find a version of repository
%s that "+
- "matches project requirements.", r.Name())
- }
-
- // If the change between the old repository and the new repository would
- // cause an upgrade. Then prompt for an upgrade response, unless the
force
- // option is present.
- if vers.CompareVersions(newVers, vers) != 0 ||
- vers.Stability() != newVers.Stability() {
- if !force {
- str := ""
- if newVers.Stability() != repo.VERSION_STABILITY_NONE {
- str += "(" + branch + ")"
- }
+ return filtered
+}
- fmt.Printf("Would you like to upgrade repository %s
from %s "+
- "to %s %s? [Yn] ",
- r.Name(), vers.String(), newVers.String(), str)
- line, more, err := bufio.NewReader(os.Stdin).ReadLine()
- if more || err != nil {
- return false, util.NewNewtError(fmt.Sprintf(
- "Couldn't read upgrade response: %s\n",
err.Error()))
- }
+// Indicates whether a repo should be installed. A repo should be installed if
+// it is not currently installed.
+func (proj *Project) shouldInstallRepo(repoName string) bool {
+ return proj.projState.GetInstalledVersion(repoName) == nil
+}
- // Anything but no means yes.
- answer := strings.ToUpper(strings.Trim(string(line), "
"))
- if answer == "N" || answer == "NO" {
- fmt.Printf("User says don't upgrade, skipping
upgrade of %s\n",
- r.Name())
- return true, nil
- }
- }
- } else {
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "Repository %s doesn't need to be upgraded, latest "+
- "version installed.\n", r.Name())
- return true, nil
+// Indicates whether a repo should be upgraded to the specified version. A
+// repo should be upgraded if it is not currently installed, or if a version
+// other than the desired one is installed.
+func (proj *Project) shouldUpgradeRepo(repoName string,
+ destVer newtutil.RepoVersion) bool {
+
+ r := proj.repos[repoName]
+ if r == nil {
+ return false
}
- return false, nil
+ stateVer := proj.projState.GetInstalledVersion(repoName)
+ if stateVer == nil {
+ return true
+ }
+
+ return !r.VersionsEqual(*stateVer, destVer)
}
-func (proj *Project) checkVersionRequirements(
- r *repo.Repo, upgrade bool, force bool) (bool, error) {
+// Removes repos that shouldn't be installed from the specified list. A repo
+// should not be installed if it is already installed (any version).
+//
+// @param repos The list of repos to filter.
+//
+// @return []*Repo The filtered list of repos.
+func (proj *Project) filterInstallList(repos []*repo.Repo) []*repo.Repo {
+ keep := []*repo.Repo{}
- rdesc, err := r.GetRepoDesc()
- if err != nil {
- return false, err
+ for _, r := range repos {
+ curVer := proj.projState.GetInstalledVersion(r.Name())
+ if curVer == nil {
+ keep = append(keep, r)
+ } else {
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Skipping \"%s\": already installed (%s)\n",
+ r.Name(), curVer.String())
+ }
}
- rname := r.Name()
+ return keep
+}
+
+// Removes repos that shouldn't be upgraded from the specified list. A repo
+// should not be upgraded if the desired version is already installed.
+//
+// @param repos The list of repos to filter.
+// @param vm Specifies the desired version of each repo.
+//
+// @return []*Repo The filtered list of repos.
+func (proj *Project) filterUpgradeList(
+ repos []*repo.Repo, vm deprepo.VersionMap) []*repo.Repo {
- vers := proj.projState.GetInstalledVersion(rname)
- if vers != nil {
- ok := rdesc.SatisfiesVersion(vers, r.VersionRequirements())
- if !ok && !upgrade {
- util.StatusMessage(util.VERBOSITY_QUIET,
- "WARNING: Installed version %s of repository %s
does not "+
- "match desired version %s in project
file. You can fix "+
- "this by either upgrading your
repository, or modifying "+
- "the project.yml file.\n",
- vers, rname, r.VersionRequirementsString())
- return true, nil
+ keep := []*repo.Repo{}
+
+ for _, r := range repos {
+ destVer := vm[r.Name()]
+ if proj.shouldUpgradeRepo(r.Name(), destVer) {
+ keep = append(keep, r)
} else {
- if !upgrade {
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "%s correct version already
installed\n", r.Name())
- return true, nil
- } else {
- skip, err := proj.upgradeCheck(r, vers, force)
- return skip, err
+ curVer := proj.projState.GetInstalledVersion(r.Name())
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Skipping \"%s\": already upgraded (%s)\n",
+ r.Name(), curVer.String())
+ }
+ }
+
+ return keep
+}
+
+// Determines if the `project.yml` file specifies a nonexistent repo version.
+// Only the repos in the specified slice are considered.
+//
+// @param repos The list of repos to consider during the check.
+// @param m A matrix containing all versions of the
+// specified repos.
+//
+// @return error Error if any repo requirement is invalid.
+func (proj *Project) detectIllegalRepoReqs(
+ repos []*repo.Repo, m deprepo.Matrix) error {
+
+ var lines []string
+ for _, r := range repos {
+ reqs, ok := proj.rootRepoReqs[r.Name()]
+ if ok {
+ row := m.FindRow(r.Name())
+ if row == nil {
+ return util.FmtNewtError(
+ "internal error; repo \"%s\" missing
from matrix", r.Name())
+ }
+
+ r := proj.repos[r.Name()]
+ nreqs, err := r.NormalizeVerReqs(reqs)
+ if err != nil {
+ return err
+ }
+
+ anySatisfied := false
+ for _, ver := range row.Vers {
+ if ver.SatisfiesAll(nreqs) {
+ anySatisfied = true
+ break
+ }
+ }
+ if !anySatisfied {
+ line := fmt.Sprintf(" %s,%s", r.Name(),
+ newtutil.RepoVerReqsString(nreqs))
+ lines = append(lines, line)
}
}
+ }
+
+ if len(lines) > 0 {
+ sort.Strings(lines)
+ return util.NewNewtError(
+ "project.yml file specifies nonexistent repo
versions:\n" +
+ strings.Join(lines, "\n"))
+ }
+
+ return nil
+}
+
+// Installs or upgrades a single repo to the specified version.
+func (proj *Project) installRepo(r *repo.Repo, ver newtutil.RepoVersion,
+ upgrade bool, force bool) error {
+
+ // Install the acceptable version.
+ if upgrade {
+ if err := r.Upgrade(ver, force); err != nil {
+ return err
+ }
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "%s successfully upgraded to version %s\n",
+ r.Name(), ver.String())
} else {
- // Fallthrough and perform the installation.
- // Check to make sure that this repository contains a version
- // that can satisfy.
- _, _, ok := rdesc.Match(r)
- if !ok {
- util.StatusMessage(util.VERBOSITY_QUIET,
- "WARNING: No matching repository version found
for "+
- "repository %s specified in
project.\n", r.Name())
- return true, nil
+ if err := r.Install(ver); err != nil {
+ return err
}
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "%s successfully installed version %s\n",
+ r.Name(), ver.String())
}
- return false, nil
+ // Update the project state with the new repository version
+ // information.
+ proj.projState.Replace(r.Name(), ver)
+
+ return nil
}
-func (proj *Project) checkDeps(r *repo.Repo) error {
- repos, updated, err := r.UpdateDesc()
- if err != nil {
- return err
- }
+func (proj *Project) installMessageOneRepo(
+ repoName string, op installOp, force bool, curVer *newtutil.RepoVersion,
+ destVer newtutil.RepoVersion) (string, error) {
- if !updated {
- return nil
+ // If the repo isn't installed yet, this is an install, not an upgrade.
+ if op == INSTALL_OP_UPGRADE && curVer == nil {
+ op = INSTALL_OP_INSTALL
}
- for _, newRepo := range repos {
- curRepo, ok := proj.repos[newRepo.Name()]
- if !ok {
- proj.repos[newRepo.Name()] = newRepo
- return proj.updateRepos(proj.SortedRepos())
+ var verb string
+ switch op {
+ case INSTALL_OP_INSTALL:
+ if !force {
+ verb = "install"
} else {
- // Add any dependencies we might have found here.
- for _, dep := range newRepo.Deps() {
- newRepo.DownloadDesc()
- newRepo.ReadDesc()
- curRepo.AddDependency(dep)
- }
+ verb = "reinstall"
}
+
+ case INSTALL_OP_UPGRADE:
+ verb = "upgrade"
+
+ case INSTALL_OP_SYNC:
+ verb = "sync"
+
+ default:
+ return "", util.FmtNewtError(
+ "internal error: invalid install op: %v", op)
}
- return nil
+ msg := fmt.Sprintf(" %s %s ", verb, repoName)
+ if curVer != nil {
+ msg += fmt.Sprintf("(%s --> %s)", curVer.String(),
destVer.String())
+ } else {
+ msg += fmt.Sprintf("(%s)", destVer.String())
+ }
+
+ return msg, nil
}
-// Selects repositories from the global state that satisfy the specified
-// predicate.
-func (proj *Project) SelectRepos(pred func(r *repo.Repo) bool) []*repo.Repo {
- all := proj.SortedRepos()
- var filtered []*repo.Repo
+// Describes an imminent repo operation to the user. In addition, prompts the
+// user for confirmation if the `-a` (ask) option was specified.
+func (proj *Project) installPrompt(repoList []*repo.Repo,
+ vm deprepo.VersionMap, op installOp, force bool, ask bool) (bool,
error) {
- for _, r := range all {
- if pred(r) {
- filtered = append(filtered, r)
+ if len(repoList) == 0 {
+ return true, nil
+ }
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Making the following changes to the project:\n")
+
+ for _, r := range repoList {
+ curVer := proj.projState.GetInstalledVersion(r.Name())
+ destVer := vm[r.Name()]
+
+ msg, err := proj.installMessageOneRepo(
+ r.Name(), op, force, curVer, destVer)
+ if err != nil {
+ return false, err
}
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT, "%s\n", msg)
}
+ util.StatusMessage(util.VERBOSITY_DEFAULT, "\n")
- return filtered
-}
+ if !ask {
+ return true, nil
+ }
-func (proj *Project) updateRepos(repos []*repo.Repo) error {
- for _, r := range repos {
- if r.IsLocal() {
- continue
+ for {
+ fmt.Printf("Proceed? [Y/n] ")
+ line, more, err := bufio.NewReader(os.Stdin).ReadLine()
+ if more || err != nil {
+ return false, util.ChildNewtError(err)
}
- err := proj.checkDeps(r)
- if err != nil {
- return err
+ trimmed := strings.ToLower(strings.TrimSpace(string(line)))
+ if len(trimmed) == 0 || strings.HasPrefix(trimmed, "y") {
+ // User wants to proceed.
+ return true, nil
+ }
+
+ if strings.HasPrefix(trimmed, "n") {
+ // User wants to cancel.
+ return false, nil
}
+
+ // Invalid response.
+ fmt.Printf("Invalid response.\n")
}
- return nil
}
-func (proj *Project) InstallIf(upgrade bool, force bool, predicate func(r
*repo.Repo) bool) error {
- repoList := proj.SelectRepos(predicate)
+// Installs or upgrades repos matching the specified predicate.
+func (proj *Project) InstallIf(
+ upgrade bool, force bool, ask bool,
+ predicate func(r *repo.Repo) bool) error {
- var verb string
+ // Make sure we have an up to date copy of all `repository.yml` files.
+ if err := proj.downloadRepositoryYmlFiles(); err != nil {
+ return err
+ }
+
+ // Now that all repos have been successfully fetched, we can finish the
+ // install procedure locally.
+
+ // Determine which repos the user wants to install or upgrade.
+ specifiedRepoList := proj.SelectRepos(predicate)
+
+ // Repos that depend on any specified repos must also be considered
during
+ // the install / upgrade operation.
+ repoList := proj.ensureDepsInList(specifiedRepoList, nil)
+
+ // Construct a table of all published repo versions.
+ m, err := deprepo.BuildMatrix(
+ repoList, proj.projState.AllInstalledVersions())
+ if err != nil {
+ return err
+ }
+
+ // If the `project.yml` file specifies an invalid repo version, abort
now.
+ if err := proj.detectIllegalRepoReqs(repoList, m); err != nil {
+ return err
+ }
+
+ // Remove blocked repo versions from the table.
+ if err := deprepo.PruneMatrix(
+ &m, proj.repos, proj.rootRepoReqs); err != nil {
+
+ return err
+ }
+
+ // Construct a repo dependency graph from the `project.yml` version
+ // requirements and from each repo's dependency list.
+ dg, err := deprepo.BuildDepGraph(proj.repos, proj.rootRepoReqs)
+ if err != nil {
+ return err
+ }
+
+ // Try to find a version set that satisfies the dependency graph. If no
+ // such set exists, report the conflicts and abort.
+ vm, conflicts := deprepo.FindAcceptableVersions(m, dg)
+ if vm == nil {
+ return deprepo.ConflictError(conflicts)
+ }
+
+ // Now that we know which repo versions we want, we can eliminate some
+ // false-positives from the repo list.
+ repoList = proj.ensureDepsInList(specifiedRepoList, vm)
+
+ // Perform some additional filtering on the list of repos to process.
+ if upgrade {
+ // Don't upgrade a repo if we already have the desired version.
+ repoList = proj.filterUpgradeList(repoList, vm)
+ } else if !force {
+ // Don't install a repo if it is already installed (any
version). We
+ // skip this filter for forced reinstalls.
+ repoList = proj.filterInstallList(repoList)
+ }
+
+ // Notify the user of what install operations are about to happen, and
+ // prompt if the `-a` (ask) option was specified.
+ var op installOp
if upgrade {
- verb = "upgraded"
+ op = INSTALL_OP_UPGRADE
} else {
- verb = "installed"
+ op = INSTALL_OP_INSTALL
+ }
+ proceed, err := proj.installPrompt(repoList, vm, op, force, ask)
+ if err != nil {
+ return err
+ }
+ if !proceed {
+ return nil
}
// For a forced install, delete all existing repos.
@@ -384,132 +568,145 @@ func (proj *Project) InstallIf(upgrade bool, force
bool, predicate func(r *repo.
}
}
- // Fetch "origin" for specified repos and copy the current version of
- // `repository.yml`.
- if err := proj.updateRepos(repoList); err != nil {
+ // Install or upgrade each repo in the list.
+ for _, r := range repoList {
+ destVer := vm[r.Name()]
+ if err := proj.installRepo(r, destVer, upgrade, force); err !=
nil {
+ return err
+ }
+ }
+
+ // Save the project state, including any updates or changes to the
project
+ // information that either install or upgrade caused.
+ if err := proj.projState.Save(); err != nil {
return err
}
- // Now that all repos have been successfully fetched, we can finish the
- // install procedure locally.
+ return nil
+}
+
+// Syncs (i.e., git pulls) repos matching the specified predicate.
+func (proj *Project) SyncIf(
+ force bool, ask bool, predicate func(r *repo.Repo) bool) error {
- // Get repository list and print every repo and its dependencies.
- if err := repo.CheckDeps(repoList); err != nil {
+ // Make sure we have an up to date copy of all `repository.yml` files.
+ if err := proj.downloadRepositoryYmlFiles(); err != nil {
return err
}
- for _, r := range repoList {
- if r.IsLocal() {
- continue
- }
- // Check the version requirements on this repository, and see
- // whether or not we need to install/upgrade it.
- skip, err := proj.checkVersionRequirements(r, upgrade, force)
- if err != nil {
- return err
- }
- if skip {
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "Skipping \"%s\": already %s\n", r.Name(), verb)
- } else {
- var rvers *repo.Version
+ // A sync operation never changes repo versions in the state file, so we
+ // can proceed with the currently-installed versions.
+ vm := proj.projState.AllInstalledVersions()
- if upgrade {
- rvers, err = r.Upgrade(force)
- if err != nil {
- return err
- }
+ // Determine which repos the user wants to sync.
+ repoList := proj.SelectRepos(predicate)
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "%s successfully upgraded to version
%s\n",
- r.Name(), rvers.String())
- } else {
- rvers, err = r.Install(force)
- if err != nil {
- return err
- }
+ // Repos that depend on any specified repos must also be considered
during
+ // the sync operation.
+ repoList = proj.ensureDepsInList(repoList, vm)
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "%s successfully installed version
%s\n",
- r.Name(), rvers)
- }
+ // Notify the user of what install operations are about to happen, and
+ // prompt if the `-a` (ask) option was specified.
+ proceed, err := proj.installPrompt(
+ repoList, vm, INSTALL_OP_SYNC, force, ask)
+ if err != nil {
+ return err
+ }
+ if !proceed {
+ return nil
+ }
- // Update the project state with the new repository
version
- // information.
- proj.projState.Replace(r.Name(), rvers)
+ // Sync each repo in the list.
+ var anyFails bool
+ for _, r := range repoList {
+ ver, ok := vm[r.Name()]
+ if !ok {
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "No installed version of %s found, skipping\n",
+ r.Name())
+ } else {
+ if _, err := r.Sync(ver, force); err != nil {
+ util.StatusMessage(util.VERBOSITY_QUIET,
+ "Failed to sync repo \"%s\": %s\n",
+ r.Name(), err.Error())
+ anyFails = true
+ }
}
}
- // Save the project state, including any updates or changes to the
project
- // information that either install or upgrade caused.
- if err := proj.projState.Save(); err != nil {
- return err
+ if anyFails {
+ var forceMsg string
+ if !force {
+ forceMsg = ". To force resync, add the -f (force)
option."
+ }
+ return util.FmtNewtError("Failed to sync%s", forceMsg)
}
return nil
}
-func (proj *Project) loadRepo(rname string, yc ycfg.YCfg) error {
- varName := fmt.Sprintf("repository.%s", rname)
-
- repoVars := yc.GetValStringMapString(varName, nil)
- if len(repoVars) == 0 {
- return util.NewNewtError(fmt.Sprintf("Missing configuration for
"+
- "repository %s.", rname))
- }
- if repoVars["type"] == "" {
- return util.NewNewtError(fmt.Sprintf("Missing type for
repository " +
- rname))
+// Loads a complete repo definition from the appropriate `repository.yml` file.
+// The supplied fields form a basic repo description as read from `project.yml`
+// or from another repo's dependency list.
+//
+// @param name The name of the repo to read.
+// @param fields Fields containing the basic repo description.
+//
+// @return *Repo The fully-read repo on success; nil on failure.
+// @return error Error on failure.
+func (proj *Project) loadRepo(name string, fields map[string]string) (
+ *repo.Repo, error) {
+
+ // First, read the repo description from the supplied fields.
+ if fields["type"] == "" {
+ return nil,
+ util.FmtNewtError("Missing type for repository %s",
name)
}
- dl, err := downloader.LoadDownloader(rname, repoVars)
+ dl, err := downloader.LoadDownloader(name, fields)
if err != nil {
- return err
+ return nil, err
}
- rversreq := repoVars["vers"]
- r, err := repo.NewRepo(rname, rversreq, dl)
+ // Construct a new repo object from the basic description information.
+ r, err := repo.NewRepo(name, dl)
if err != nil {
- return err
+ return nil, err
}
for _, ignDir := range ignoreSearchDirs {
r.AddIgnoreDir(ignDir)
}
- rd, err := repo.NewRepoDependency(rname, rversreq)
- if err != nil {
- return err
+ // Read the full repo definition from its `repository.yml` file.
+ if err := r.Read(); err != nil {
+ return r, err
}
- rd.Storerepo = r
-
- proj.localRepo.AddDependency(rd)
- // Read the repo's descriptor file so that we have its newt version
- // compatibility map.
- r.ReadDesc()
-
- rvers := proj.projState.GetInstalledVersion(rname)
- if rvers != nil {
- code, msg := r.CheckNewtCompatibility(rvers,
newtutil.NewtVersion)
+ // Warn the user about incompatibilities with this version of newt.
+ ver := proj.projState.GetInstalledVersion(name)
+ if ver != nil {
+ code, msg := r.CheckNewtCompatibility(*ver,
newtutil.NewtVersion)
switch code {
case compat.NEWT_COMPAT_GOOD:
case compat.NEWT_COMPAT_WARN:
util.StatusMessage(util.VERBOSITY_QUIET, "WARNING:
%s.\n", msg)
case compat.NEWT_COMPAT_ERROR:
- return util.NewNewtError(msg)
+ return nil, util.NewNewtError(msg)
}
}
- log.Debugf("Loaded repository %s (type: %s, user: %s, repo: %s)", rname,
- repoVars["type"], repoVars["user"], repoVars["repo"])
+ // XXX: This log message assumes a "github" type repo.
+ log.Debugf("Loaded repository %s (type: %s, user: %s, repo: %s)", name,
+ fields["type"], fields["user"], fields["repo"])
- proj.repos[r.Name()] = r
- return nil
+ return r, nil
}
func (proj *Project) checkNewtVer() error {
- compatSms :=
proj.yc.GetValStringMapString("project.newt_compatibility", nil)
+ compatSms := proj.yc.GetValStringMapString(
+ "project.newt_compatibility", nil)
+
// If this project doesn't have a newt compatibility map, just assume
there
// is no incompatibility.
if len(compatSms) == 0 {
@@ -538,6 +735,123 @@ func (proj *Project) checkNewtVer() error {
}
}
+// Loads the `repository.yml` file for each depended-on repo. This
+func (proj *Project) loadRepoDeps(download bool) error {
+ seen := map[string]struct{}{}
+
+ loadDeps := func(r *repo.Repo) ([]*repo.Repo, error) {
+ var newRepos []*repo.Repo
+
+ depMap := r.BranchDepMap()
+ for _, depSlice := range depMap {
+ for _, dep := range depSlice {
+ if _, ok := seen[dep.Name]; !ok {
+ seen[r.Name()] = struct{}{}
+
+ depRepo := proj.repos[dep.Name]
+ if depRepo == nil {
+ depRepo, _ =
proj.loadRepo(dep.Name, dep.Fields)
+ proj.repos[dep.Name] = depRepo
+ }
+ newRepos = append(newRepos, depRepo)
+
+ if download {
+ if _, err :=
depRepo.UpdateDesc(); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+ }
+
+ return newRepos, nil
+ }
+
+ curRepos := proj.repos.Sorted()
+ for len(curRepos) > 0 {
+ var nextRepos []*repo.Repo
+
+ for _, r := range curRepos {
+ depRepos, err := loadDeps(r)
+ if err != nil {
+ return err
+ }
+
+ nextRepos = append(nextRepos, depRepos...)
+ }
+
+ curRepos = nextRepos
+ }
+
+ return nil
+}
+
+func (proj *Project) downloadRepositoryYmlFiles() error {
+ // Download the `repository.yml` file for each root-level repo (those
+ // specified in the `project.yml` file).
+ for _, r := range proj.repos.Sorted() {
+ if !r.IsLocal() {
+ if _, err := r.UpdateDesc(); err != nil {
+ return err
+ }
+ }
+ }
+
+ // Download the `repository.yml` file for each depended-on repo.
+ if err := proj.loadRepoDeps(true); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Given a slice of repos, recursively appends all depended-on repos, ensuring
+// each element is unique.
+//
+// @param repos The list of dependent repos to process.
+// @param vm Indicates the version of each repo to consider.
+// Pass nil to consider all versions of all
+// repos.
+//
+// @return []*repo.Repo The original list, augmented with all
+// depended-on repos.
+func (proj *Project) ensureDepsInList(repos []*repo.Repo,
+ vm deprepo.VersionMap) []*repo.Repo {
+
+ seen := map[string]struct{}{}
+
+ var recurse func(r *repo.Repo) []*repo.Repo
+ recurse = func(r *repo.Repo) []*repo.Repo {
+ // Don't process this repo a second time.
+ if _, ok := seen[r.Name()]; ok {
+ return nil
+ }
+ seen[r.Name()] = struct{}{}
+
+ result := []*repo.Repo{r}
+
+ var deps []*repo.RepoDependency
+ if vm == nil {
+ deps = r.AllDeps()
+ } else {
+ deps = r.DepsForVersion(vm[r.Name()])
+ }
+ for _, d := range deps {
+ depRepo := proj.repos[d.Name]
+ result = append(result, recurse(depRepo)...)
+ }
+
+ return result
+ }
+
+ deps := []*repo.Repo{}
+ for _, r := range repos {
+ deps = append(deps, recurse(r)...)
+ }
+
+ return deps
+}
+
func (proj *Project) loadConfig() error {
yc, err := newtutil.ReadConfig(proj.BasePath,
strings.TrimSuffix(PROJECT_FILE_NAME, ".yml"))
@@ -573,13 +887,28 @@ func (proj *Project) loadConfig() error {
for k, _ := range yc.AllSettings() {
repoName := strings.TrimPrefix(k, "repository.")
if repoName != k {
- if err := proj.loadRepo(repoName, yc); err != nil {
- util.StatusMessage(util.VERBOSITY_QUIET,
- "* Warning: %s\n", err.Error())
+ fields := yc.GetValStringMapString(k, nil)
+ r, _ := proj.loadRepo(repoName, fields)
+
+ verReqs, err :=
newtutil.ParseRepoVersionReqs(fields["vers"])
+ if err != nil {
+ return util.FmtNewtError(
+ "Repo \"%s\" contains invalid version
requirement: %s (%s)",
+ repoName, fields["vers"], err.Error())
}
+
+ proj.repos[repoName] = r
+ proj.rootRepoReqs[repoName] = verReqs
}
}
+ // Read `repository.yml` files belonging to dependee repos from disk.
+ // These repos might not be specified in the `project.yml` file, but
they
+ // are still part of the project.
+ if err := proj.loadRepoDeps(false); err != nil {
+ return err
+ }
+
ignoreDirs := yc.GetValStringSlice("project.ignore_dirs", nil)
for _, ignDir := range ignoreDirs {
repoName, dirName, err := newtutil.ParsePackageString(ignDir)
@@ -612,6 +941,7 @@ func (proj *Project) Init(dir string) error {
interfaces.SetProject(proj)
proj.repos = map[string]*repo.Repo{}
+ proj.rootRepoReqs = map[string][]newtutil.RepoVersionReq{}
// Load Project configuration
if err := proj.loadConfig(); err != nil {
@@ -731,7 +1061,7 @@ func (proj *Project) loadPackageList() error {
* warning if the project state indicates that this
repo should be
* installed.
*/
- if proj.projState.installedRepos[name] != nil {
+ if _, ok := proj.projState.installedRepos[name]; ok {
util.StatusMessage(util.VERBOSITY_QUIET,
err.Error()+"\n")
}
} else {
diff --git a/newt/project/projectstate.go b/newt/project/projectstate.go
index aa6795cc..28397df0 100644
--- a/newt/project/projectstate.go
+++ b/newt/project/projectstate.go
@@ -25,23 +25,31 @@ import (
"os"
"strings"
+ "mynewt.apache.org/newt/newt/deprepo"
"mynewt.apache.org/newt/newt/interfaces"
- "mynewt.apache.org/newt/newt/repo"
+ "mynewt.apache.org/newt/newt/newtutil"
"mynewt.apache.org/newt/util"
)
const PROJECT_STATE_FILE = "project.state"
type ProjectState struct {
- installedRepos map[string]*repo.Version
+ installedRepos deprepo.VersionMap
}
-func (ps *ProjectState) GetInstalledVersion(rname string) *repo.Version {
- v, _ := ps.installedRepos[rname]
- return v
+func (ps *ProjectState) GetInstalledVersion(rname string)
*newtutil.RepoVersion {
+ v, ok := ps.installedRepos[rname]
+ if !ok {
+ return nil
+ }
+ return &v
+}
+
+func (ps *ProjectState) AllInstalledVersions() deprepo.VersionMap {
+ return ps.installedRepos
}
-func (ps *ProjectState) Replace(rname string, rvers *repo.Version) {
+func (ps *ProjectState) Replace(rname string, rvers newtutil.RepoVersion) {
ps.installedRepos[rname] = rvers
}
@@ -62,10 +70,10 @@ func (ps *ProjectState) Save() error {
for k, v := range ps.installedRepos {
str := ""
- if v.Tag() == "" {
- str = fmt.Sprintf("%s,%d.%d.%d\n", k, v.Major(),
v.Minor(), v.Revision())
+ if v.Tag == "" {
+ str = fmt.Sprintf("%s,%d.%d.%d\n", k, v.Major, v.Minor,
v.Revision)
} else {
- str = fmt.Sprintf("%s,%s-tag\n", k, v.Tag())
+ str = fmt.Sprintf("%s,%s-tag\n", k, v.Tag)
}
file.WriteString(str)
}
@@ -74,7 +82,7 @@ func (ps *ProjectState) Save() error {
}
func (ps *ProjectState) Init() error {
- ps.installedRepos = map[string]*repo.Version{}
+ ps.installedRepos = map[string]newtutil.RepoVersion{}
path := ps.StateFile()
@@ -99,7 +107,7 @@ func (ps *ProjectState) Init() error {
}
repoName := line[0]
- repoVers, err := repo.LoadVersion(line[1])
+ repoVers, err := newtutil.ParseRepoVersion(line[1])
if err != nil {
return err
}
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index 55021aaf..d049d8c2 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -20,12 +20,11 @@
package repo
import (
- "bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
- "strconv"
+ "sort"
"strings"
"time"
@@ -50,50 +49,50 @@ type Repo struct {
name string
downloader downloader.Downloader
localPath string
- versreq []interfaces.VersionReqInterface
- rdesc *RepoDesc
- deps []*RepoDependency
ignDirs []string
updated bool
local bool
ncMap compat.NewtCompatMap
-}
-type RepoDesc struct {
- name string
- vers map[*Version]string
+ // [branch] =>
+ deps map[string][]*RepoDependency
+
+ // version => branch
+ vers map[newtutil.RepoVersion]string
}
type RepoDependency struct {
- versreq []interfaces.VersionReqInterface
- name string
- Storerepo *Repo
+ Name string
+ VerReqs []newtutil.RepoVersionReq
+ Fields map[string]string
}
-func (rd *RepoDependency) String() string {
- rstr := "<"
+func (r *Repo) BranchDepMap() map[string][]*RepoDependency {
+ return r.deps
+}
- for idx, vr := range rd.versreq {
- if idx != 0 {
- rstr = rstr + " " + vr.Version().String()
- } else {
- rstr = rstr + vr.Version().String()
- }
+func (r *Repo) AllDeps() []*RepoDependency {
+ branches := make([]string, 0, len(r.deps))
+ for branch, _ := range r.deps {
+ branches = append(branches, branch)
}
- rstr = rstr + ">"
- return rstr
-}
+ sort.Strings(branches)
-func (r *Repo) Deps() []*RepoDependency {
- return r.deps
-}
+ deps := []*RepoDependency{}
+ for _, b := range branches {
+ deps = append(deps, r.deps[b]...)
+ }
-func (r *Repo) AddDependency(rd *RepoDependency) {
- r.deps = append(r.deps, rd)
+ return deps
}
-func (rd *RepoDependency) Name() string {
- return rd.name
+func (r *Repo) DepsForVersion(ver newtutil.RepoVersion) []*RepoDependency {
+ branch, err := r.BranchFromVer(ver)
+ if err != nil {
+ return nil
+ }
+
+ return r.deps[branch]
}
func (r *Repo) AddIgnoreDir(ignDir string) {
@@ -167,246 +166,6 @@ func (repo *Repo) FilteredSearchList(
return list, nil
}
-func NewRepoDependency(rname string, verstr string) (*RepoDependency, error) {
- var err error
-
- rd := &RepoDependency{}
- rd.versreq, err = LoadVersionMatches(verstr)
- if err != nil {
- return nil, err
- }
- rd.name = rname
-
- return rd, nil
-}
-
-func pickVersion(repo *Repo, versions []*Version) ([]*Version, error) {
- fmt.Printf("Dependency list for %s contains a specific commit tag, "+
- "so normal version number/stability comparison cannot be
done.\n",
- repo.Name())
- fmt.Printf("If the following list does not contain the requirement to
use, " +
- "then modify your project.yml so that it does.\n")
- for {
- for i, vers := range versions {
- fmt.Printf(" %d) %s\n", i, vers.String())
- }
- fmt.Printf("Pick the index of a version to use from above list:
")
- line, _, err := bufio.NewReader(os.Stdin).ReadLine()
- if err != nil {
- return nil, util.NewNewtError(fmt.Sprintf("Couldn't
read "+
- "response: %s", err.Error()))
- }
- idx, err := strconv.ParseUint(string(line), 10, 8)
- if err != nil {
- fmt.Printf("Error: could not parse the response.\n")
- } else {
- repo.versreq, err =
LoadVersionMatches(versions[idx].String())
- return []*Version{versions[idx]}, nil
- }
- }
-}
-
-func CheckDeps(repos []*Repo) error {
- repoMap := map[string]*Repo{}
- for _, r := range repos {
- repoMap[r.Name()] = r
- }
-
- // For each dependency, get it's version
- depArray := map[string][]*Version{}
-
- for _, checkRepo := range repoMap {
- for _, rd := range checkRepo.Deps() {
- lookupRepo := repoMap[rd.Name()]
-
- _, vers, ok := lookupRepo.rdesc.Match(rd.Storerepo)
- if !ok {
- return util.NewNewtError(fmt.Sprintf("No "+
- "matching version for dependent
repository %s", rd.name))
- }
- log.Debugf("Dependency for %s: %s (%s)",
checkRepo.Name(), rd.Name(), vers.String())
-
- _, ok = depArray[rd.Name()]
- if !ok {
- depArray[rd.Name()] = []*Version{}
- }
- depArray[rd.Name()] = append(depArray[rd.Name()], vers)
- }
- }
-
- for repoName, depVersList := range depArray {
- if len(depVersList) <= 1 {
- continue
- }
-
- pickVer := false
- for _, depVers := range depVersList {
- if depVers.Tag() != "" {
- pickVer = true
- break
- }
- }
- if pickVer {
- newArray, err := pickVersion(repoMap[repoName],
- depArray[repoName])
- depArray[repoName] = newArray
- if err != nil {
- return err
- }
- }
- }
- for repoName, depVersList := range depArray {
- for _, depVers := range depVersList {
- for _, curVers := range depVersList {
- if depVers.CompareVersions(depVers, curVers) !=
0 ||
- depVers.Stability() !=
curVers.Stability() {
- return util.NewNewtError(fmt.Sprintf(
- "Conflict detected. Repository
%s has multiple dependency versions on %s. "+
- "Notion of repository
version is %s, whereas required is %s ",
- repoName, curVers, depVers))
- }
- }
- }
- }
-
- return nil
-}
-
-func (rd *RepoDesc) MatchVersion(searchVers *Version) (string, *Version, bool)
{
- for vers, curBranch := range rd.vers {
- if vers.CompareVersions(vers, searchVers) == 0 &&
- searchVers.Stability() == vers.Stability() {
- return curBranch, vers, true
- }
- }
- return "", nil, false
-}
-
-func (rd *RepoDesc) Match(r *Repo) (string, *Version, bool) {
- log.Debugf("Requires repository version %s for %s\n",
r.VersionRequirements(),
- r.Name())
- for vers, branch := range rd.vers {
- if vers.SatisfiesVersion(r.VersionRequirements()) {
- log.Debugf("Found matching version %s for repo %s",
- vers.String(), r.Name())
- if vers.Stability() != VERSION_STABILITY_NONE {
- // Load the branch as a version, and search for
it
- searchVers, err := LoadVersion(branch)
- if err != nil {
- return "", nil, false
- }
- // Need to match the NONE stability in order to
find the right
- // branch.
- searchVers.stability = VERSION_STABILITY_NONE
-
- var ok bool
- branch, vers, ok = rd.MatchVersion(searchVers)
- if !ok {
- return "", nil, false
- }
- log.Debugf("Founding matching version %s for
search version %s, related branch is %s\n",
- vers, searchVers, branch)
-
- }
-
- return branch, vers, true
- } else {
- log.Debugf("Rejected version %s for repo %s",
- vers.String(), r.Name())
- }
- }
-
- /*
- * No match so far. See if requirements have a repository tag directly.
- * If so, then return that as the branch.
- */
- for _, versreq := range r.VersionRequirements() {
- tag := versreq.Version().Tag()
- if tag != "" {
- log.Debugf("Requirements for %s have a tag option %s\n",
- r.Name(), tag)
- return tag, NewTag(tag), true
- }
- }
- return "", nil, false
-}
-
-func (rd *RepoDesc) SatisfiesVersion(vers *Version, versReqs
[]interfaces.VersionReqInterface) bool {
- var err error
- versMatches := []interfaces.VersionReqInterface{}
- for _, versReq := range versReqs {
- versMatch := &VersionMatch{}
- versMatch.compareType = versReq.CompareType()
-
- if versReq.Version().Stability() != VERSION_STABILITY_NONE {
- // Look up this item in the RepoDescription, and get a
version
- searchVers := versReq.Version().(*Version)
- branch, _, ok := rd.MatchVersion(searchVers)
- if !ok {
- return false
- }
- versMatch.Vers, err = LoadVersion(branch)
- if err != nil {
- return false
- }
- } else {
- versMatch.Vers = versReq.Version().(*Version)
- }
-
- versMatches = append(versMatches, versMatch)
- }
-
- return vers.SatisfiesVersion(versMatches)
-}
-
-func (rd *RepoDesc) Init(name string, versBranchMap map[string]string) error {
- rd.name = name
- rd.vers = map[*Version]string{}
-
- for versStr, branch := range versBranchMap {
- log.Debugf("Printing version %s for remote repo %s", versStr,
name)
- vers, err := LoadVersion(versStr)
- if err != nil {
- return err
- }
-
- // store branch->version mapping
- rd.vers[vers] = branch
- }
-
- return nil
-}
-
-func (rd *RepoDesc) String() string {
- name := rd.name + "@"
- for k, v := range rd.vers {
- name += fmt.Sprintf("%s=%s", k.String(), v)
- name += "#"
- }
-
- return name
-}
-
-func NewRepoDesc(name string, versBranchMap map[string]string) (*RepoDesc,
error) {
- rd := &RepoDesc{}
-
- if err := rd.Init(name, versBranchMap); err != nil {
- return nil, err
- }
-
- return rd, nil
-}
-
-func (r *Repo) GetRepoDesc() (*RepoDesc, error) {
- if r.rdesc == nil {
- return nil, util.NewNewtError(fmt.Sprintf(
- "Repository description for %s not yet initialized.
Must "+
- "download it first. ", r.Name()))
- } else {
- return r.rdesc, nil
- }
-}
-
func (r *Repo) Name() string {
return r.name
}
@@ -419,19 +178,6 @@ func (r *Repo) IsLocal() bool {
return r.local
}
-func (r *Repo) VersionRequirements() []interfaces.VersionReqInterface {
- return r.versreq
-}
-
-func (r *Repo) VersionRequirementsString() string {
- str := ""
- for _, vreq := range r.versreq {
- str += vreq.String()
- }
-
- return str
-}
-
func (r *Repo) repoFilePath() string {
return interfaces.GetProject().Path() + "/" + REPOS_DIR + "/" +
".configs/" + r.name + "/"
@@ -476,7 +222,8 @@ func (r *Repo) updateRepo(branchName string) error {
dl := r.downloader
err := dl.UpdateRepo(r.Path(), branchName)
if err != nil {
- return util.FmtNewtError("Error updating: %s", err.Error())
+ return util.FmtNewtError("Error updating \"%s\": %s",
+ r.Name(), err.Error())
}
return nil
}
@@ -529,58 +276,214 @@ func (r *Repo) currentBranch() (string, error) {
return filepath.Base(branch), nil
}
-func (r *Repo) Install(force bool) (*Version, error) {
- branchName, vers, found := r.rdesc.Match(r)
- if !found {
- return nil, util.FmtNewtError("No repository "+
- "matching description %s found", r.rdesc.String())
+func (r *Repo) BranchFromVer(ver newtutil.RepoVersion) (string, error) {
+ nver, err := r.NormalizeVersion(ver)
+ if err != nil {
+ return "", err
+ }
+
+ branch := r.vers[nver]
+ if branch == "" {
+ return "",
+ util.FmtNewtError("repo \"%s\" version %s does not map
to a branch",
+ r.Name(), nver.String())
+ }
+
+ return branch, nil
+}
+
+func (r *Repo) CurrentVersion() (*newtutil.RepoVersion, error) {
+ branch, err := r.currentBranch()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, v := range r.AllVersions() {
+ if r.vers[v] == branch {
+ return &v, nil
+ }
+ }
+
+ // No matching version.
+ return nil, nil
+}
+
+func (r *Repo) CurrentNormalizedVersion() (*newtutil.RepoVersion, error) {
+ ver, err := r.CurrentVersion()
+ if err != nil {
+ return nil, err
+ }
+ if ver == nil {
+ return nil, nil
}
- if err := r.updateRepo(branchName); err != nil {
+ *ver, err = r.NormalizeVersion(*ver)
+ if err != nil {
return nil, err
}
+ return ver, nil
+}
+
+func (r *Repo) AllVersions() []newtutil.RepoVersion {
+ var vers []newtutil.RepoVersion
+ for ver, _ := range r.vers {
+ vers = append(vers, ver)
+ }
+
+ return newtutil.SortedVersions(vers)
+}
+
+func (r *Repo) NormalizedVersions() ([]newtutil.RepoVersion, error) {
+ verMap := map[newtutil.RepoVersion]struct{}{}
+
+ for ver, _ := range r.vers {
+ nver, err := r.NormalizeVersion(ver)
+ if err != nil {
+ return nil, err
+ }
+ verMap[nver] = struct{}{}
+ }
+
+ vers := make([]newtutil.RepoVersion, 0, len(verMap))
+ for ver, _ := range verMap {
+ vers = append(vers, ver)
+ }
+
return vers, nil
}
-func (r *Repo) Upgrade(force bool) (*Version, error) {
- branchName, vers, found := r.rdesc.Match(r)
- if !found {
- return nil, util.FmtNewtError("No repository "+
- "matching description %s found", r.rdesc.String())
+// Converts the specified version to its equivalent x.x.x form for this repo.
+// For example, this might convert "0-dev" to "0.0.0" (depending on the
+// `repository.yml` file contents).
+func (r *Repo) NormalizeVersion(
+ ver newtutil.RepoVersion) (newtutil.RepoVersion, error) {
+
+ origVer := ver
+ for {
+ if ver.Stability == "" ||
+ ver.Stability == newtutil.VERSION_STABILITY_NONE {
+ return ver, nil
+ }
+ verStr := r.vers[ver]
+ if verStr == "" {
+ return ver, util.FmtNewtError(
+ "cannot normalize version \"%s\" for repo
\"%s\"; "+
+ "no mapping to numeric version",
+ origVer.String(), r.Name())
+ }
+
+ nextVer, err := newtutil.ParseRepoVersion(verStr)
+ if err != nil {
+ return ver, err
+ }
+ ver = nextVer
+ }
+}
+
+// Normalizes the version component of a version requirement.
+func (r *Repo) NormalizeVerReq(verReq newtutil.RepoVersionReq) (
+ newtutil.RepoVersionReq, error) {
+
+ ver, err := r.NormalizeVersion(verReq.Ver)
+ if err != nil {
+ return verReq, err
+ }
+
+ verReq.Ver = ver
+ return verReq, nil
+}
+
+// Normalizes the version component of each specified version requirement.
+func (r *Repo) NormalizeVerReqs(verReqs []newtutil.RepoVersionReq) (
+ []newtutil.RepoVersionReq, error) {
+
+ result := make([]newtutil.RepoVersionReq, len(verReqs))
+ for i, verReq := range verReqs {
+ n, err := r.NormalizeVerReq(verReq)
+ if err != nil {
+ return nil, err
+ }
+ result[i] = n
+ }
+
+ return result, nil
+}
+
+// Compares the two specified versions for equality. Two versions are equal if
+// they ultimately map to the same branch.
+func (r *Repo) VersionsEqual(v1 newtutil.RepoVersion,
+ v2 newtutil.RepoVersion) bool {
+
+ if newtutil.CompareRepoVersions(v1, v2) == 0 {
+ return true
+ }
+
+ b1, err := r.BranchFromVer(v1)
+ if err != nil {
+ return false
+ }
+
+ b2, err := r.BranchFromVer(v2)
+ if err != nil {
+ return false
+ }
+
+ return b1 == b2
+}
+
+func (r *Repo) Install(ver newtutil.RepoVersion) error {
+ branch, err := r.BranchFromVer(ver)
+ if err != nil {
+ return err
+ }
+
+ if err := r.updateRepo(branch); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (r *Repo) Upgrade(ver newtutil.RepoVersion, force bool) error {
+ branch, err := r.BranchFromVer(ver)
+ if err != nil {
+ return err
}
changes, err := r.downloader.AreChanges(r.Path())
if err != nil {
- return nil, err
+ return err
}
if changes && !force {
- return nil, util.FmtNewtError(
+ return util.FmtNewtError(
"Repository \"%s\" contains local changes. Provide the
"+
"-f option to attempt a merge.", r.Name())
}
- if err := r.downloader.UpdateRepo(r.Path(), branchName); err != nil {
- return nil, err
+ if err := r.updateRepo(branch); err != nil {
+ return err
}
- return vers, nil
+ return nil
}
-func (r *Repo) Sync(vers *Version, force bool) (bool, error) {
- var err error
+func (r *Repo) Sync(ver newtutil.RepoVersion, force bool) (bool, error) {
var currBranch string
// Update the repo description
- if _, updated, err := r.UpdateDesc(); updated != true || err != nil {
+ if _, err := r.UpdateDesc(); err != nil {
return false, util.NewNewtError("Cannot update repository
description.")
}
- branchName, _, found := r.rdesc.MatchVersion(vers)
- if found == false {
+ branchName, err := r.BranchFromVer(ver)
+ if err != nil {
+ return false, err
+ }
+ if branchName == "" {
return false, util.FmtNewtError(
- "Branch description for %s not found", r.Name())
+ "No branch mapping for %s,%s", r.Name(), ver.String())
}
// Here assuming that if the branch was changed by the user,
@@ -596,18 +499,17 @@ func (r *Repo) Sync(vers *Version, force bool) (bool,
error) {
msg := "Unexpected local branch for %s: \"%s\" != \"%s\""
if force {
util.StatusMessage(util.VERBOSITY_DEFAULT,
- msg+"\n", r.rdesc.name, currBranch, branchName)
+ msg+"\n", r.Name(), currBranch, branchName)
} else {
- err = util.FmtNewtError(
- msg, r.rdesc.name, currBranch, branchName)
- return false, err
+ return false, util.FmtNewtError(
+ msg, r.Name(), currBranch, branchName)
}
}
// Don't try updating if on an invalid branch...
if currBranch == "HEAD" || currBranch == branchName {
util.StatusMessage(util.VERBOSITY_DEFAULT,
- "Updating repository \"%s\"... ", r.Name())
+ "Syncing repository \"%s\"... ", r.Name())
err = r.updateRepo(branchName)
if err == nil {
util.StatusMessage(util.VERBOSITY_DEFAULT, "success\n")
@@ -639,27 +541,26 @@ func (r *Repo) Sync(vers *Version, force bool) (bool,
error) {
return true, nil
}
-func (r *Repo) UpdateDesc() ([]*Repo, bool, error) {
+func (r *Repo) UpdateDesc() (bool, error) {
var err error
if r.updated {
- return nil, false, nil
+ return false, nil
}
util.StatusMessage(util.VERBOSITY_VERBOSE, "[%s]:\n", r.Name())
if err = r.DownloadDesc(); err != nil {
- return nil, false, err
+ return false, err
}
- _, repos, err := r.ReadDesc()
- if err != nil {
- return nil, false, err
+ if err := r.Read(); err != nil {
+ return false, err
}
r.updated = true
- return repos, true, nil
+ return true, nil
}
func (r *Repo) downloadRepositoryYml() error {
@@ -721,92 +622,109 @@ func (r *Repo) DownloadDesc() error {
return nil
}
-func (r *Repo) readDepRepos(yc ycfg.YCfg) ([]*Repo, error) {
- rdesc := r.rdesc
- repos := []*Repo{}
+func parseRepoDepMap(depName string,
+ repoMapYml interface{}) (map[string]*RepoDependency, error) {
- branch, _, ok := rdesc.Match(r)
+ result := map[string]*RepoDependency{}
+
+ tlMap, err := cast.ToStringMapE(repoMapYml)
+ if err != nil {
+ return nil, util.FmtNewtError("missing \"repository.yml\" file")
+ }
+
+ versYml, ok := tlMap["vers"]
if !ok {
- // No matching branch, barf!
- return nil, util.FmtNewtError("No "+
- "matching branch for %s repo", r.Name())
+ return nil, util.FmtNewtError("missing \"vers\" map")
}
- repoTag := fmt.Sprintf("%s.repositories", branch)
+ versMap, err := cast.ToStringMapStringE(versYml)
+ if !ok {
+ return nil, util.FmtNewtError("invalid \"vers\" map")
+ }
- repoList := yc.GetValStringMap(repoTag, nil)
- for repoName, repoItf := range repoList {
- repoVars := cast.ToStringMapString(repoItf)
+ fields := map[string]string{}
+ for k, v := range tlMap {
+ if s, ok := v.(string); ok {
+ fields[k] = s
+ }
+ }
- dl, err := downloader.LoadDownloader(repoName, repoVars)
+ for branch, verReqsStr := range versMap {
+ verReqs, err := newtutil.ParseRepoVersionReqs(verReqsStr)
if err != nil {
- return nil, err
+ return nil, util.FmtNewtError("invalid version string:
%s",
+ verReqsStr)
}
- rversreq := repoVars["vers"]
- newRepo, err := NewRepo(repoName, rversreq, dl)
- if err != nil {
- return nil, err
+ result[branch] = &RepoDependency{
+ Name: depName,
+ VerReqs: verReqs,
+ Fields: fields,
}
+ }
- rd, err := NewRepoDependency(repoName, rversreq)
+ return result, nil
+}
+
+func (r *Repo) readDepRepos(yc ycfg.YCfg) error {
+ depMap := yc.GetValStringMap("repo.deps", nil)
+ for depName, repoMapYml := range depMap {
+ rdm, err := parseRepoDepMap(depName, repoMapYml)
if err != nil {
- return nil, err
+ return util.FmtNewtError(
+ "Error while parsing 'repo.deps' for repo
\"%s\", "+
+ "dependency \"%s\": %s", r.Name(),
depName, err.Error())
}
- rd.Storerepo = newRepo
- r.AddDependency(rd)
-
- repos = append(repos, newRepo)
+ for branch, dep := range rdm {
+ r.deps[branch] = append(r.deps[branch], dep)
+ }
}
- return repos, nil
+
+ return nil
}
-func (r *Repo) ReadDesc() (*RepoDesc, []*Repo, error) {
- if util.NodeNotExist(r.repoFilePath() + REPO_FILE_NAME) {
- return nil, nil,
- util.NewNewtError("No configuration exists for
repository " + r.name)
- }
+// Reads a `repository.yml` file and populates the receiver repo with its
+// contents.
+func (r *Repo) Read() error {
+ r.Init(r.Name(), r.downloader)
yc, err := newtutil.ReadConfig(r.repoFilePath(),
strings.TrimSuffix(REPO_FILE_NAME, ".yml"))
if err != nil {
- return nil, nil, err
+ return err
}
- name := yc.GetValString("repo.name", nil)
versMap := yc.GetValStringMapString("repo.versions", nil)
+ for versStr, branch := range versMap {
+ log.Debugf("Printing version %s for remote repo %s", versStr,
r.name)
+ vers, err := newtutil.ParseRepoVersion(versStr)
+ if err != nil {
+ return err
+ }
- rdesc, err := NewRepoDesc(name, versMap)
- if err != nil {
- return nil, nil, err
+ // store branch->version mapping
+ r.vers[vers] = branch
}
- r.rdesc = rdesc
- repos, err := r.readDepRepos(yc)
- if err != nil {
- return nil, nil, err
+ if err := r.readDepRepos(yc); err != nil {
+ return err
}
// Read the newt version compatibility map.
r.ncMap, err = compat.ReadNcMap(yc)
if err != nil {
- return nil, nil, err
+ return err
}
- return rdesc, repos, nil
+ return nil
}
-func (r *Repo) Init(repoName string, rversreq string, d downloader.Downloader)
error {
- var err error
-
+func (r *Repo) Init(repoName string, d downloader.Downloader) error {
r.name = repoName
r.downloader = d
- r.deps = []*RepoDependency{}
- r.versreq, err = LoadVersionMatches(rversreq)
- if err != nil {
- return err
- }
+ r.deps = map[string][]*RepoDependency{}
+ r.vers = map[newtutil.RepoVersion]string{}
path := interfaces.GetProject().Path()
@@ -819,7 +737,8 @@ func (r *Repo) Init(repoName string, rversreq string, d
downloader.Downloader) e
return nil
}
-func (r *Repo) CheckNewtCompatibility(rvers *Version, nvers newtutil.Version) (
+func (r *Repo) CheckNewtCompatibility(
+ rvers newtutil.RepoVersion, nvers newtutil.Version) (
compat.NewtCompatCode, string) {
// If this repo doesn't have a newt compatibility map, just assume
there is
@@ -846,12 +765,12 @@ func (r *Repo) CheckNewtCompatibility(rvers *Version,
nvers newtutil.Version) (
nvers.String(), r.name, rnuver.String(), text)
}
-func NewRepo(repoName string, rversreq string, d downloader.Downloader)
(*Repo, error) {
+func NewRepo(repoName string, d downloader.Downloader) (*Repo, error) {
r := &Repo{
local: false,
}
- if err := r.Init(repoName, rversreq, d); err != nil {
+ if err := r.Init(repoName, d); err != nil {
return nil, err
}
@@ -863,7 +782,7 @@ func NewLocalRepo(repoName string) (*Repo, error) {
local: true,
}
- if err := r.Init(repoName, "", nil); err != nil {
+ if err := r.Init(repoName, nil); err != nil {
return nil, err
}
diff --git a/newt/repo/version.go b/newt/repo/version.go
deleted file mode 100644
index 2dc723f4..00000000
--- a/newt/repo/version.go
+++ /dev/null
@@ -1,304 +0,0 @@
-/**
- * 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.
- */
-
-package repo
-
-import (
- "fmt"
- "regexp"
- "strconv"
- "strings"
-
- "mynewt.apache.org/newt/newt/interfaces"
- "mynewt.apache.org/newt/newt/newtutil"
- "mynewt.apache.org/newt/util"
-
- log "github.com/Sirupsen/logrus"
-)
-
-const (
- VERSION_STABILITY_NONE = "none"
- VERSION_STABILITY_STABLE = "stable"
- VERSION_STABILITY_DEV = "dev"
- VERSION_STABILITY_LATEST = "latest"
- VERSION_STABILITY_TAG = "tag"
-)
-
-// Represents an unspecified part in a version. For example, in "1-latest",
-// the minor and revision parts are floating.
-const VERSION_FLOATING = -1
-
-type VersionMatch struct {
- compareType string
- Vers *Version
-}
-
-type Version struct {
- major int64
- minor int64
- revision int64
- stability string
- tag string
-}
-
-func (vm *VersionMatch) CompareType() string {
- return vm.compareType
-}
-
-func (vm *VersionMatch) Version() interfaces.VersionInterface {
- return vm.Vers
-}
-
-func (vm *VersionMatch) String() string {
- return vm.compareType + vm.Vers.String()
-}
-
-func (v *Version) Major() int64 {
- return v.major
-}
-
-func (v *Version) Minor() int64 {
- return v.minor
-}
-
-func (v *Version) Revision() int64 {
- return v.revision
-}
-
-func (v *Version) Stability() string {
- return v.stability
-}
-
-func (v *Version) Tag() string {
- return v.tag
-}
-
-func (v *Version) CompareVersions(vers1 interfaces.VersionInterface,
- vers2 interfaces.VersionInterface) int64 {
- if r := vers1.Major() - vers2.Major(); r != 0 {
- return r
- }
-
- if r := vers1.Minor() - vers2.Minor(); r != 0 {
- return r
- }
-
- if r := vers1.Revision() - vers2.Revision(); r != 0 {
- return r
- }
-
- if vers1.Tag() != vers2.Tag() {
- return 1
- }
-
- return 0
-}
-
-func (v *Version) SatisfiesVersion(versMatches
[]interfaces.VersionReqInterface) bool {
- if len(versMatches) == 0 {
- return false
- }
-
- for _, match := range versMatches {
- if match.Version().Tag() != "" && match.CompareType() != "==" {
- log.Warningf("Version comparison with a tag %s %s %s",
- match.Version(), match.CompareType(), v)
- }
- r := v.CompareVersions(match.Version(), v)
- switch match.CompareType() {
- case "<":
- if r <= 0 {
- return false
- }
- case "<=":
- if r < 0 {
- return false
- }
- case ">":
- if r >= 0 {
- return false
- }
- case ">=":
- if r > 0 {
- return false
- }
- case "==":
- if r != 0 {
- return false
- }
- }
-
- if match.Version().Stability() != v.Stability() {
- return false
- }
- }
-
- return true
-}
-
-func (vers *Version) String() string {
- if vers.tag != "" {
- return fmt.Sprintf("%s-tag", vers.tag)
- }
-
- s := fmt.Sprintf("%d", vers.Major())
- if vers.Minor() != VERSION_FLOATING {
- s += fmt.Sprintf(".%d", vers.Minor())
- }
- if vers.Revision() != VERSION_FLOATING {
- s += fmt.Sprintf(".%d", vers.Revision())
- }
-
- s += fmt.Sprintf("-%s", vers.Stability())
-
- return s
-}
-
-func (vers *Version) ToNuVersion() newtutil.Version {
- return newtutil.Version{
- Major: vers.major,
- Minor: vers.minor,
- Revision: vers.revision,
- }
-}
-
-func LoadVersion(versStr string) (*Version, error) {
- var err error
-
- // Split to get stability level first
- sparts := strings.Split(versStr, "-")
- stability := VERSION_STABILITY_NONE
- if len(sparts) > 1 {
- stability = strings.Trim(sparts[1], " ")
- switch stability {
- case VERSION_STABILITY_TAG:
- return NewTag(strings.Trim(sparts[0], " ")), nil
- case VERSION_STABILITY_STABLE:
- fallthrough
- case VERSION_STABILITY_DEV:
- fallthrough
- case VERSION_STABILITY_LATEST:
- default:
- return nil, util.FmtNewtError(
- "Unknown stability (%s) in version %s",
stability, versStr)
- }
- }
- parts := strings.Split(sparts[0], ".")
- if len(parts) > 3 {
- return nil, util.FmtNewtError("Invalid version string: %s",
versStr)
- }
-
- if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[0], " ") ==
"none" {
- return nil, nil
- }
-
- // Assume no parts of the version are specified.
- vers := &Version{
- major: VERSION_FLOATING,
- minor: VERSION_FLOATING,
- revision: VERSION_FLOATING,
- }
- vers.stability = stability
-
- // convert first string to an int
- if vers.major, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
- return nil, util.NewNewtError(err.Error())
- }
- if len(parts) >= 2 {
- if vers.minor, err = strconv.ParseInt(parts[1], 10, 64); err !=
nil {
- return nil, util.NewNewtError(err.Error())
- }
- }
- if len(parts) == 3 {
- if vers.revision, err = strconv.ParseInt(parts[2], 10, 64); err
!= nil {
- return nil, util.NewNewtError(err.Error())
- }
- }
-
- return vers, nil
-}
-
-func NewVersion(major int64, minor int64, rev int64) *Version {
- vers := &Version{}
-
- vers.major = major
- vers.minor = minor
- vers.revision = rev
- vers.tag = ""
-
- return vers
-}
-
-func NewTag(tag string) *Version {
- vers := &Version{}
- vers.tag = tag
- vers.stability = VERSION_STABILITY_NONE
-
- return vers
-}
-
-// Parse a set of version string constraints on a dependency.
-// This function
-// The version string contains a list of version constraints in the following
format:
-// - <comparison><version>
-// Where <comparison> can be any one of the following comparison
-// operators: <=, <, >, >=, ==
-// And <version> is specified in the form: X.Y.Z where X, Y and Z are all
-// int64 types in decimal form
-func LoadVersionMatches(versStr string) ([]interfaces.VersionReqInterface,
error) {
- var err error
-
- versMatches := []interfaces.VersionReqInterface{}
-
- re, err := regexp.Compile(`(<=|>=|==|>|<)([\d\.]+)`)
- if err != nil {
- return nil, err
- }
-
- matches := re.FindAllStringSubmatch(versStr, -1)
- if matches != nil {
- for _, match := range matches {
- vm := &VersionMatch{}
- vm.compareType = match[1]
- if vm.Vers, err = LoadVersion(match[2]); err != nil {
- return nil, err
- }
-
- if vm.Vers != nil {
- versMatches = append(versMatches, vm)
- }
- }
- } else {
- vm := &VersionMatch{}
- vm.compareType = "=="
- if vm.Vers, err = LoadVersion(versStr); err != nil {
- return nil, err
- }
-
- if vm.Vers != nil {
- versMatches = append(versMatches, vm)
- }
- }
-
- if len(versMatches) == 0 {
- versMatches = nil
- }
-
- return versMatches, nil
-}
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services