MYNEWT-655 newt - Detect repo/newt ver incompat.

This is done with a separate map "repo.newt_compatibility" in the repo's
repository.yml file.

Example:

repo.newt_compatibility:
    1.0.0:
        1.1.0: error
        1.0.0: good
        0.9.99: warn
        0.9.9: error

This says that this version 1.0.0 of this repo is compatible with newt
versions [1.0.0, 1.1.0). Newt versions in the range [0.9.99, 1.0.0)
elicit a warning message. All other newt versions generate an error.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/f264e676
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/f264e676
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/f264e676

Branch: refs/heads/master
Commit: f264e6767fbfd26ff24f31b55e63fd8b189daa9f
Parents: 1fb9697
Author: Christopher Collins <ccoll...@apache.org>
Authored: Tue Mar 7 12:11:12 2017 -0800
Committer: Christopher Collins <ccoll...@apache.org>
Committed: Tue Mar 7 12:22:06 2017 -0800

----------------------------------------------------------------------
 newt/newtutil/newtutil.go |  71 ++++++++---
 newt/project/project.go   |  18 +++
 newt/repo/compat.go       | 284 +++++++++++++++++++++++++++++++++++++++++
 newt/repo/repo.go         | 114 +++++------------
 newt/repo/version.go      |   9 ++
 5 files changed, 397 insertions(+), 99 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/f264e676/newt/newtutil/newtutil.go
----------------------------------------------------------------------
diff --git a/newt/newtutil/newtutil.go b/newt/newtutil/newtutil.go
index 48ef4a0..98307ec 100644
--- a/newt/newtutil/newtutil.go
+++ b/newt/newtutil/newtutil.go
@@ -34,6 +34,7 @@ import (
        "mynewt.apache.org/newt/viper"
 )
 
+var NewtVersion Version = Version{1, 0, 0}
 var NewtVersionStr string = "Apache Newt (incubating) version: 1.0.0-dev"
 var NewtBlinkyTag string = "develop"
 var NewtNumJobs int
@@ -45,27 +46,59 @@ const REPOS_FILENAME string = "repos.yml"
 const CORE_REPO_NAME string = "apache-mynewt-core"
 const ARDUINO_ZERO_REPO_NAME string = "mynewt_arduino_zero"
 
-type RepoCommitEntry struct {
-       Version     string
-       Hash        string
-       Description string
+type Version struct {
+       Major    int64
+       Minor    int64
+       Revision int64
 }
 
-// A warning is displayed if newt requires a newer version of a repo.
-var RepoMinCommits = map[string]*RepoCommitEntry{
-       // Newt no longer cd's to a source directory when it compiles its 
contents.
-       // Consequently, package include flags need to be relative to the 
project
-       // directory, not the package source directory.
-       CORE_REPO_NAME: &RepoCommitEntry{
-               Version:     "develop",
-               Hash:        "cd99344df197d5b9e372b93142184a39ec078f69",
-               Description: "Include paths now relative to project base.",
-       },
-       ARDUINO_ZERO_REPO_NAME: &RepoCommitEntry{
-               Version:     "develop",
-               Hash:        "a6348961fef56dbfe09a1b9418d3add3ad22eaf2",
-               Description: "Include paths now relative to project base.",
-       },
+func ParseVersion(s string) (Version, error) {
+       v := Version{}
+       parseErr := util.FmtNewtError("Invalid version string: %s", s)
+
+       parts := strings.Split(s, ".")
+       if len(parts) != 3 {
+               return v, parseErr
+       }
+
+       var err error
+
+       v.Major, err = strconv.ParseInt(parts[0], 10, 64)
+       if err != nil {
+               return v, parseErr
+       }
+
+       v.Minor, err = strconv.ParseInt(parts[1], 10, 64)
+       if err != nil {
+               return v, parseErr
+       }
+
+       v.Revision, err = strconv.ParseInt(parts[2], 10, 64)
+       if err != nil {
+               return v, parseErr
+       }
+
+       return v, nil
+}
+
+func (v *Version) String() string {
+       return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Revision)
+}
+
+func VerCmp(v1 Version, v2 Version) 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
+       }
+
+       return 0
 }
 
 // Contains general newt settings read from $HOME/.newt

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/f264e676/newt/project/project.go
----------------------------------------------------------------------
diff --git a/newt/project/project.go b/newt/project/project.go
index cc68c30..bf4b00e 100644
--- a/newt/project/project.go
+++ b/newt/project/project.go
@@ -416,6 +416,24 @@ func (proj *Project) loadRepo(rname string, v 
*viper.Viper) error {
 
        proj.localRepo.AddDependency(rd)
 
+       // Read the repo's descriptor file so that we have its newt version
+       // compatibility map.
+       _, _, err = r.ReadDesc()
+       if err != nil {
+               return util.FmtNewtError("Failed to read repo descriptor; %s",
+                       err.Error())
+       }
+
+       rvers := proj.projState.GetInstalledVersion(rname)
+       code, msg := r.CheckNewtCompatibility(rvers, newtutil.NewtVersion)
+       switch code {
+       case repo.NEWT_COMPAT_GOOD:
+       case repo.NEWT_COMPAT_WARN:
+               util.StatusMessage(util.VERBOSITY_QUIET, "WARNING: %s.\n", msg)
+       case repo.NEWT_COMPAT_ERROR:
+               return util.NewNewtError(msg)
+       }
+
        log.Debugf("Loaded repository %s (type: %s, user: %s, repo: %s)", rname,
                repoVars["type"], repoVars["user"], repoVars["repo"])
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/f264e676/newt/repo/compat.go
----------------------------------------------------------------------
diff --git a/newt/repo/compat.go b/newt/repo/compat.go
new file mode 100644
index 0000000..cd9fc82
--- /dev/null
+++ b/newt/repo/compat.go
@@ -0,0 +1,284 @@
+/**
+ * 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"
+       "math"
+       "sort"
+
+       "github.com/spf13/cast"
+
+       "mynewt.apache.org/newt/newt/newtutil"
+       "mynewt.apache.org/newt/util"
+       "mynewt.apache.org/newt/viper"
+)
+
+type NewtCompatCode int
+
+const (
+       NEWT_COMPAT_GOOD NewtCompatCode = iota
+       NEWT_COMPAT_WARN
+       NEWT_COMPAT_ERROR
+)
+
+var NewtCompatCodeNames = map[NewtCompatCode]string{
+       NEWT_COMPAT_GOOD:  "good",
+       NEWT_COMPAT_WARN:  "warn",
+       NEWT_COMPAT_ERROR: "error",
+}
+
+type NewtCompatEntry struct {
+       code       NewtCompatCode
+       minNewtVer newtutil.Version
+}
+
+type NewtCompatTable struct {
+       // Sorted in ascending order by newt version number.
+       entries []NewtCompatEntry
+}
+
+type NewtCompatMap struct {
+       verTableMap map[newtutil.Version]NewtCompatTable
+}
+
+func newNewtCompatMap() *NewtCompatMap {
+       return &NewtCompatMap{
+               verTableMap: map[newtutil.Version]NewtCompatTable{},
+       }
+}
+
+func newtCompatCodeToString(code NewtCompatCode) string {
+       return NewtCompatCodeNames[code]
+}
+
+func newtCompatCodeFromString(codeStr string) (NewtCompatCode, error) {
+       for c, s := range NewtCompatCodeNames {
+               if codeStr == s {
+                       return c, nil
+               }
+       }
+
+       return NewtCompatCode(0),
+               util.FmtNewtError("Invalid newt compatibility code: %s", 
codeStr)
+}
+
+func parseNcEntry(verStr string, codeStr string) (NewtCompatEntry, error) {
+       entry := NewtCompatEntry{}
+       var err error
+
+       entry.minNewtVer, err = newtutil.ParseVersion(verStr)
+       if err != nil {
+               return entry, err
+       }
+
+       entry.code, err = newtCompatCodeFromString(codeStr)
+       if err != nil {
+               return entry, err
+       }
+
+       return entry, nil
+}
+
+func parseNcTable(strMap map[string]string) (NewtCompatTable, error) {
+       tbl := NewtCompatTable{}
+
+       for c, v := range strMap {
+               entry, err := parseNcEntry(c, v)
+               if err != nil {
+                       return tbl, err
+               }
+
+               tbl.entries = append(tbl.entries, entry)
+       }
+
+       sortEntries(tbl.entries)
+
+       return tbl, nil
+}
+
+func readNcMap(v *viper.Viper) (*NewtCompatMap, error) {
+       mp := newNewtCompatMap()
+       ncMap := v.GetStringMap("repo.newt_compatibility")
+
+       for k, v := range ncMap {
+               repoVer, err := newtutil.ParseVersion(k)
+               if err != nil {
+                       return nil, util.FmtNewtError("Newt compatibility table 
contains " +
+                               "invalid repo version \"%s\"")
+               }
+
+               if _, ok := mp.verTableMap[repoVer]; ok {
+                       return nil, util.FmtNewtError("Newt compatibility table 
contains "+
+                               "duplicate version specifier: %s", 
repoVer.String())
+               }
+
+               strMap := cast.ToStringMapString(v)
+               tbl, err := parseNcTable(strMap)
+               if err != nil {
+                       return nil, err
+               }
+
+               mp.verTableMap[repoVer] = tbl
+       }
+
+       return mp, nil
+}
+
+func (tbl *NewtCompatTable) matchIdx(newtVer newtutil.Version) int {
+       // Iterate the table backwards.  The first entry whose version is less 
than
+       // or equal to the specified version is the match.
+       for i := 0; i < len(tbl.entries); i++ {
+               idx := len(tbl.entries) - i - 1
+               entry := &tbl.entries[idx]
+               cmp := newtutil.VerCmp(entry.minNewtVer, newtVer)
+               if cmp <= 0 {
+                       return idx
+               }
+       }
+
+       return -1
+}
+
+func (tbl *NewtCompatTable) newIdxRange(i int, j int) []int {
+       if i >= len(tbl.entries) {
+               return []int{j, i}
+       }
+
+       if j >= len(tbl.entries) {
+               return []int{i, j}
+       }
+
+       e1 := tbl.entries[i]
+       e2 := tbl.entries[j]
+
+       if newtutil.VerCmp(e1.minNewtVer, e2.minNewtVer) < 0 {
+               return []int{i, j}
+       } else {
+               return []int{j, i}
+       }
+}
+
+func (tbl *NewtCompatTable) idxRangesWithCode(c NewtCompatCode) [][]int {
+       ranges := [][]int{}
+
+       curi := -1
+       for i, e := range tbl.entries {
+               if curi == -1 {
+                       if e.code == c {
+                               curi = i
+                       }
+               } else {
+                       if e.code != c {
+                               ranges = append(ranges, tbl.newIdxRange(curi, 
i))
+                               curi = -1
+                       }
+               }
+       }
+
+       if curi != -1 {
+               ranges = append(ranges, tbl.newIdxRange(curi, len(tbl.entries)))
+       }
+       return ranges
+}
+
+func (tbl *NewtCompatTable) minMaxTgtVers(goodRange []int) (
+       newtutil.Version, newtutil.Version, newtutil.Version) {
+
+       minVer := tbl.entries[goodRange[0]].minNewtVer
+
+       var maxVer newtutil.Version
+       if goodRange[1] < len(tbl.entries) {
+               maxVer = tbl.entries[goodRange[1]].minNewtVer
+       } else {
+               maxVer = newtutil.Version{math.MaxInt64, math.MaxInt64, 
math.MaxInt64}
+       }
+
+       targetVer := tbl.entries[goodRange[1]-1].minNewtVer
+
+       return minVer, maxVer, targetVer
+}
+
+// @return NewtCompatCode       The severity of the newt incompatibility
+//         string               The warning or error message to display in case
+//                                  of incompatibility.
+func (tbl *NewtCompatTable) CheckNewtVer(
+       newtVer newtutil.Version) (NewtCompatCode, string) {
+
+       var code NewtCompatCode
+       idx := tbl.matchIdx(newtVer)
+       if idx == -1 {
+               // This version of newt is older than every entry in the table.
+               code = NEWT_COMPAT_ERROR
+       } else {
+               code = tbl.entries[idx].code
+               if code == NEWT_COMPAT_GOOD {
+                       return NEWT_COMPAT_GOOD, ""
+               }
+       }
+
+       goodRanges := tbl.idxRangesWithCode(NEWT_COMPAT_GOOD)
+       for i := 0; i < len(goodRanges); i++ {
+               minVer, maxVer, tgtVer := tbl.minMaxTgtVers(goodRanges[i])
+
+               if newtutil.VerCmp(newtVer, minVer) < 0 {
+                       return code, fmt.Sprintf("Please upgrade your newt tool 
to "+
+                               "version %s", tgtVer.String())
+               }
+
+               if newtutil.VerCmp(newtVer, maxVer) >= 0 {
+                       return code, "Please upgrade your repos with \"newt 
upgrade\""
+               }
+       }
+
+       return code, ""
+}
+
+type entrySorter struct {
+       entries []NewtCompatEntry
+}
+
+func (s entrySorter) Len() int {
+       return len(s.entries)
+}
+func (s entrySorter) Swap(i, j int) {
+       s.entries[i], s.entries[j] = s.entries[j], s.entries[i]
+}
+func (s entrySorter) Less(i, j int) bool {
+       e1 := s.entries[i]
+       e2 := s.entries[j]
+
+       cmp := newtutil.VerCmp(e1.minNewtVer, e2.minNewtVer)
+       if cmp < 0 {
+               return true
+       } else if cmp > 0 {
+               return false
+       }
+
+       return false
+}
+
+func sortEntries(entries []NewtCompatEntry) {
+       sorter := entrySorter{
+               entries: entries,
+       }
+
+       sort.Sort(sorter)
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/f264e676/newt/repo/repo.go
----------------------------------------------------------------------
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index a2331f7..a534ea3 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -52,10 +52,7 @@ type Repo struct {
        ignDirs    []string
        updated    bool
        local      bool
-
-       // The minimim git commit the repo must have to interoperate with this
-       // version of newt.
-       minCommit *newtutil.RepoCommitEntry
+       ncMap      *NewtCompatMap
 }
 
 type RepoDesc struct {
@@ -475,8 +472,8 @@ func (r *Repo) DownloadDesc() error {
        }
 
        dl.SetBranch("master")
-       if err := dl.FetchFile("repository.yml",
-               cpath+"/"+"repository.yml"); err != nil {
+       if err := dl.FetchFile(REPO_FILE_NAME,
+               cpath+"/"+REPO_FILE_NAME); err != nil {
                util.StatusMessage(util.VERBOSITY_VERBOSE, " failed\n")
                return err
        }
@@ -553,60 +550,13 @@ func (r *Repo) ReadDesc() (*RepoDesc, []*Repo, error) {
                return nil, nil, err
        }
 
-       return rdesc, repos, nil
-}
-
-// Checks if the specified repo is compatible with this version of newt.  This
-// function only verifies that the repo is new enough; it doesn't check that
-// newt is new enough.
-func (r *Repo) HasMinCommit() (bool, error) {
-       if r.minCommit == nil {
-               return true, nil
-       }
-
-       // Change back to the initial directory when this function returns.
-       cwd, err := os.Getwd()
-       if err != nil {
-               return false, util.ChildNewtError(err)
-       }
-       defer os.Chdir(cwd)
-
-       if err := os.Chdir(r.localPath); err != nil {
-               return false, util.ChildNewtError(err)
-       }
-
-       cmd := []string{
-               "git",
-               "merge-base",
-               r.minCommit.Hash,
-               "HEAD",
-       }
-       mergeBase, err := util.ShellCommand(cmd, nil)
-       if err != nil {
-               if strings.Contains(err.Error(), "Not a valid commit name") {
-                       return false, nil
-               } else {
-                       return false, util.ChildNewtError(err)
-               }
-       }
-       if len(mergeBase) == 0 {
-               // No output means the commit does not exist in the current 
branch.
-               return false, nil
-       }
-
-       cmd = []string{
-               "git",
-               "rev-parse",
-               "--verify",
-               r.minCommit.Hash,
-       }
-       revParse, err := util.ShellCommand(cmd, nil)
+       // Read the newt version compatibility map.
+       r.ncMap, err = readNcMap(v)
        if err != nil {
-               return false, util.ChildNewtError(err)
+               return nil, nil, err
        }
 
-       // The commit exists in HEAD if the two commands produced identical 
output.
-       return string(mergeBase) == string(revParse), nil
+       return rdesc, repos, nil
 }
 
 func (r *Repo) Init(repoName string, rversreq string, d downloader.Downloader) 
error {
@@ -626,38 +576,41 @@ func (r *Repo) Init(repoName string, rversreq string, d 
downloader.Downloader) e
                r.localPath = filepath.ToSlash(filepath.Clean(path))
        } else {
                r.localPath = filepath.ToSlash(filepath.Clean(path + "/" + 
REPOS_DIR + "/" + r.name))
-               r.minCommit = newtutil.RepoMinCommits[repoName]
+       }
 
-               upToDate, err := r.HasMinCommit()
-               if err != nil {
-                       // If there is an error checking the repo's commit log, 
just abort
-                       // the check.  An error could have many possible 
causes: repo not
-                       // installed, network issue, etc.  In none of these 
cases does it
-                       // makes sense to warn about an out of date repo.  If 
the issue is
-                       // a real one, it will be handled when it prevents 
forward
-                       // progress.
-                       return nil
-               }
+       return nil
+}
 
-               if !upToDate {
-                       util.ErrorMessage(util.VERBOSITY_QUIET,
-                               "Warning: repo \"%s\" is out of date for this 
version of "+
-                                       "newt.  Please upgrade the repo to meet 
these "+
-                                       "requirements:\n"+
-                                       "    * Version: %s\n"+
-                                       "    * Commit: %s\n"+
-                                       "    * Change: %s\n",
-                               r.name, r.minCommit.Version, r.minCommit.Hash,
-                               r.minCommit.Description)
-               }
+func (r *Repo) CheckNewtCompatibility(rvers *Version, nvers newtutil.Version) (
+       NewtCompatCode, string) {
+
+       // If this repo doesn't have a newt compatibility map, just assume 
there is
+       // no incompatibility.
+       if len(r.ncMap.verTableMap) == 0 {
+               return NEWT_COMPAT_GOOD, ""
        }
 
-       return nil
+       rnuver := rvers.ToNuVersion()
+       tbl, ok := r.ncMap.verTableMap[rnuver]
+       if !ok {
+               // Unknown repo version.
+               return NEWT_COMPAT_WARN, "Repo version missing from 
compatibility map"
+       }
+
+       code, text := tbl.CheckNewtVer(nvers)
+       if code == NEWT_COMPAT_GOOD {
+               return code, text
+       }
+
+       return code, fmt.Sprintf("This version of newt (%s) is incompatible 
with "+
+               "your version of the %s repo (%s); %s",
+               nvers.String(), r.name, rnuver.String(), text)
 }
 
 func NewRepo(repoName string, rversreq string, d downloader.Downloader) 
(*Repo, error) {
        r := &Repo{
                local: false,
+               ncMap: newNewtCompatMap(),
        }
 
        if err := r.Init(repoName, rversreq, d); err != nil {
@@ -670,6 +623,7 @@ func NewRepo(repoName string, rversreq string, d 
downloader.Downloader) (*Repo,
 func NewLocalRepo(repoName string) (*Repo, error) {
        r := &Repo{
                local: true,
+               ncMap: newNewtCompatMap(),
        }
 
        if err := r.Init(repoName, "", nil); err != nil {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/f264e676/newt/repo/version.go
----------------------------------------------------------------------
diff --git a/newt/repo/version.go b/newt/repo/version.go
index ce81ef2..bbf5b56 100644
--- a/newt/repo/version.go
+++ b/newt/repo/version.go
@@ -26,6 +26,7 @@ import (
        "strings"
 
        "mynewt.apache.org/newt/newt/interfaces"
+       "mynewt.apache.org/newt/newt/newtutil"
        "mynewt.apache.org/newt/util"
 )
 
@@ -137,6 +138,14 @@ func (vers *Version) String() string {
        return fmt.Sprintf(VERSION_FORMAT, vers.Major(), vers.Minor(), 
vers.Revision(), vers.Stability())
 }
 
+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
 

Reply via email to