blob: ea6bac678b00ebd632e19ae04c3daafedfe488a9 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package newtutil
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"mynewt.apache.org/newt/util"
log "github.com/Sirupsen/logrus"
)
const (
VERSION_STABILITY_NONE = ""
VERSION_STABILITY_STABLE = "stable"
VERSION_STABILITY_DEV = "dev"
VERSION_STABILITY_LATEST = "latest"
// "commit" is not actually a stability, but it takes the place of one in
// the repo version notation. The "commit" string indicates a commit hash,
// tag, or branch, rather than a version specifier.
VERSION_STABILITY_COMMIT = "commit"
)
// 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
Commit string
}
func (v *RepoVersion) IsNormalized() bool {
return v.Stability == VERSION_STABILITY_NONE
}
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
}
return 0
}
func (v *RepoVersion) Satisfies(verReq RepoVersionReq) bool {
if verReq.Ver.Commit != "" && 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 {
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)
}
if ver.Commit != "" {
s += fmt.Sprintf("/%s", ver.Commit)
}
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
stability := VERSION_STABILITY_NONE
base := verStr
dashIdx := strings.LastIndex(verStr, "-")
if dashIdx != -1 {
stability = strings.TrimSpace(verStr[dashIdx+1:])
base = strings.TrimSpace(verStr[:dashIdx])
switch stability {
case VERSION_STABILITY_COMMIT:
return RepoVersion{Commit: strings.TrimSpace(base)}, nil
case VERSION_STABILITY_STABLE:
case VERSION_STABILITY_DEV:
case VERSION_STABILITY_LATEST:
default:
return RepoVersion{}, util.FmtNewtError(
"Unknown stability (%s) in version %s", stability, verStr)
}
}
parts := strings.Split(base, ".")
if len(parts) > 3 {
return RepoVersion{},
util.FmtNewtError("Invalid version string: %s", verStr)
}
if len(parts) != 3 && stability == VERSION_STABILITY_NONE {
return RepoVersion{},
util.FmtNewtError("Invalid version string: %s", verStr)
}
// Assume no parts of the version are specified.
ver := RepoVersion{
Major: VERSION_FLOATING,
Minor: VERSION_FLOATING,
Revision: VERSION_FLOATING,
Stability: stability,
}
// Convert each dot-delimited part to an integer.
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
}
// 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
}