135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
|
|
package pack
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
func CheckHostCompatibility(manifest Manifest, hostVersion string) error {
|
||
|
|
targetHost := strings.TrimSpace(manifest.TargetHost)
|
||
|
|
if targetHost == "" {
|
||
|
|
return fmt.Errorf("pack manifest target_host is required")
|
||
|
|
}
|
||
|
|
if targetHost != "sub2api" {
|
||
|
|
return fmt.Errorf("pack target_host %q is not supported", targetHost)
|
||
|
|
}
|
||
|
|
|
||
|
|
normalizedHost, err := parseVersion(strings.TrimSpace(hostVersion))
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("parse host version %q: %w", hostVersion, err)
|
||
|
|
}
|
||
|
|
minVersion := strings.TrimSpace(manifest.MinHostVersion)
|
||
|
|
if minVersion != "" {
|
||
|
|
cmp, err := compareVersions(normalizedHost.raw, minVersion)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("compare min_host_version: %w", err)
|
||
|
|
}
|
||
|
|
if cmp < 0 {
|
||
|
|
return fmt.Errorf("host version %q is below min_host_version %q", hostVersion, minVersion)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
maxVersion := strings.TrimSpace(manifest.MaxHostVersion)
|
||
|
|
if maxVersion != "" {
|
||
|
|
ok, err := matchesMaxConstraint(normalizedHost.raw, maxVersion)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("compare max_host_version: %w", err)
|
||
|
|
}
|
||
|
|
if !ok {
|
||
|
|
return fmt.Errorf("host version %q is above max_host_version %q", hostVersion, maxVersion)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
type parsedVersion struct {
|
||
|
|
raw string
|
||
|
|
parts [3]int
|
||
|
|
}
|
||
|
|
|
||
|
|
func compareVersions(a, b string) (int, error) {
|
||
|
|
left, err := parseVersion(a)
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
right, err := parseVersion(b)
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
for i := 0; i < len(left.parts); i++ {
|
||
|
|
if left.parts[i] < right.parts[i] {
|
||
|
|
return -1, nil
|
||
|
|
}
|
||
|
|
if left.parts[i] > right.parts[i] {
|
||
|
|
return 1, nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func matchesMaxConstraint(hostVersion, maxVersion string) (bool, error) {
|
||
|
|
normalizedMax := normalizeVersion(maxVersion)
|
||
|
|
if strings.HasSuffix(normalizedMax, ".x") {
|
||
|
|
prefix := strings.TrimSuffix(normalizedMax, ".x")
|
||
|
|
parts := strings.Split(prefix, ".")
|
||
|
|
if len(parts) != 2 {
|
||
|
|
return false, fmt.Errorf("wildcard max version %q must be in N.N.x format", maxVersion)
|
||
|
|
}
|
||
|
|
host, err := parseVersion(hostVersion)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
major, err := strconv.Atoi(parts[0])
|
||
|
|
if err != nil {
|
||
|
|
return false, fmt.Errorf("parse major version %q: %w", parts[0], err)
|
||
|
|
}
|
||
|
|
minor, err := strconv.Atoi(parts[1])
|
||
|
|
if err != nil {
|
||
|
|
return false, fmt.Errorf("parse minor version %q: %w", parts[1], err)
|
||
|
|
}
|
||
|
|
if host.parts[0] < major {
|
||
|
|
return true, nil
|
||
|
|
}
|
||
|
|
if host.parts[0] > major {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
return host.parts[1] <= minor, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
cmp, err := compareVersions(hostVersion, maxVersion)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
return cmp <= 0, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseVersion(value string) (parsedVersion, error) {
|
||
|
|
normalized := normalizeVersion(value)
|
||
|
|
if normalized == "" {
|
||
|
|
return parsedVersion{}, fmt.Errorf("version is required")
|
||
|
|
}
|
||
|
|
parts := strings.Split(normalized, ".")
|
||
|
|
if len(parts) != 3 {
|
||
|
|
return parsedVersion{}, fmt.Errorf("version %q must be in N.N.N format", value)
|
||
|
|
}
|
||
|
|
|
||
|
|
var parsed parsedVersion
|
||
|
|
parsed.raw = normalized
|
||
|
|
for i, part := range parts {
|
||
|
|
number, err := strconv.Atoi(part)
|
||
|
|
if err != nil {
|
||
|
|
return parsedVersion{}, fmt.Errorf("parse version segment %q: %w", part, err)
|
||
|
|
}
|
||
|
|
parsed.parts[i] = number
|
||
|
|
}
|
||
|
|
return parsed, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalizeVersion(value string) string {
|
||
|
|
trimmed := strings.TrimSpace(value)
|
||
|
|
trimmed = strings.TrimPrefix(trimmed, "v")
|
||
|
|
trimmed = strings.TrimPrefix(trimmed, "V")
|
||
|
|
return trimmed
|
||
|
|
}
|