mirror of https://github.com/aptly-dev/aptly
309 lines
6.7 KiB
Go
309 lines
6.7 KiB
Go
package deb
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// Using documentation from: http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
|
|
|
// CompareVersions compares two package versions
|
|
func CompareVersions(ver1, ver2 string) int {
|
|
e1, u1, d1 := parseVersion(ver1)
|
|
e2, u2, d2 := parseVersion(ver2)
|
|
|
|
r := compareVersionPart(e1, e2)
|
|
if r != 0 {
|
|
return r
|
|
}
|
|
|
|
r = compareVersionPart(u1, u2)
|
|
if r != 0 {
|
|
return r
|
|
}
|
|
|
|
return compareVersionPart(d1, d2)
|
|
}
|
|
|
|
// parseVersions breaks down full version to components (possibly empty)
|
|
func parseVersion(ver string) (epoch, upstream, debian string) {
|
|
i := strings.LastIndex(ver, "-")
|
|
if i != -1 {
|
|
debian, ver = ver[i+1:], ver[:i]
|
|
}
|
|
|
|
i = strings.Index(ver, ":")
|
|
if i != -1 {
|
|
epoch, ver = ver[:i], ver[i+1:]
|
|
}
|
|
|
|
upstream = ver
|
|
|
|
return
|
|
}
|
|
|
|
// compareLexicographic compares in "Debian lexicographic" way, see below compareVersionPart for details
|
|
func compareLexicographic(s1, s2 string) int {
|
|
i := 0
|
|
l1, l2 := len(s1), len(s2)
|
|
|
|
for {
|
|
if i == l1 && i == l2 {
|
|
// s1 equal to s2
|
|
break
|
|
}
|
|
|
|
if i == l2 {
|
|
// s1 is longer than s2
|
|
if s1[i] == '~' {
|
|
return -1 // s1 < s2
|
|
}
|
|
return 1 // s1 > s2
|
|
}
|
|
|
|
if i == l1 {
|
|
// s2 is longer than s1
|
|
if s2[i] == '~' {
|
|
return 1 // s1 > s2
|
|
}
|
|
return -1 // s1 < s2
|
|
}
|
|
|
|
if s1[i] == s2[i] {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if s1[i] == '~' {
|
|
return -1
|
|
}
|
|
|
|
if s2[i] == '~' {
|
|
return 1
|
|
}
|
|
|
|
c1, c2 := unicode.IsLetter(rune(s1[i])), unicode.IsLetter(rune(s2[i]))
|
|
if c1 && !c2 {
|
|
return -1
|
|
}
|
|
if !c1 && c2 {
|
|
return 1
|
|
}
|
|
|
|
if s1[i] < s2[i] {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// compareVersionPart compares parts of full version
|
|
//
|
|
// From Debian Policy Manual:
|
|
//
|
|
// "The strings are compared from left to right.
|
|
//
|
|
// First the initial part of each string consisting entirely of non-digit characters is
|
|
// determined. These two parts (one of which may be empty) are compared lexically. If a
|
|
// difference is found it is returned. The lexical comparison is a comparison of ASCII values
|
|
// modified so that all the letters sort earlier than all the non-letters and so that a tilde
|
|
// sorts before anything, even the end of a part. For example, the following parts are in sorted
|
|
// order from earliest to latest: ~~, ~~a, ~, the empty part.
|
|
//
|
|
// Then the initial part of the remainder of each string which consists entirely of digit
|
|
// characters is determined. The numerical values of these two parts are compared, and any difference
|
|
// found is returned as the result of the comparison. For these purposes an empty string (which can only occur at
|
|
// the end of one or both version strings being compared) counts as zero.
|
|
|
|
// These two steps (comparing and removing initial non-digit strings and initial digit strings) are
|
|
// repeated until a difference is found or both strings are exhausted."
|
|
func compareVersionPart(part1, part2 string) int {
|
|
i1, i2 := 0, 0
|
|
l1, l2 := len(part1), len(part2)
|
|
|
|
for {
|
|
j1, j2 := i1, i2
|
|
for j1 < l1 && !unicode.IsDigit(rune(part1[j1])) {
|
|
j1++
|
|
}
|
|
|
|
for j2 < l2 && !unicode.IsDigit(rune(part2[j2])) {
|
|
j2++
|
|
}
|
|
|
|
s1, s2 := part1[i1:j1], part2[i2:j2]
|
|
r := compareLexicographic(s1, s2)
|
|
if r != 0 {
|
|
return r
|
|
}
|
|
|
|
i1, i2 = j1, j2
|
|
|
|
for j1 < l1 && unicode.IsDigit(rune(part1[j1])) {
|
|
j1++
|
|
}
|
|
|
|
for j2 < l2 && unicode.IsDigit(rune(part2[j2])) {
|
|
j2++
|
|
}
|
|
|
|
s1, s2 = part1[i1:j1], part2[i2:j2]
|
|
n1, _ := strconv.Atoi(s1)
|
|
n2, _ := strconv.Atoi(s2)
|
|
|
|
if n1 < n2 {
|
|
return -1
|
|
}
|
|
if n1 > n2 {
|
|
return 1
|
|
}
|
|
|
|
i1, i2 = j1, j2
|
|
|
|
if i1 == l1 && i2 == l2 {
|
|
break
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Version relations
|
|
const (
|
|
VersionDontCare = iota
|
|
VersionLess
|
|
VersionLessOrEqual
|
|
VersionEqual
|
|
VersionGreaterOrEqual
|
|
VersionGreater
|
|
VersionPatternMatch
|
|
VersionRegexp
|
|
)
|
|
|
|
// Dependency is a parsed version of Debian dependency to package
|
|
type Dependency struct {
|
|
Pkg string
|
|
Relation int
|
|
Version string
|
|
Architecture string
|
|
Regexp *regexp.Regexp
|
|
}
|
|
|
|
// Hash calculates some predefined unique ID of Dependency
|
|
func (d *Dependency) Hash() string {
|
|
return fmt.Sprintf("%s:%s:%d:%s", d.Architecture, d.Pkg, d.Relation, d.Version)
|
|
}
|
|
|
|
// String produces human-readable representation
|
|
func (d *Dependency) String() string {
|
|
var rel string
|
|
switch d.Relation {
|
|
case VersionEqual:
|
|
rel = "="
|
|
case VersionGreater:
|
|
rel = ">>"
|
|
case VersionLess:
|
|
rel = "<<"
|
|
case VersionGreaterOrEqual:
|
|
rel = ">="
|
|
case VersionLessOrEqual:
|
|
rel = "<="
|
|
case VersionPatternMatch:
|
|
rel = "%"
|
|
case VersionRegexp:
|
|
rel = "~"
|
|
case VersionDontCare:
|
|
return fmt.Sprintf("%s [%s]", d.Pkg, d.Architecture)
|
|
}
|
|
return fmt.Sprintf("%s (%s %s) [%s]", d.Pkg, rel, d.Version, d.Architecture)
|
|
}
|
|
|
|
// ParseDependencyVariants parses dependencies in format "pkg (>= 1.35) | other-package"
|
|
func ParseDependencyVariants(variants string) (l []Dependency, err error) {
|
|
parts := strings.Split(variants, "|")
|
|
l = make([]Dependency, len(parts))
|
|
|
|
for i, part := range parts {
|
|
l[i], err = ParseDependency(strings.TrimSpace(part))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ParseDependencyArch parses the dependency name in format "pkg:any" into name and architecture
|
|
func ParseDependencyArch(d *Dependency) {
|
|
if strings.ContainsRune(d.Pkg, ':') {
|
|
parts := strings.SplitN(d.Pkg, ":", 2)
|
|
d.Pkg, d.Architecture = parts[0], parts[1]
|
|
if d.Architecture == "any" {
|
|
d.Architecture = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// ParseDependency parses dependency in format "pkg (>= 1.35) [arch]" into parts
|
|
func ParseDependency(dep string) (d Dependency, err error) {
|
|
if strings.HasSuffix(dep, "}") {
|
|
i := strings.LastIndex(dep, "{")
|
|
if i == -1 {
|
|
err = fmt.Errorf("unable to parse dependency: %s", dep)
|
|
return
|
|
}
|
|
d.Architecture = dep[i+1 : len(dep)-1]
|
|
|
|
dep = strings.TrimSpace(dep[:i])
|
|
}
|
|
|
|
if !strings.HasSuffix(dep, ")") {
|
|
d.Pkg = strings.TrimSpace(dep)
|
|
d.Relation = VersionDontCare
|
|
ParseDependencyArch(&d)
|
|
return
|
|
}
|
|
|
|
i := strings.Index(dep, "(")
|
|
if i == -1 {
|
|
err = fmt.Errorf("unable to parse dependency: %s", dep)
|
|
return
|
|
}
|
|
|
|
d.Pkg = strings.TrimSpace(dep[0:i])
|
|
ParseDependencyArch(&d)
|
|
|
|
rel := ""
|
|
if dep[i+1] == '>' || dep[i+1] == '<' || dep[i+1] == '=' {
|
|
rel += dep[i+1 : i+2]
|
|
if dep[i+2] == '>' || dep[i+2] == '<' || dep[i+2] == '=' {
|
|
rel += dep[i+2 : i+3]
|
|
d.Version = strings.TrimSpace(dep[i+3 : len(dep)-1])
|
|
} else {
|
|
d.Version = strings.TrimSpace(dep[i+2 : len(dep)-1])
|
|
}
|
|
} else {
|
|
d.Version = strings.TrimSpace(dep[i+1 : len(dep)-1])
|
|
}
|
|
|
|
switch rel {
|
|
case "<", "<=":
|
|
d.Relation = VersionLessOrEqual
|
|
case ">", ">=":
|
|
d.Relation = VersionGreaterOrEqual
|
|
case "<<":
|
|
d.Relation = VersionLess
|
|
case ">>":
|
|
d.Relation = VersionGreater
|
|
case "", "=":
|
|
d.Relation = VersionEqual
|
|
default:
|
|
err = fmt.Errorf("relation unknown %#v in dependency %s", rel, dep)
|
|
}
|
|
|
|
return
|
|
}
|