From 0fa844d8a1170c12aab93c4b66f6b51f68594b5f Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 29 Aug 2026 11:14:25 +0100 Subject: [PATCH] Compare APK versions with apk-tools tokenization The apk and alpine schemes previously delegated to the Gentoo comparator, which does not recognise the VCS suffixes (_cvs, _svn, _git, _hg), commit-hash markers (~), or apk-tools' leading-zero and uint64 rules. Port the tokenizer from apk-tools src/version.c at 900a3f5 and drive it from the vendored test/unit/version.data fixture (738 comparisons, 31 validity checks). APK validation and prerelease classification now use the same tokenizer instead of Gentoo's. --- apk.go | 244 +++++++++ apk_test.go | 138 +++++ normalization.go | 4 +- testdata/local/data/apk_version.data | 788 +++++++++++++++++++++++++++ testdata/local/provenance.json | 12 + vers.go | 4 +- version.go | 4 +- 7 files changed, 1189 insertions(+), 5 deletions(-) create mode 100644 apk.go create mode 100644 apk_test.go create mode 100644 testdata/local/data/apk_version.data diff --git a/apk.go b/apk.go new file mode 100644 index 0000000..d411dc1 --- /dev/null +++ b/apk.go @@ -0,0 +1,244 @@ +package vers + +import "strings" + +// APK version comparison follows the tokenizer in apk-tools src/version.c +// at commit 900a3f5280bfad7ea22eda7de46d0d4c0c9ce8f2. + +const ( + apkTokenInitialDigit = iota + apkTokenDigit + apkTokenLetter + apkTokenSuffix + apkTokenSuffixNumber + apkTokenCommitHash + apkTokenRevisionNumber + apkTokenEnd + apkTokenInvalid +) + +const apkSuffixNone = 5 + +var apkSuffixRank = map[string]int{ + "alpha": 1, + "beta": 2, + "pre": 3, + "rc": 4, + "cvs": 6, + "svn": 7, + "git": 8, + "hg": 9, + "p": 10, +} + +type apkToken struct { + kind int + number uint64 + text string +} + +func compareAPK(a, b string) int { + left := parseAPKVersion(a) + right := parseAPKVersion(b) + + i := 0 + for apkTokenType(left, i) == apkTokenType(right, i) && apkTokenType(left, i) < apkTokenEnd { + if c := compareAPKToken(left[i], right[i]); c != 0 { + return c + } + i++ + } + + lt, rt := apkTokenType(left, i), apkTokenType(right, i) + if lt == rt { + return 0 + } + if lt == apkTokenSuffix && left[i].number < apkSuffixNone { + return -1 + } + if rt == apkTokenSuffix && right[i].number < apkSuffixNone { + return 1 + } + if lt > rt { + return -1 + } + return 1 +} + +func compareAPKToken(a, b apkToken) int { + switch a.kind { + case apkTokenDigit: + if strings.HasPrefix(a.text, "0") || strings.HasPrefix(b.text, "0") { + return cmpString(a.text, b.text) + } + return cmpUint64(a.number, b.number) + case apkTokenInitialDigit, apkTokenSuffixNumber, apkTokenRevisionNumber, apkTokenLetter, apkTokenSuffix: + return cmpUint64(a.number, b.number) + default: + return cmpString(a.text, b.text) + } +} + +func validAPKVersion(s string) bool { + tokens := parseAPKVersion(s) + for _, t := range tokens { + if t.kind == apkTokenInvalid { + return false + } + } + return len(tokens) > 0 +} + +func apkVersionIsPrerelease(s string) bool { + for _, t := range parseAPKVersion(s) { + if t.kind == apkTokenInvalid { + return false + } + if t.kind == apkTokenSuffix && t.number < apkSuffixNone { + return true + } + } + return false +} + +func parseAPKVersion(s string) []apkToken { + s = strings.TrimSpace(s) + tokens := make([]apkToken, 0, 6) //nolint:mnd + + first, i := scanAPKDigits(s, 0) + if first == "" { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + tokens = append(tokens, apkToken{kind: apkTokenInitialDigit, number: apkUint64(first), text: first}) + previous := apkTokenInitialDigit + + for i < len(s) { + c := s[i] + switch { + case c >= 'a' && c <= 'z': + if previous > apkTokenDigit { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + tokens = append(tokens, apkToken{kind: apkTokenLetter, number: uint64(c)}) + previous = apkTokenLetter + i++ + case c == '.': + if previous > apkTokenDigit { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + digits, next := scanAPKDigits(s, i+1) + if digits == "" { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + tokens = append(tokens, apkToken{kind: apkTokenDigit, number: apkUint64(digits), text: digits}) + previous = apkTokenDigit + i = next + case c >= '0' && c <= '9': + var kind int + switch previous { + case apkTokenInitialDigit, apkTokenDigit: + kind = apkTokenDigit + case apkTokenSuffix: + kind = apkTokenSuffixNumber + default: + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + digits, next := scanAPKDigits(s, i) + tokens = append(tokens, apkToken{kind: kind, number: apkUint64(digits), text: digits}) + previous = kind + i = next + case c == '_': + if previous > apkTokenSuffixNumber { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + suffix, next := scanAPKLower(s, i+1) + rank, ok := apkSuffixRank[suffix] + if !ok { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + tokens = append(tokens, apkToken{kind: apkTokenSuffix, number: uint64(rank)}) + previous = apkTokenSuffix + i = next + case c == '~': + if previous >= apkTokenCommitHash { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + hash, next := scanAPKHex(s, i+1) + if hash == "" { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + tokens = append(tokens, apkToken{kind: apkTokenCommitHash, text: hash}) + previous = apkTokenCommitHash + i = next + case c == '-': + if previous >= apkTokenRevisionNumber || i+1 >= len(s) || s[i+1] != 'r' { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + digits, next := scanAPKDigits(s, i+2) + if digits == "" { + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + tokens = append(tokens, apkToken{kind: apkTokenRevisionNumber, number: apkUint64(digits), text: digits}) + previous = apkTokenRevisionNumber + i = next + default: + return append(tokens, apkToken{kind: apkTokenInvalid}) + } + } + + return tokens +} + +func apkTokenType(tokens []apkToken, i int) int { + if i < len(tokens) { + return tokens[i].kind + } + return apkTokenEnd +} + +func apkUint64(s string) uint64 { + var n uint64 + for i := 0; i < len(s); i++ { + n = n*10 + uint64(s[i]-'0') + } + return n +} + +func scanAPKDigits(s string, i int) (string, int) { + start := i + for i < len(s) && s[i] >= '0' && s[i] <= '9' { + i++ + } + return s[start:i], i +} + +func scanAPKLower(s string, i int) (string, int) { + start := i + for i < len(s) && s[i] >= 'a' && s[i] <= 'z' { + i++ + } + return s[start:i], i +} + +func scanAPKHex(s string, i int) (string, int) { + start := i + for i < len(s) { + c := s[i] + if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') { + i++ + } else { + break + } + } + return s[start:i], i +} + +func cmpUint64(a, b uint64) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} diff --git a/apk_test.go b/apk_test.go new file mode 100644 index 0000000..b8c6c23 --- /dev/null +++ b/apk_test.go @@ -0,0 +1,138 @@ +package vers + +import ( + "bufio" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestAPKVersionData(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", "local", "data", "apk_version.data")) + if err != nil { + t.Fatal(err) + } + defer func() { _ = f.Close() }() + + var comparisons, validity int + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if i := strings.Index(line, "#"); i >= 0 { + line = line[:i] + } + line = strings.TrimSpace(line) + if line == "" { + continue + } + + if a, op, b, ok := splitAPKComparison(line); ok { + comparisons++ + got := CompareWithScheme(a, b, "apk") + if got != op { + t.Errorf("CompareWithScheme(%q, %q, apk) = %d, want %d", a, b, got, op) + } + continue + } + + if strings.ContainsRune(line, ' ') { + continue + } + + validity++ + version := line + want := true + if strings.HasPrefix(line, "!") { + version = line[1:] + want = false + } + if got := ValidWithScheme(version, "apk"); got != want { + t.Errorf("ValidWithScheme(%q, apk) = %v, want %v", version, got, want) + } + } + if err := scanner.Err(); err != nil { + t.Fatal(err) + } + + t.Logf("apk_version.data: %d comparisons, %d validity checks", comparisons, validity) + if comparisons < 730 || validity < 30 { + t.Fatalf("apk_version.data yielded %d comparisons and %d validity checks", comparisons, validity) + } +} + +func splitAPKComparison(line string) (a string, op int, b string, ok bool) { + fields := strings.Fields(line) + if len(fields) != 3 { + return "", 0, "", false + } + switch fields[1] { + case "<": + return fields[0], -1, fields[2], true + case ">": + return fields[0], 1, fields[2], true + case "=": + return fields[0], 0, fields[2], true + } + return "", 0, "", false +} + +func TestAPKComparisonThroughPublicAPI(t *testing.T) { + tests := []struct { + left, right string + want int + }{ + {"1.0", "1.0_alpha", 1}, + {"1.0", "1.0_p1", -1}, + {"1.0_cvs", "1.0", 1}, + {"1.0_git20240101", "1.0", 1}, + {"1.0_svn", "1.0_git", -1}, + {"1.0~1234", "1.0~1235", -1}, + {"1.0~1234-r1", "1.0~1234-r0", 1}, + {"1.0", "1.0bc", -1}, + {"1.06", "1.6", -1}, + {"1.006", "1.06", -1}, + } + for _, tt := range tests { + if got := CompareWithScheme(tt.left, tt.right, "apk"); got != tt.want { + t.Errorf("CompareWithScheme(%q, %q, apk) = %d, want %d", tt.left, tt.right, got, tt.want) + } + if got := CompareWithScheme(tt.right, tt.left, "apk"); got != -tt.want { + t.Errorf("CompareWithScheme(%q, %q, apk) = %d, want %d", tt.right, tt.left, got, -tt.want) + } + } +} + +func TestAPKClassificationThroughPublicAPI(t *testing.T) { + if !IsPrereleaseWithScheme("1.0_alpha", "apk") || !IsPrereleaseWithScheme("1.0_rc1", "apk") { + t.Error("apk pre-release suffixes should classify as prerelease") + } + for _, stable := range []string{"1.0", "1.0_p1", "1.0_git20240101", "1.0-r1", "1.0~abcd"} { + if IsPrereleaseWithScheme(stable, "apk") { + t.Errorf("IsPrereleaseWithScheme(%q, apk) = true", stable) + } + if !IsStableWithScheme(stable, "apk") { + t.Errorf("IsStableWithScheme(%q, apk) = false", stable) + } + } + if IsStableWithScheme("0.1bc", "apk") || IsPrereleaseWithScheme("0.1bc", "apk") { + t.Error("invalid apk version should not classify") + } +} + +func TestAPKRangesThroughPublicAPI(t *testing.T) { + r, err := Parse("vers:apk/>=1.0|<2.0") + if err != nil { + t.Fatal(err) + } + if !r.Contains("1.0_git20240101") { + t.Error("apk range should contain a git snapshot above the lower bound") + } + if r.Contains("1.0_alpha") { + t.Error("apk range should not contain a pre-release below the lower bound") + } + + if !ValidWithScheme("1.0_hg1", "alpine") { + t.Error("alpine alias should validate apk versions") + } +} diff --git a/normalization.go b/normalization.go index 911f308..c075960 100644 --- a/normalization.go +++ b/normalization.go @@ -45,7 +45,9 @@ func validVersionForScheme(version, scheme string) bool { //nolint:gocyclo case schemeOpenSSL: _, ok := parseOpenSSLVersion(version) return ok - case schemeMaven, schemeLexicographic, schemeDatetime, schemeAPK, schemeAlpine, schemeGentoo, schemeALPM, schemeConan: + case schemeAPK, schemeAlpine: + return validAPKVersion(version) + case schemeMaven, schemeLexicographic, schemeDatetime, schemeGentoo, schemeALPM, schemeConan: return !strings.ContainsAny(version, " \t\r\n") default: return Valid(version) diff --git a/testdata/local/data/apk_version.data b/testdata/local/data/apk_version.data new file mode 100644 index 0000000..22654ff --- /dev/null +++ b/testdata/local/data/apk_version.data @@ -0,0 +1,788 @@ +2.34 > 0.1.0_alpha +23_foo > 4_beta +1.0 < 1.0bc # invalid. do string sort +0.1.0_alpha = 0.1.0_alpha +0.1.0_alpha < 0.1.3_alpha +0.1.3_alpha > 0.1.0_alpha +0.1.0_alpha2 > 0.1.0_alpha +0.1.0_alpha < 2.2.39-r1 +2.2.39-r1 > 1.0.4-r3 +1.0.4-r3 < 1.0.4-r4 +1.0.4-r4 < 1.6 +1.6 > 1.0.2 +1.0.2 > 0.7-r1 +0.7-r1 < 1.0.0 +1.0.0 < 1.0.1 +1.0.1 < 1.1 +1.1 > 1.1_alpha1 +1.1_alpha1 < 1.2.1 +1.2.1 > 1.2 +1.2 < 1.3_alpha +1.3_alpha < 1.3_alpha2 +1.3_alpha2 < 1.3_alpha3 +1.3_alpha8 > 0.6.0 +0.6.0 < 0.6.1 +0.6.1 < 0.7.0 +0.7.0 < 0.8_beta1 +0.8_beta1 < 0.8_beta2 +0.8_beta4 < 4.8-r1 +4.8-r1 > 3.10.18-r1 +3.10.18-r1 > 2.3.0b-r1 +2.3.0b-r1 < 2.3.0b-r2 +2.3.0b-r2 < 2.3.0b-r3 +2.3.0b-r3 < 2.3.0b-r4 +2.3.0b-r4 > 0.12.1 +0.12.1 < 0.12.2 +0.12.2 < 0.12.3 +0.12.3 > 0.12 +0.12 < 0.13_beta1 +0.13_beta1 < 0.13_beta2 +0.13_beta2 < 0.13_beta3 +0.13_beta3 < 0.13_beta4 +0.13_beta4 < 0.13_beta5 +0.13_beta5 > 0.9.12 +0.9.12 < 0.9.13 +0.9.13 > 0.9.12 +0.9.12 < 0.9.13 +0.9.13 > 0.0.16 +0.0.16 < 0.6 +0.6 < 2.1.13-r3 +2.1.13-r3 < 2.1.15-r2 +2.1.15-r2 < 2.1.15-r3 +2.1.15-r3 > 1.2.11 +1.2.11 < 1.2.12.1 +1.2.12.1 < 1.2.13 +1.2.13 < 1.2.14-r1 +1.2.14-r1 > 0.7.1 +0.7.1 > 0.5.4 +0.5.4 < 0.7.0 +0.7.0 < 1.2.13 +1.2.13 > 1.0.8 +1.0.8 < 1.2.1 +1.2.1 > 0.7-r1 +0.7-r1 < 2.4.32 +2.4.32 < 2.8-r4 +2.8-r4 > 0.9.6 +0.9.6 > 0.2.0-r1 +0.2.0-r1 = 0.2.0-r1 +0.2.0-r1 < 3.1_p16 +3.1_p16 < 3.1_p17 +3.1_p17 > 1.06-r6 +1.06-r6 < 006 +006 > 1.0.0 +1.0.0 < 1.2.2-r1 +1.2.2-r1 > 1.2.2 +1.2.2 > 0.3-r1 +0.3-r1 < 9.3.2-r4 +9.3.2-r4 < 9.3.4-r2 +9.3.4-r2 > 9.3.4 +9.3.4 > 9.3.2 +9.3.2 < 9.3.4 +9.3.4 > 1.1.3 +1.1.3 < 2.16.1-r3 +2.16.1-r3 = 2.16.1-r3 +2.16.1-r3 > 2.1.0-r2 +2.1.0-r2 < 2.9.3-r1 +2.9.3-r1 > 0.9-r1 +0.9-r1 > 0.8-r1 +0.8-r1 < 1.0.6-r3 +1.0.6-r3 > 0.11 +0.11 < 0.12 +0.12 < 1.2.1-r1 +1.2.1-r1 < 1.2.2.1 +1.2.2.1 < 1.4.1-r1 +1.4.1-r1 < 1.4.1-r2 +1.4.1-r2 > 1.2.2 +1.2.2 < 1.3 +1.3 > 1.0.3-r6 +1.0.3-r6 < 1.0.4 +1.0.4 < 2.59 +2.59 < 20050718-r1 +20050718-r1 < 20050718-r2 +20050718-r2 > 3.9.8-r5 +3.9.8-r5 > 2.01.01_alpha10 +2.01.01_alpha10 > 0.94 +0.94 < 1.0 +1.0 > 0.99.3.20040818 +0.99.3.20040818 > 0.7 +0.7 < 1.21-r1 +1.21-r1 > 0.13 +0.13 < 0.90.1-r1 +0.90.1-r1 > 0.10.2 +0.10.2 < 0.10.3 +0.10.3 < 1.6 +1.6 < 1.39 +1.39 > 1.00_beta2 +1.00_beta2 > 0.9.2 +0.9.2 < 5.94-r1 +5.94-r1 < 6.4 +6.4 > 2.6-r5 +2.6-r5 > 1.4 +1.4 < 2.8.9-r1 +2.8.9-r1 > 2.8.9 +2.8.9 > 1.1 +1.1 > 1.0.3-r2 +1.0.3-r2 < 1.3.4-r3 +1.3.4-r3 < 2.2 +2.2 > 1.2.6 +1.2.6 < 7.15.1-r1 +7.15.1-r1 > 1.02 +1.02 < 1.03-r1 +1.03-r1 < 1.12.12-r2 +1.12.12-r2 < 2.8.0.6-r1 +2.8.0.6-r1 > 0.5.2.7 +0.5.2.7 < 4.2.52_p2-r1 +4.2.52_p2-r1 < 4.2.52_p4-r2 +4.2.52_p4-r2 > 1.02.07 +1.02.07 < 1.02.10-r1 +1.02.10-r1 < 3.0.3-r9 +3.0.3-r9 > 2.0.5-r1 +2.0.5-r1 < 4.5 +4.5 > 2.8.7-r1 +2.8.7-r1 > 1.0.5 +1.0.5 < 8 +8 < 9 +9 > 2.18.3-r10 +2.18.3-r10 > 1.05-r18 +1.05-r18 < 1.05-r19 +1.05-r19 < 2.2.5 +2.2.5 < 2.8 +2.8 < 2.20.1 +2.20.1 < 2.20.3 +2.20.3 < 2.31 +2.31 < 2.34 +2.34 < 2.38 +2.38 < 20050405 +20050405 > 1.8 +1.8 < 2.11-r1 +2.11-r1 > 2.11 +2.11 > 0.1.6-r3 +0.1.6-r3 < 0.47-r1 +0.47-r1 < 0.49 +0.49 < 3.6.8-r2 +3.6.8-r2 > 1.39 +1.39 < 2.43 +2.43 > 2.0.6-r1 +2.0.6-r1 > 0.2-r6 +0.2-r6 < 0.4 +0.4 < 1.0.0 +1.0.0 < 10-r1 +10-r1 > 4 +4 > 0.7.3-r2 +0.7.3-r2 > 0.7.3 +0.7.3 < 1.95.8 +1.95.8 > 1.1.19 +1.1.19 > 1.1.5 +1.1.5 < 6.3.2-r1 +6.3.2-r1 < 6.3.3 +6.3.3 > 4.17-r1 +4.17-r1 < 4.18 +4.18 < 4.19 +4.19 > 4.3.0 +4.3.0 < 4.3.2-r1 +4.3.2-r1 > 4.3.2 +4.3.2 > 0.68-r3 +0.68-r3 < 1.0.0 +1.0.0 < 1.0.1 +1.0.1 > 1.0.0 +1.0.0 = 1.0.0 +1.0.0 < 1.0.1 +1.0.1 < 2.3.2-r1 +2.3.2-r1 < 2.4.2 +2.4.2 < 20060720 +20060720 > 3.0.20060720 +3.0.20060720 < 20060720 +20060720 > 1.1 +1.1 = 1.1 +1.1 < 1.1.1-r1 +1.1.1-r1 < 1.1.3-r1 +1.1.3-r1 < 1.1.3-r2 +1.1.3-r2 < 2.1.10-r2 +2.1.10-r2 > 0.7.18-r2 +0.7.18-r2 < 0.17-r6 +0.17-r6 < 2.6.1 +2.6.1 < 2.6.3 +2.6.3 < 3.1.5-r2 +3.1.5-r2 < 3.4.6-r1 +3.4.6-r1 < 3.4.6-r2 +3.4.6-r2 = 3.4.6-r2 +3.4.6-r2 > 2.0.33 +2.0.33 < 2.0.34 +2.0.34 > 1.8.3-r2 +1.8.3-r2 < 1.8.3-r3 +1.8.3-r3 < 4.1 +4.1 < 8.54 +8.54 > 4.1.4 +4.1.4 > 1.2.10-r5 +1.2.10-r5 < 4.1.4-r3 +4.1.4-r3 = 4.1.4-r3 +4.1.4-r3 < 4.2.1 +4.2.1 > 4.1.0 +4.1.0 < 8.11 +8.11 > 1.4.4-r1 +1.4.4-r1 < 2.1.9.200602141850 +2.1.9.200602141850 > 1.6 +1.6 < 2.5.1-r8 +2.5.1-r8 < 2.5.1a-r1 +2.5.1a-r1 > 1.19.2-r1 +1.19.2-r1 > 0.97-r2 +0.97-r2 < 0.97-r3 +0.97-r3 < 1.3.5-r10 +1.3.5-r10 > 1.3.5-r8 +1.3.5-r8 < 1.3.5-r9 +1.3.5-r9 > 1.0 +1.0 < 1.1 +1.1 > 0.9.11 +0.9.11 < 0.9.12 +0.9.12 < 0.9.13 +0.9.13 < 0.9.14 +0.9.14 < 0.9.15 +0.9.15 < 0.9.16 +0.9.16 > 0.3-r2 +0.3-r2 < 6.3 +6.3 < 6.6 +6.6 < 6.9 +6.9 > 0.7.2-r3 +0.7.2-r3 < 1.2.10 +1.2.10 < 20040923-r2 +20040923-r2 > 20040401 +20040401 > 2.0.0_rc3-r1 +2.0.0_rc3-r1 > 1.5 +1.5 < 4.4 +4.4 > 1.0.1 +1.0.1 < 2.2.0 +2.2.0 > 1.1.0-r2 +1.1.0-r2 > 0.3 +0.3 < 20020207-r2 +20020207-r2 > 1.31-r2 +1.31-r2 < 3.7 +3.7 > 2.0.1 +2.0.1 < 2.0.2 +2.0.2 > 0.99.163 +0.99.163 < 2.6.15.20060110 +2.6.15.20060110 < 2.6.16.20060323 +2.6.16.20060323 < 2.6.19.20061214 +2.6.19.20061214 > 0.6.2-r1 +0.6.2-r1 < 0.6.3 +0.6.3 < 0.6.5 +0.6.5 < 1.3.5-r1 +1.3.5-r1 < 1.3.5-r4 +1.3.5-r4 < 3.0.0-r2 +3.0.0-r2 < 021109-r3 +021109-r3 < 20060512 +20060512 > 1.24 +1.24 > 0.9.16-r1 +0.9.16-r1 < 3.9_pre20060124 +3.9_pre20060124 > 0.01 +0.01 < 0.06 +0.06 < 1.1.7 +1.1.7 < 6b-r7 +6b-r7 > 1.12-r7 +1.12-r7 < 1.12-r8 +1.12-r8 > 1.1.12 +1.1.12 < 1.1.13 +1.1.13 > 0.3 +0.3 < 0.5 +0.5 < 3.96.1 +3.96.1 < 3.97 +3.97 > 0.10.0-r1 +0.10.0-r1 > 0.10.0 +0.10.0 < 0.10.1_rc1 +0.10.1_rc1 > 0.9.11 +0.9.11 < 394 +394 > 2.31 +2.31 > 1.0.1 +1.0.1 = 1.0.1 +1.0.1 < 1.0.3 +1.0.3 > 1.0.2 +1.0.2 = 1.0.2 +1.0.2 > 1.0.1 +1.0.1 = 1.0.1 +1.0.1 < 1.2.2 +1.2.2 < 2.1.10 +2.1.10 > 1.0.1 +1.0.1 < 1.0.2 +1.0.2 < 3.5.5 +3.5.5 > 1.1.1 +1.1.1 > 0.9.1 +0.9.1 < 1.0.2 +1.0.2 > 1.0.1 +1.0.1 < 1.0.2 +1.0.2 > 1.0.1 +1.0.1 = 1.0.1 +1.0.1 < 1.0.5 +1.0.5 > 0.8.5 +0.8.5 < 0.8.6-r3 +0.8.6-r3 < 2.3.17 +2.3.17 > 1.10-r5 +1.10-r5 < 1.10-r9 +1.10-r9 < 2.0.2 +2.0.2 > 1.1a +1.1a < 1.3a +1.3a > 1.0.2 +1.0.2 < 1.2.2-r1 +1.2.2-r1 > 1.0-r1 +1.0-r1 > 0.15.1b +0.15.1b < 1.0.1 +1.0.1 < 1.06-r1 +1.06-r1 < 1.06-r2 +1.06-r2 > 0.15.1b-r2 +0.15.1b-r2 > 0.15.1b +0.15.1b < 2.5.7 +2.5.7 > 1.1.2.1-r1 +1.1.2.1-r1 > 0.0.31 +0.0.31 < 0.0.50 +0.0.50 > 0.0.16 +0.0.16 < 0.0.25 +0.0.25 < 0.17 +0.17 > 0.5.0 +0.5.0 < 1.1.2 +1.1.2 < 1.1.3 +1.1.3 < 1.1.20 +1.1.20 > 0.9.4 +0.9.4 < 0.9.5 +0.9.5 < 6.3 +6.3 < 6.6 +6.6 > 6.3 +6.3 < 6.6 +6.6 > 1.2.12-r1 +1.2.12-r1 < 1.2.13 +1.2.13 < 1.2.14 +1.2.14 < 1.2.15 +1.2.15 < 8.0.12 +8.0.12 > 8.0.9 +8.0.9 > 1.2.3-r1 +1.2.3-r1 < 1.2.4-r1 +1.2.4-r1 > 0.1 +0.1 < 0.3.5 +0.3.5 < 1.5.22 +1.5.22 > 0.1.11 +0.1.11 < 0.1.12 +0.1.12 < 1.1.4.1 +1.1.4.1 > 1.1.0 +1.1.0 < 1.1.2 +1.1.2 > 1.0.3 +1.0.3 > 1.0.2 +1.0.2 < 2.6.26 +2.6.26 < 2.6.27 +2.6.27 > 1.1.17 +1.1.17 < 1.4.11 +1.4.11 < 22.7-r1 +22.7-r1 < 22.7.3-r1 +22.7.3-r1 > 22.7 +22.7 > 2.1_pre20 +2.1_pre20 < 2.1_pre26 +2.1_pre26 > 0.2.3-r2 +0.2.3-r2 > 0.2.2 +0.2.2 < 2.10.0 +2.10.0 < 2.10.1 +2.10.1 > 02.08.01b +02.08.01b < 4.77 +4.77 > 0.17 +0.17 < 5.1.1-r1 +5.1.1-r1 < 5.1.1-r2 +5.1.1-r2 > 5.1.1 +5.1.1 > 1.2 +1.2 < 5.1 +5.1 > 2.02.06 +2.02.06 < 2.02.10 +2.02.10 < 2.8.5-r3 +2.8.5-r3 < 2.8.6-r1 +2.8.6-r1 < 2.8.6-r2 +2.8.6-r2 > 2.02-r1 +2.02-r1 > 1.5.0-r1 +1.5.0-r1 > 1.5.0 +1.5.0 > 0.9.2 +0.9.2 < 8.1.2.20040524-r1 +8.1.2.20040524-r1 < 8.1.2.20050715-r1 +8.1.2.20050715-r1 < 20030215 +20030215 > 3.80-r4 +3.80-r4 < 3.81 +3.81 > 1.6d +1.6d > 1.2.07.8 +1.2.07.8 < 1.2.12.04 +1.2.12.04 < 1.2.12.05 +1.2.12.05 < 1.3.3 +1.3.3 < 2.6.4 +2.6.4 > 2.5.2 +2.5.2 < 2.6.1 +2.6.1 > 2.6 +2.6 < 6.5.1-r1 +6.5.1-r1 > 1.1.35-r1 +1.1.35-r1 < 1.1.35-r2 +1.1.35-r2 > 0.9.2 +0.9.2 < 1.07-r1 +1.07-r1 < 1.07.5 +1.07.5 > 1.07 +1.07 < 1.19 +1.19 < 2.1-r2 +2.1-r2 < 2.2 +2.2 > 1.0.4 +1.0.4 < 20060811 +20060811 < 20061003 +20061003 > 0.1_pre20060810 +0.1_pre20060810 < 0.1_pre20060817 +0.1_pre20060817 < 1.0.3 +1.0.3 > 1.0.2 +1.0.2 > 1.0.1 +1.0.1 < 3.2.2-r1 +3.2.2-r1 < 3.2.2-r2 +3.2.2-r2 < 3.3.17 +3.3.17 > 0.59s-r11 +0.59s-r11 < 0.65 +0.65 > 0.2.10-r2 +0.2.10-r2 < 2.01 +2.01 < 3.9.10 +3.9.10 > 1.2.18 +1.2.18 < 1.5.11-r2 +1.5.11-r2 < 1.5.13-r1 +1.5.13-r1 > 1.3.12-r1 +1.3.12-r1 < 2.0.1 +2.0.1 < 2.0.2 +2.0.2 < 2.0.3 +2.0.3 > 0.2.0 +0.2.0 < 5.5-r2 +5.5-r2 < 5.5-r3 +5.5-r3 > 0.25.3 +0.25.3 < 0.26.1-r1 +0.26.1-r1 < 5.2.1.2-r1 +5.2.1.2-r1 < 5.4 +5.4 > 1.60-r11 +1.60-r11 < 1.60-r12 +1.60-r12 < 110-r8 +110-r8 > 0.17-r2 +0.17-r2 < 1.05-r4 +1.05-r4 < 5.28.0 +5.28.0 > 0.51.6-r1 +0.51.6-r1 < 1.0.6-r6 +1.0.6-r6 > 0.8.3 +0.8.3 < 1.42 +1.42 < 20030719 +20030719 > 4.01 +4.01 < 4.20 +4.20 > 0.20070118 +0.20070118 < 0.20070207_rc1 +0.20070207_rc1 < 1.0 +1.0 < 1.13.0 +1.13.0 < 1.13.1 +1.13.1 > 0.21 +0.21 > 0.3.7-r3 +0.3.7-r3 < 0.4.10 +0.4.10 < 0.5.0 +0.5.0 < 0.5.5 +0.5.5 < 0.5.7 +0.5.7 < 0.6.11-r1 +0.6.11-r1 < 2.3.30-r2 +2.3.30-r2 < 3.7_p1 +3.7_p1 > 1.3 +1.3 > 0.10.1 +0.10.1 < 4.3_p2-r1 +4.3_p2-r1 < 4.3_p2-r5 +4.3_p2-r5 < 4.4_p1-r6 +4.4_p1-r6 < 4.5_p1-r1 +4.5_p1-r1 > 4.5_p1 +4.5_p1 < 4.5_p1-r1 +4.5_p1-r1 > 4.5_p1 +4.5_p1 > 0.9.8c-r1 +0.9.8c-r1 < 0.9.8d +0.9.8d < 2.4.4 +2.4.4 < 2.4.7 +2.4.7 > 2.0.6 +2.0.6 = 2.0.6 +2.0.6 > 0.78-r3 +0.78-r3 > 0.3.2 +0.3.2 < 1.7.1-r1 +1.7.1-r1 < 2.5.9 +2.5.9 > 0.1.13 +0.1.13 < 0.1.15 +0.1.15 < 0.4 +0.4 < 0.9.6 +0.9.6 < 2.2.0-r1 +2.2.0-r1 < 2.2.3-r2 +2.2.3-r2 < 013 +013 < 014-r1 +014-r1 > 1.3.1-r1 +1.3.1-r1 < 5.8.8-r2 +5.8.8-r2 > 5.1.6-r4 +5.1.6-r4 < 5.1.6-r6 +5.1.6-r6 < 5.2.1-r3 +5.2.1-r3 > 0.11.3 +0.11.3 = 0.11.3 +0.11.3 < 1.10.7 +1.10.7 > 1.7-r1 +1.7-r1 > 0.1.20 +0.1.20 < 0.1.23 +0.1.23 < 5b-r9 +5b-r9 > 2.2.10 +2.2.10 < 2.3.6 +2.3.6 < 8.0.12 +8.0.12 > 2.4.3-r16 +2.4.3-r16 < 2.4.4-r4 +2.4.4-r4 < 3.0.3-r5 +3.0.3-r5 < 3.0.6 +3.0.6 < 3.2.6 +3.2.6 < 3.2.7 +3.2.7 > 0.3.1_rc8 +0.3.1_rc8 < 22.2 +22.2 < 22.3 +22.3 > 1.2.2 +1.2.2 < 2.04 +2.04 < 2.4.3-r1 +2.4.3-r1 < 2.4.3-r4 +2.4.3-r4 > 0.98.6-r1 +0.98.6-r1 < 5.7-r2 +5.7-r2 < 5.7-r3 +5.7-r3 > 5.1_p4 +5.1_p4 > 1.0.5 +1.0.5 < 3.6.19-r1 +3.6.19-r1 > 3.6.19 +3.6.19 > 1.0.1 +1.0.1 < 3.8 +3.8 > 0.2.3 +0.2.3 < 1.2.15-r3 +1.2.15-r3 > 1.2.6-r1 +1.2.6-r1 < 2.6.8-r2 +2.6.8-r2 < 2.6.9-r1 +2.6.9-r1 > 1.7 +1.7 < 1.7b +1.7b < 1.8.4-r3 +1.8.4-r3 < 1.8.5 +1.8.5 < 1.8.5_p2 +1.8.5_p2 > 1.1.3 +1.1.3 < 3.0.22-r3 +3.0.22-r3 < 3.0.24 +3.0.24 = 3.0.24 +3.0.24 = 3.0.24 +3.0.24 < 4.0.2-r5 +4.0.2-r5 < 4.0.3 +4.0.3 > 0.98 +0.98 < 1.00 +1.00 < 4.1.4-r1 +4.1.4-r1 < 4.1.5 +4.1.5 > 2.3 +2.3 < 2.17-r3 +2.17-r3 > 0.1.7 +0.1.7 < 1.11 +1.11 < 4.2.1-r11 +4.2.1-r11 > 3.2.3 +3.2.3 < 3.2.4 +3.2.4 < 3.2.8 +3.2.8 < 3.2.9 +3.2.9 > 3.2.3 +3.2.3 < 3.2.4 +3.2.4 < 3.2.8 +3.2.8 < 3.2.9 +3.2.9 > 1.4.9-r2 +1.4.9-r2 < 2.9.11_pre20051101-r2 +2.9.11_pre20051101-r2 < 2.9.11_pre20051101-r3 +2.9.11_pre20051101-r3 > 2.9.11_pre20051101 +2.9.11_pre20051101 < 2.9.11_pre20061021-r1 +2.9.11_pre20061021-r1 < 2.9.11_pre20061021-r2 +2.9.11_pre20061021-r2 < 5.36-r1 +5.36-r1 > 1.0.1 +1.0.1 < 7.0-r2 +7.0-r2 > 2.4.5 +2.4.5 < 2.6.1.2 +2.6.1.2 < 2.6.1.3-r1 +2.6.1.3-r1 > 2.6.1.3 +2.6.1.3 < 2.6.1.3-r1 +2.6.1.3-r1 < 12.17.9 +12.17.9 > 1.1.12 +1.1.12 > 1.1.7 +1.1.7 < 2.5.14 +2.5.14 < 2.6.6-r1 +2.6.6-r1 < 2.6.7 +2.6.7 < 2.6.9-r1 +2.6.9-r1 > 2.6.9 +2.6.9 > 1.39 +1.39 > 0.9 +0.9 < 2.61-r2 +2.61-r2 < 4.5.14 +4.5.14 > 4.09-r1 +4.09-r1 > 1.3.1 +1.3.1 < 1.3.2-r3 +1.3.2-r3 < 1.6.8_p12-r1 +1.6.8_p12-r1 > 1.6.8_p9-r2 +1.6.8_p9-r2 > 1.3.0-r1 +1.3.0-r1 < 3.11 +3.11 < 3.20 +3.20 > 1.6.11-r1 +1.6.11-r1 > 1.6.9 +1.6.9 < 5.0.5-r2 +5.0.5-r2 > 2.86-r5 +2.86-r5 < 2.86-r6 +2.86-r6 > 1.15.1-r1 +1.15.1-r1 < 8.4.9 +8.4.9 > 7.6-r8 +7.6-r8 > 3.9.4-r2 +3.9.4-r2 < 3.9.4-r3 +3.9.4-r3 < 3.9.5-r2 +3.9.5-r2 > 1.1.9 +1.1.9 > 1.0.6 +1.0.6 < 5.9 +5.9 < 6.5 +6.5 > 0.40-r1 +0.40-r1 < 2.25b-r5 +2.25b-r5 < 2.25b-r6 +2.25b-r6 > 1.0.4 +1.0.4 < 1.0.5 +1.0.5 < 1.4_p12-r2 +1.4_p12-r2 < 1.4_p12-r5 +1.4_p12-r5 > 1.1 +1.1 > 0.2.0-r1 +0.2.0-r1 < 0.2.1 +0.2.1 < 0.9.28-r1 +0.9.28-r1 < 0.9.28-r2 +0.9.28-r2 < 0.9.28.1 +0.9.28.1 > 0.9.28 +0.9.28 < 0.9.28.1 +0.9.28.1 < 087-r1 +087-r1 < 103 +103 < 104-r11 +104-r11 > 104-r9 +104-r9 > 1.23-r1 +1.23-r1 > 1.23 +1.23 < 1.23-r1 +1.23-r1 > 1.0.2 +1.0.2 < 5.52-r1 +5.52-r1 > 1.2.5_rc2 +1.2.5_rc2 > 0.1 +0.1 < 0.71-r1 +0.71-r1 < 20040406-r1 +20040406-r1 > 2.12r-r4 +2.12r-r4 < 2.12r-r5 +2.12r-r5 > 0.0.7 +0.0.7 < 1.0.3 +1.0.3 < 1.8 +1.8 < 7.0.17 +7.0.17 < 7.0.174 +7.0.174 > 7.0.17 +7.0.17 < 7.0.174 +7.0.174 > 1.0.1 +1.0.1 < 1.1.1-r3 +1.1.1-r3 > 0.3.4_pre20061029 +0.3.4_pre20061029 < 0.4.0 +0.4.0 > 0.1.2 +0.1.2 < 1.10.2 +1.10.2 < 2.16 +2.16 < 28 +28 > 0.99.4 +0.99.4 < 1.13 +1.13 > 1.0.1 +1.0.1 < 1.1.2-r2 +1.1.2-r2 > 1.1.0 +1.1.0 < 1.1.1 +1.1.1 = 1.1.1 +1.1.1 > 0.6.0 +0.6.0 < 6.6.3 +6.6.3 > 1.1.1 +1.1.1 > 1.1.0 +1.1.0 = 1.1.0 +1.1.0 > 0.2.0 +0.2.0 < 0.3.0 +0.3.0 < 1.1.1 +1.1.1 < 1.2.0 +1.2.0 > 1.1.0 +1.1.0 < 1.6.5 +1.6.5 > 1.1.0 +1.1.0 < 1.4.2 +1.4.2 > 1.1.1 +1.1.1 < 2.8.1 +2.8.1 > 1.2.0 +1.2.0 < 4.1.0 +4.1.0 > 0.4.1 +0.4.1 < 1.9.1 +1.9.1 < 2.1.1 +2.1.1 > 1.4.1 +1.4.1 > 0.9.1-r1 +0.9.1-r1 > 0.8.1 +0.8.1 < 1.2.1-r1 +1.2.1-r1 > 1.1.0 +1.1.0 < 1.2.1 +1.2.1 > 1.1.0 +1.1.0 > 0.1.1 +0.1.1 < 1.2.1 +1.2.1 < 4.1.0 +4.1.0 > 0.2.1-r1 +0.2.1-r1 < 1.1.0 +1.1.0 < 2.7.11 +2.7.11 > 1.0.2-r6 +1.0.2-r6 > 1.0.2 +1.0.2 > 0.8 +0.8 < 1.1.1-r4 +1.1.1-r4 < 222 +222 > 1.0.1 +1.0.1 < 1.2.12-r1 +1.2.12-r1 > 1.2.8 +1.2.8 < 1.2.9.1-r1 +1.2.9.1-r1 > 1.2.9.1 +1.2.9.1 < 2.31-r1 +2.31-r1 > 2.31 +2.31 > 1.2.3-r1 +1.2.3-r1 > 1.2.3 +1.2.3 < 4.2.5 +4.2.5 < 4.3.2-r2 +1.3-r0 < 1.3.1-r0 +1.3_pre1-r1 < 1.3.2 +1.0_p10-r0 > 1.0_p9-r0 +0.1.0_alpha_pre2 < 0.1.0_alpha +1.0.0_pre20191002222144-r0 < 1.0.0_pre20210530193627-r0 +6.0_pre1 < 6.0 +6.1_pre1 < 6.1 +6.0_p1 > 6.0 +6.1_p1 > 6.1 +8.2.0 < 8.2.001 +8.2.0015 < 8.2.002 + +1.0~1234 < 1.0~2345 +1.0~1234-r1 < 1.0~2345-r0 +1.0~1234-r1 > 1.0~1234-r0 + +3.6.0 ~ 3.6 +3.6.9 ~ 3.6 +3.6_pre1 ~ 3.6 +3.6.0_pre1 ~ 3.6 +3.6.9_post1 ~ 3.6 +3.6.0 ~ 3.6.0 +3.5 <~ 3.6 +3.7 >~ 3.6 +3.6.0 !~ 3.8 +3.6.9 !~ 3.8 +3.8 !~ 3.8.1 +3.8.0 !~ 3.8.1 +3.8.0.1 !~ 3.8.1 +3.6_pre1 !~ 3.8 +3.6.0_pre1 !~ 3.8 +3.6.9_post1 !~ 3.8 + +1.2 +0.1_pre2 +0.1_pre2~1234abcd +0.1_p1_pre2 +0.1_alpha1_pre2 +0.1_git20240101_pre1 +! +!0.1bc +!0.1bc1 +!0.1a1 +!0.1a.1 +!0.1_pre2~ +!0.1_pre2~1234xbcd +!0.1_pre2~1234abcd_pre1 +!0.1_pre2-r1~1234xbcd +!0.1_foobar +!0.1_foobar1 +!0.1-pre1.1 +!0.1-r +!0.1-r2_pre1 +!0.1-r2_p3_pre1 +!0.1-r2-r3 +!0.1-r2.1 +!.1 +!a +!_pre1 +!-r1 +!0.1_ +!0.1_-r0 +!0.1__alpha +!0.1_1_alpha diff --git a/testdata/local/provenance.json b/testdata/local/provenance.json index d62ee93..fa3a6b5 100644 --- a/testdata/local/provenance.json +++ b/testdata/local/provenance.json @@ -178,6 +178,18 @@ "tests/conan_univers_range_test.json", "data/npm_advisory.json" ] + }, + { + "repository": "https://github.com/alpinelinux/apk-tools.git", + "commit": "900a3f5280bfad7ea22eda7de46d0d4c0c9ce8f2", + "license": "GPL-2.0-only", + "source_files": [ + "src/version.c", + "test/unit/version.data" + ], + "generated_files": [ + "data/apk_version.data" + ] } ] } diff --git a/vers.go b/vers.go index 5bd2f2e..f391756 100644 --- a/vers.go +++ b/vers.go @@ -186,8 +186,10 @@ func classifyVersionWithScheme(version, scheme string) (bool, bool) { return true, strings.HasPrefix(parsed.patch, "-") } return true, strings.HasPrefix(parsed.patch, "-alpha") || strings.HasPrefix(parsed.patch, "-beta") - case schemeGentoo, schemeAPK: + case schemeGentoo: return true, gentooVersionIsPrerelease(version) + case schemeAPK: + return true, apkVersionIsPrerelease(version) case schemeConan: _, _, prerelease, _, _ := splitConanVersion(version) return true, prerelease diff --git a/version.go b/version.go index 080f52c..1855d08 100644 --- a/version.go +++ b/version.go @@ -380,9 +380,7 @@ func compareFuncFor(scheme string) func(a, b string) int { case schemeIntDot: return compareIntDot case schemeAPK, schemeAlpine: - // This covers the vendored Alpine cases, but is not a full apk-tools - // implementation; APK has additional VCS suffix and letter rules. - return compareGentoo + return compareAPK case schemeGentoo: return compareGentoo case schemeALPM: