diff --git a/pkg/package/database_mem.go b/pkg/package/database_mem.go index 420c623e..2e56d051 100644 --- a/pkg/package/database_mem.go +++ b/pkg/package/database_mem.go @@ -20,7 +20,6 @@ import ( "encoding/json" "sync" - version "github.com/hashicorp/go-version" "github.com/pkg/errors" ) @@ -178,16 +177,11 @@ func (db *InMemoryDatabase) getProvide(p Package) (Package, error) { for ve, _ := range versions { - v, err := version.NewVersion(p.GetVersion()) + match, err := p.VersionMatchSelector(ve) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Error on match version") } - constraints, err := version.NewConstraint(ve) - if err != nil { - return nil, err - } - - if constraints.Check(v) { + if match { pa, ok := db.ProvidesDatabase[p.GetPackageName()][ve] if !ok { return nil, errors.New("No versions found for package") @@ -257,15 +251,12 @@ func (db *InMemoryDatabase) FindPackages(p Package) ([]Package, error) { } var versionsInWorld []Package for ve, _ := range versions { - v, err := version.NewVersion(ve) + match, err := p.SelectorMatchVersion(ve) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Error on match selector") } - constraints, err := version.NewConstraint(p.GetVersion()) - if err != nil { - return nil, err - } - if constraints.Check(v) { + + if match { w, err := db.FindPackage(&DefaultPackage{Name: p.GetName(), Category: p.GetCategory(), Version: ve}) if err != nil { return nil, errors.Wrap(err, "Cache mismatch - this shouldn't happen") diff --git a/pkg/package/package.go b/pkg/package/package.go index 34715a5f..24811622 100644 --- a/pkg/package/package.go +++ b/pkg/package/package.go @@ -83,6 +83,8 @@ type Package interface { GetLicense() string IsSelector() bool + VersionMatchSelector(string) (bool, error) + SelectorMatchVersion(string) (bool, error) } type Tree interface { @@ -322,15 +324,11 @@ func (p *DefaultPackage) Expand(definitiondb PackageDatabase) ([]Package, error) return nil, err } for _, w := range all { - v, err := version.NewVersion(w.GetVersion()) + match, err := p.SelectorMatchVersion(w.GetVersion()) if err != nil { return nil, err } - constraints, err := version.NewConstraint(p.GetVersion()) - if err != nil { - return nil, err - } - if constraints.Check(v) { + if match { versionsInWorld = append(versionsInWorld, w) } } diff --git a/pkg/package/version.go b/pkg/package/version.go new file mode 100644 index 00000000..a3e98d39 --- /dev/null +++ b/pkg/package/version.go @@ -0,0 +1,262 @@ +// Copyright © 2019 Ettore Di Giacinto , +// Daniele Rondina +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package pkg + +import ( + "errors" + "fmt" + "regexp" + "strings" + + version "github.com/hashicorp/go-version" +) + +// Package Selector Condition +type PkgSelectorCondition int + +type PkgVersionSelector struct { + Version string + VersionSuffix string + Condition PkgSelectorCondition + // TODO: Integrate support for multiple repository +} + +const ( + PkgCondInvalid = 0 + // > + PkgCondGreater = 1 + // >= + PkgCondGreaterEqual = 2 + // < + PkgCondLess = 3 + // <= + PkgCondLessEqual = 4 + // = + PkgCondEqual = 5 + // ! + PkgCondNot = 6 + // ~ + PkgCondAnyRevision = 7 + // =* + PkgCondMatchVersion = 8 +) + +func ParseVersion(v string) (PkgVersionSelector, error) { + var ans PkgVersionSelector = PkgVersionSelector{ + Version: "", + VersionSuffix: "", + Condition: PkgCondInvalid, + } + + if strings.HasPrefix(v, ">=") { + v = v[2:] + ans.Condition = PkgCondGreaterEqual + } else if strings.HasPrefix(v, ">") { + v = v[1:] + ans.Condition = PkgCondGreater + } else if strings.HasPrefix(v, "<=") { + v = v[2:] + ans.Condition = PkgCondLessEqual + } else if strings.HasPrefix(v, "<") { + v = v[1:] + ans.Condition = PkgCondLess + } else if strings.HasPrefix(v, "=") { + v = v[1:] + if strings.HasSuffix(v, "*") { + ans.Condition = PkgCondMatchVersion + v = v[0 : len(v)-1] + } else { + ans.Condition = PkgCondEqual + } + } else if strings.HasPrefix(v, "~") { + v = v[1:] + ans.Condition = PkgCondAnyRevision + } else if strings.HasPrefix(v, "!") { + v = v[1:] + ans.Condition = PkgCondNot + } + + regexPkg := regexp.MustCompile( + fmt.Sprintf("(%s|%s|%s|%s|%s|%s)((%s|%s|%s|%s|%s|%s|%s)+)*$", + // Version regex + // 1.1 + "[0-9]+[.][0-9]+[a-z]*", + // 1 + "[0-9]+[a-z]*", + // 1.1.1 + "[0-9]+[.][0-9]+[.][0-9]+[a-z]*", + // 1.1.1.1 + "[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[a-z]*", + // 1.1.1.1.1 + "[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[a-z]*", + // 1.1.1.1.1.1 + "[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[.][0-9]+[a-z]*", + // suffix + "-r[0-9]+", + "_p[0-9]+", + "_pre[0-9]*", + "_rc[0-9]+", + // handle also rc without number + "_rc", + "_alpha", + "_beta", + ), + ) + matches := regexPkg.FindAllString(v, -1) + + if len(matches) > 0 { + // Check if there patch + if strings.Contains(matches[0], "_p") { + ans.Version = matches[0][0:strings.Index(matches[0], "_p")] + ans.VersionSuffix = matches[0][strings.Index(matches[0], "_p"):] + } else if strings.Contains(matches[0], "_rc") { + ans.Version = matches[0][0:strings.Index(matches[0], "_rc")] + ans.VersionSuffix = matches[0][strings.Index(matches[0], "_rc"):] + } else if strings.Contains(matches[0], "_alpha") { + ans.Version = matches[0][0:strings.Index(matches[0], "_alpha")] + ans.VersionSuffix = matches[0][strings.Index(matches[0], "_alpha"):] + } else if strings.Contains(matches[0], "_beta") { + ans.Version = matches[0][0:strings.Index(matches[0], "_beta")] + ans.VersionSuffix = matches[0][strings.Index(matches[0], "_beta"):] + } else if strings.Contains(matches[0], "-r") { + ans.Version = matches[0][0:strings.Index(matches[0], "-r")] + ans.VersionSuffix = matches[0][strings.Index(matches[0], "-r"):] + } else { + ans.Version = matches[0] + } + } + + // Set condition if there isn't a prefix but only a version + if ans.Condition == PkgCondInvalid && ans.Version != "" { + ans.Condition = PkgCondEqual + } + + // NOTE: Now suffix complex like _alpha_rc1 are not supported. + return ans, nil +} + +func PackageAdmit(selector, i PkgVersionSelector) (bool, error) { + var v1 *version.Version = nil + var v2 *version.Version = nil + var ans bool + var err error + + if selector.Version != "" { + v1, err = version.NewVersion(selector.Version) + if err != nil { + return false, err + } + } + if i.Version != "" { + v2, err = version.NewVersion(i.Version) + if err != nil { + return false, err + } + } else { + // If version is not defined match always package + ans = true + } + + // If package doesn't define version admit all versions of the package. + if selector.Version == "" { + ans = true + } else { + if selector.Condition == PkgCondInvalid || selector.Condition == PkgCondEqual { + // case 1: source-pkg-1.0 and dest-pkg-1.0 or dest-pkg without version + if i.Version != "" && i.Version == selector.Version && selector.VersionSuffix == i.VersionSuffix { + ans = true + } + } else if selector.Condition == PkgCondAnyRevision { + if v1 != nil && v2 != nil { + ans = v1.Equal(v2) + } + } else if selector.Condition == PkgCondMatchVersion { + // TODO: case of 7.3* where 7.30 is accepted. + if v1 != nil && v2 != nil { + segments := v1.Segments() + n := strings.Count(selector.Version, ".") + switch n { + case 0: + segments[0]++ + case 1: + segments[1]++ + case 2: + segments[2]++ + default: + segments[len(segments)-1]++ + } + nextVersion := strings.Trim(strings.Replace(fmt.Sprint(segments), " ", ".", -1), "[]") + constraints, err := version.NewConstraint( + fmt.Sprintf(">= %s, < %s", selector.Version, nextVersion), + ) + if err != nil { + return false, err + } + ans = constraints.Check(v2) + } + } else if v1 != nil && v2 != nil { + + // TODO: Integrate check of version suffix + switch selector.Condition { + case PkgCondGreaterEqual: + ans = v2.GreaterThanOrEqual(v1) + case PkgCondLessEqual: + ans = v2.LessThanOrEqual(v1) + case PkgCondGreater: + ans = v2.GreaterThan(v1) + case PkgCondLess: + ans = v2.LessThan(v1) + case PkgCondNot: + ans = !v2.Equal(v1) + } + } + } + + return ans, nil +} + +func (p *DefaultPackage) SelectorMatchVersion(v string) (bool, error) { + if !p.IsSelector() { + return false, errors.New("Package is not a selector") + } + + vS, err := ParseVersion(p.GetVersion()) + if err != nil { + return false, err + } + + vSI, err := ParseVersion(v) + if err != nil { + return false, err + } + + return PackageAdmit(vS, vSI) +} + +func (p *DefaultPackage) VersionMatchSelector(selector string) (bool, error) { + vS, err := ParseVersion(selector) + if err != nil { + return false, err + } + + vSI, err := ParseVersion(p.GetVersion()) + if err != nil { + return false, err + } + + return PackageAdmit(vS, vSI) +} diff --git a/pkg/package/version_test.go b/pkg/package/version_test.go new file mode 100644 index 00000000..60288779 --- /dev/null +++ b/pkg/package/version_test.go @@ -0,0 +1,184 @@ +// Copyright © 2019 Ettore Di Giacinto +// Daniele Rondina +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package pkg_test + +import ( + . "github.com/mudler/luet/pkg/package" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Versions", func() { + + Context("Versions Parser1", func() { + v, err := ParseVersion(">=1.0") + It("ParseVersion1", func() { + var c PkgSelectorCondition = PkgCondGreaterEqual + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("1.0")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser2", func() { + v, err := ParseVersion(">1.0") + It("ParseVersion2", func() { + var c PkgSelectorCondition = PkgCondGreater + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("1.0")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser3", func() { + v, err := ParseVersion("<=1.0") + It("ParseVersion3", func() { + var c PkgSelectorCondition = PkgCondLessEqual + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("1.0")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser4", func() { + v, err := ParseVersion("<1.0") + It("ParseVersion4", func() { + var c PkgSelectorCondition = PkgCondLess + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("1.0")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser5", func() { + v, err := ParseVersion("=1.0") + It("ParseVersion5", func() { + var c PkgSelectorCondition = PkgCondEqual + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("1.0")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser6", func() { + v, err := ParseVersion("!1.0") + It("ParseVersion6", func() { + var c PkgSelectorCondition = PkgCondNot + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("1.0")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser7", func() { + v, err := ParseVersion("") + It("ParseVersion7", func() { + var c PkgSelectorCondition = PkgCondInvalid + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("")) + Expect(v.VersionSuffix).Should(Equal("")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser8", func() { + v, err := ParseVersion("=12.1.0.2_p1") + It("ParseVersion8", func() { + var c PkgSelectorCondition = PkgCondEqual + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("12.1.0.2")) + Expect(v.VersionSuffix).Should(Equal("_p1")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser9", func() { + v, err := ParseVersion(">=0.0.20190406.4.9.172-r1") + It("ParseVersion9", func() { + var c PkgSelectorCondition = PkgCondGreaterEqual + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("0.0.20190406.4.9.172")) + Expect(v.VersionSuffix).Should(Equal("-r1")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Versions Parser10", func() { + v, err := ParseVersion(">=0.0.20190406.4.9.172_alpha") + It("ParseVersion10", func() { + var c PkgSelectorCondition = PkgCondGreaterEqual + Expect(err).Should(BeNil()) + Expect(v.Version).Should(Equal("0.0.20190406.4.9.172")) + Expect(v.VersionSuffix).Should(Equal("_alpha")) + Expect(v.Condition).Should(Equal(c)) + }) + }) + + Context("Selector1", func() { + v1, err := ParseVersion(">=0.0.20190406.4.9.172-r1") + v2, err2 := ParseVersion("1.0.111") + match, err3 := PackageAdmit(v1, v2) + It("Selector1", func() { + Expect(err).Should(BeNil()) + Expect(err2).Should(BeNil()) + Expect(err3).Should(BeNil()) + Expect(match).Should(Equal(true)) + }) + }) + + Context("Selector2", func() { + v1, err := ParseVersion(">=0.0.20190406.4.9.172-r1") + v2, err2 := ParseVersion("0") + match, err3 := PackageAdmit(v1, v2) + It("Selector2", func() { + Expect(err).Should(BeNil()) + Expect(err2).Should(BeNil()) + Expect(err3).Should(BeNil()) + Expect(match).Should(Equal(false)) + }) + }) + + Context("Selector3", func() { + v1, err := ParseVersion(">0") + v2, err2 := ParseVersion("0.0.40-alpha") + match, err3 := PackageAdmit(v1, v2) + It("Selector3", func() { + Expect(err).Should(BeNil()) + Expect(err2).Should(BeNil()) + Expect(err3).Should(BeNil()) + Expect(match).Should(Equal(true)) + }) + }) + + Context("Selector4", func() { + v1, err := ParseVersion(">0") + v2, err2 := ParseVersion("") + match, err3 := PackageAdmit(v1, v2) + It("Selector4", func() { + Expect(err).Should(BeNil()) + Expect(err2).Should(BeNil()) + Expect(err3).Should(BeNil()) + Expect(match).Should(Equal(true)) + }) + }) +})