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