mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-01-12 03:21:33 +00:00
This commit closes: #145 The dependency format "pkg:arch" (e.g. "python3:any") was not well parsed if not any version is given. This commit splits the dependency name and architecture in all cases.
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
|
|
}
|