diff --git a/data.go b/data.go index c737bf2..10ba2af 100644 --- a/data.go +++ b/data.go @@ -16,6 +16,8 @@ var osesJSON []byte //go:embed data/platforms.json var platformsJSON []byte +const jsonNull = "null" + // rawMapping is a JSON value that can be a string, array of strings, or null. type rawMapping = json.RawMessage @@ -90,7 +92,7 @@ func loadData() (*indices, error) { // resolveMapping extracts the string value(s) from a raw JSON mapping. // Returns the preferred (first) value and all values. func resolveMapping(raw rawMapping) (preferred string, all []string) { - if raw == nil || string(raw) == "null" { + if raw == nil || string(raw) == jsonNull { return "", nil } diff --git a/format.go b/format.go index 9d1ff20..b1d1b8b 100644 --- a/format.go +++ b/format.go @@ -5,6 +5,21 @@ import ( "strings" ) +// Common platform string constants used across formatting and parsing. +const ( + osLinux = "linux" + osDarwin = "darwin" + osWindows = "windows" + + abiGNU = "gnu" + abiMusl = "musl" + abiMSVC = "msvc" + abiEABI = "eabi" + abiEABIHF = "eabihf" + abiGNUEABI = "gnueabi" + abiGNUEABIHF = "gnueabihf" +) + // Format converts a canonical Platform into an ecosystem-specific string. func Format(eco Ecosystem, p Platform) (string, error) { if !validEcosystem(eco) { @@ -72,74 +87,25 @@ func compose(idx *indices, eco Ecosystem, p Platform) (string, error) { case Node: return osName + "-" + arch, nil case Rust: - vendor := p.Vendor - if vendor == "" { - vendor = defaultVendor(p.OS) - } - abi := p.ABI - if abi == "" && p.OS == "linux" { - abi = "gnu" - } - if abi != "" { - return arch + "-" + vendor + "-" + osName + "-" + rustABI(abi), nil - } - return arch + "-" + vendor + "-" + osName, nil + return composeTriple(arch, osName, p, rustABI), nil case RubyGems: - if p.ABI != "" && p.ABI != "gnu" { - return arch + "-" + osName + "-" + p.ABI, nil - } - return arch + "-" + osName, nil + return composeRubyGems(arch, osName, p), nil case Python: return composePython(arch, osName, p), nil case Debian: - abi := p.ABI - if abi == "" && p.OS == "linux" { - abi = "gnu" - } - if abi == "" { - return "", &ErrNoMapping{Ecosystem: eco, Platform: p} - } - return arch + "-" + osName + "-" + debianABI(abi), nil + return composeDebian(arch, osName, eco, p) case LLVM: - vendor := p.Vendor - if vendor == "" { - vendor = defaultVendor(p.OS) - } - abi := p.ABI - if abi == "" && p.OS == "linux" { - abi = "gnu" - } - if abi != "" { - return arch + "-" + vendor + "-" + osName + "-" + abi, nil - } - return arch + "-" + vendor + "-" + osName, nil + return composeTriple(arch, osName, p, identityABI), nil case NuGet: - if p.ABI == "musl" { - return osName + "-musl-" + arch, nil - } - return osName + "-" + arch, nil + return composeNuGet(arch, osName, p), nil case Vcpkg: return arch + "-" + osName, nil case Conan: return osName + "/" + arch, nil case Homebrew: - if p.OS != "darwin" { - return "", &ErrNoMapping{Ecosystem: eco, Platform: p} - } - return arch + "_darwin", nil + return composeHomebrew(arch, eco, p) case Swift: - vendor := p.Vendor - if vendor == "" { - vendor = defaultVendor(p.OS) - } - abi := p.ABI - if abi == "" && p.OS == "linux" { - abi = "gnu" - } - if abi != "" { - return arch + "-" + vendor + "-" + osName + "-" + abi, nil - } - return arch + "-" + vendor + "-" + osName, nil + return composeTriple(arch, osName, p, identityABI), nil case Kotlin: return osName + arch, nil case Maven: @@ -148,11 +114,60 @@ func compose(idx *indices, eco Ecosystem, p Platform) (string, error) { return "", &ErrNoMapping{Ecosystem: eco, Platform: p} } +func composeTriple(arch, osName string, p Platform, abiFn func(string) string) string { + vendor := p.Vendor + if vendor == "" { + vendor = defaultVendor(p.OS) + } + abi := p.ABI + if abi == "" && p.OS == osLinux { + abi = abiGNU + } + if abi != "" { + return arch + "-" + vendor + "-" + osName + "-" + abiFn(abi) + } + return arch + "-" + vendor + "-" + osName +} + +func identityABI(abi string) string { return abi } + +func composeRubyGems(arch, osName string, p Platform) string { + if p.ABI != "" && p.ABI != abiGNU { + return arch + "-" + osName + "-" + p.ABI + } + return arch + "-" + osName +} + +func composeDebian(arch, osName string, eco Ecosystem, p Platform) (string, error) { + abi := p.ABI + if abi == "" && p.OS == osLinux { + abi = abiGNU + } + if abi == "" { + return "", &ErrNoMapping{Ecosystem: eco, Platform: p} + } + return arch + "-" + osName + "-" + debianABI(abi), nil +} + +func composeNuGet(arch, osName string, p Platform) string { + if p.ABI == abiMusl { + return osName + "-musl-" + arch + } + return osName + "-" + arch +} + +func composeHomebrew(arch string, eco Ecosystem, p Platform) (string, error) { + if p.OS != osDarwin { + return "", &ErrNoMapping{Ecosystem: eco, Platform: p} + } + return arch + "_darwin", nil +} + func defaultVendor(os string) string { switch os { - case "darwin", "ios": + case osDarwin, "ios": return "apple" - case "windows": + case osWindows: return "pc" default: return "unknown" @@ -161,10 +176,10 @@ func defaultVendor(os string) string { func defaultABI(os string) string { switch os { - case "linux": - return "gnu" - case "windows": - return "msvc" + case osLinux: + return abiGNU + case osWindows: + return abiMSVC default: return "" } @@ -172,10 +187,10 @@ func defaultABI(os string) string { func rustABI(abi string) string { switch abi { - case "eabihf": - return "gnueabihf" - case "eabi": - return "gnueabi" + case abiEABIHF: + return abiGNUEABIHF + case abiEABI: + return abiGNUEABI default: return abi } @@ -183,12 +198,12 @@ func rustABI(abi string) string { func debianABI(abi string) string { switch abi { - case "eabihf": - return "gnueabihf" - case "eabi": - return "gnueabi" - case "gnu": - return "gnu" + case abiEABIHF: + return abiGNUEABIHF + case abiEABI: + return abiGNUEABI + case abiGNU: + return abiGNU default: return abi } @@ -196,7 +211,7 @@ func debianABI(abi string) string { func composePython(arch, osName string, p Platform) string { switch p.OS { - case "darwin": + case osDarwin: ver := p.OSVersion if ver == "" { if p.Arch == "aarch64" { @@ -207,8 +222,8 @@ func composePython(arch, osName string, p Platform) string { } verParts := underscoreVersion(ver) return "macosx_" + verParts + "_" + arch - case "linux": - if p.ABI == "musl" { + case osLinux: + if p.ABI == abiMusl { ver := p.LibCVersion if ver == "" { ver = "1.1" @@ -222,7 +237,7 @@ func composePython(arch, osName string, p Platform) string { } verParts := underscoreVersion(ver) return "manylinux_" + verParts + "_" + arch - case "windows": + case osWindows: if p.Arch == "i686" { return "win32" } diff --git a/parse.go b/parse.go index 85d25a8..1e0dda6 100644 --- a/parse.go +++ b/parse.go @@ -6,6 +6,13 @@ import ( "strings" ) +// Split limits for strings.SplitN in decompose functions. +const ( + splitTwo = 2 + splitThree = 3 + splitFour = 4 +) + var ( // manylinux_2_17_x86_64, musllinux_1_1_aarch64 reManylinux = regexp.MustCompile(`^(many|musl)linux_(\d+)_(\d+)_(\w+)$`) @@ -91,8 +98,8 @@ func decompose(idx *indices, eco Ecosystem, s string) (Platform, bool) { // go: os/arch func decomposeGo(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "/", 2) - if len(parts) != 2 { + parts := strings.SplitN(s, "/", splitTwo) + if len(parts) != splitTwo { return Platform{}, false } osName := resolveOS(idx, Go, parts[0]) @@ -105,8 +112,8 @@ func decomposeGo(idx *indices, s string) (Platform, bool) { // node: os-arch func decomposeNode(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 2) - if len(parts) != 2 { + parts := strings.SplitN(s, "-", splitTwo) + if len(parts) != splitTwo { return Platform{}, false } osName := resolveOS(idx, Node, parts[0]) @@ -119,8 +126,8 @@ func decomposeNode(idx *indices, s string) (Platform, bool) { // rust/llvm: arch-vendor-os[-abi] func decomposeRustLLVM(idx *indices, eco Ecosystem, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 4) - if len(parts) < 3 { + parts := strings.SplitN(s, "-", splitFour) + if len(parts) < splitThree { return Platform{}, false } arch := resolveArch(idx, eco, parts[0]) @@ -133,7 +140,7 @@ func decomposeRustLLVM(idx *indices, eco Ecosystem, s string) (Platform, bool) { return Platform{}, false } p := Platform{Arch: arch, OS: osName, Vendor: vendor} - if len(parts) == 4 { + if len(parts) == splitFour { p.ABI = normalizeABI(parts[3]) } return p, true @@ -141,8 +148,8 @@ func decomposeRustLLVM(idx *indices, eco Ecosystem, s string) (Platform, bool) { // rubygems: arch-os[-abi] or cpu-os[-version] func decomposeRubyGems(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 3) - if len(parts) < 2 { + parts := strings.SplitN(s, "-", splitThree) + if len(parts) < splitTwo { return Platform{}, false } arch := resolveArch(idx, RubyGems, parts[0]) @@ -154,7 +161,7 @@ func decomposeRubyGems(idx *indices, s string) (Platform, bool) { return Platform{}, false } p := Platform{Arch: arch, OS: osName} - if len(parts) == 3 { + if len(parts) == splitThree { p.ABI = normalizeABI(parts[2]) } return p, true @@ -167,13 +174,13 @@ func decomposePython(idx *indices, s string) (Platform, bool) { if arch == "" { return Platform{}, false } - abi := "gnu" - if m[1] == "musl" { - abi = "musl" + abi := abiGNU + if m[1] == abiMusl { + abi = abiMusl } return Platform{ Arch: arch, - OS: "linux", + OS: osLinux, ABI: abi, LibCVersion: m[2] + "." + m[3], }, true @@ -186,14 +193,14 @@ func decomposePython(idx *indices, s string) (Platform, bool) { } return Platform{ Arch: arch, - OS: "darwin", + OS: osDarwin, Vendor: "apple", OSVersion: m[1] + "." + m[2], }, true } if s == "win32" { - return Platform{Arch: "i686", OS: "windows", Vendor: "pc"}, true + return Platform{Arch: "i686", OS: osWindows, Vendor: "pc"}, true } if m := reWinPython.FindStringSubmatch(s); m != nil && m[1] != "" { @@ -201,7 +208,7 @@ func decomposePython(idx *indices, s string) (Platform, bool) { if arch == "" { return Platform{}, false } - return Platform{Arch: arch, OS: "windows", Vendor: "pc"}, true + return Platform{Arch: arch, OS: osWindows, Vendor: "pc"}, true } if m := reLinuxPython.FindStringSubmatch(s); m != nil { @@ -209,7 +216,7 @@ func decomposePython(idx *indices, s string) (Platform, bool) { if arch == "" { return Platform{}, false } - return Platform{Arch: arch, OS: "linux"}, true + return Platform{Arch: arch, OS: osLinux}, true } return Platform{}, false @@ -217,8 +224,8 @@ func decomposePython(idx *indices, s string) (Platform, bool) { // debian: arch-os-abi func decomposeDebian(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 3) - if len(parts) != 3 { + parts := strings.SplitN(s, "-", splitThree) + if len(parts) != splitThree { return Platform{}, false } arch := resolveArch(idx, Debian, parts[0]) @@ -235,20 +242,20 @@ func decomposeDebian(idx *indices, s string) (Platform, bool) { func normalizeABI(s string) string { s = strings.ToLower(s) switch { - case s == "gnu" || s == "gnueabi": - return "gnu" - case s == "gnueabihf": - return "eabihf" - case s == "musl": - return "musl" - case s == "msvc": - return "msvc" + case s == abiGNU || s == abiGNUEABI: + return abiGNU + case s == abiGNUEABIHF: + return abiEABIHF + case s == abiMusl: + return abiMusl + case s == abiMSVC: + return abiMSVC case strings.HasPrefix(s, "mingw"): return "mingw" - case s == "eabi": - return "eabi" - case s == "eabihf": - return "eabihf" + case s == abiEABI: + return abiEABI + case s == abiEABIHF: + return abiEABIHF } return s } @@ -263,16 +270,16 @@ func parsePythonVersions(s string, p *Platform) { // nuget: os-arch or os-musl-arch (e.g., linux-x64, linux-musl-x64, win-arm64, osx-x64) func decomposeNuGet(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 3) - if len(parts) == 3 && strings.ToLower(parts[1]) == "musl" { + parts := strings.SplitN(s, "-", splitThree) + if len(parts) == splitThree && strings.ToLower(parts[1]) == abiMusl { osName := resolveOS(idx, NuGet, parts[0]) arch := resolveArch(idx, NuGet, parts[2]) if osName == "" || arch == "" { return Platform{}, false } - return Platform{Arch: arch, OS: osName, ABI: "musl"}, true + return Platform{Arch: arch, OS: osName, ABI: abiMusl}, true } - if len(parts) < 2 { + if len(parts) < splitTwo { return Platform{}, false } osName := resolveOS(idx, NuGet, parts[0]) @@ -285,8 +292,8 @@ func decomposeNuGet(idx *indices, s string) (Platform, bool) { // vcpkg: arch-os (e.g., x64-linux, arm64-osx, x64-windows) func decomposeVcpkg(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 2) - if len(parts) != 2 { + parts := strings.SplitN(s, "-", splitTwo) + if len(parts) != splitTwo { return Platform{}, false } arch := resolveArch(idx, Vcpkg, parts[0]) @@ -301,8 +308,8 @@ func decomposeVcpkg(idx *indices, s string) (Platform, bool) { // Conan uses settings like os=Linux, arch=armv8 but we parse "os/arch" pairs func decomposeConan(idx *indices, s string) (Platform, bool) { // Try os/arch with slash separator - parts := strings.SplitN(s, "/", 2) - if len(parts) == 2 { + parts := strings.SplitN(s, "/", splitTwo) + if len(parts) == splitTwo { osName := resolveOS(idx, Conan, parts[0]) arch := resolveArch(idx, Conan, parts[1]) if osName != "" && arch != "" { @@ -310,8 +317,8 @@ func decomposeConan(idx *indices, s string) (Platform, bool) { } } // Try os-arch with dash separator - parts = strings.SplitN(s, "-", 2) - if len(parts) == 2 { + parts = strings.SplitN(s, "-", splitTwo) + if len(parts) == splitTwo { osName := resolveOS(idx, Conan, parts[0]) arch := resolveArch(idx, Conan, parts[1]) if osName != "" && arch != "" { @@ -324,8 +331,8 @@ func decomposeConan(idx *indices, s string) (Platform, bool) { // homebrew: arch_codename (e.g., arm64_sonoma, arm64_ventura) // We can only extract the arch since the codename maps to a macOS version, not a canonical OS func decomposeHomebrew(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "_", 2) - if len(parts) != 2 { + parts := strings.SplitN(s, "_", splitTwo) + if len(parts) != splitTwo { return Platform{}, false } arch := resolveArch(idx, Homebrew, parts[0]) @@ -333,7 +340,7 @@ func decomposeHomebrew(idx *indices, s string) (Platform, bool) { return Platform{}, false } // Homebrew is macOS-only - return Platform{Arch: arch, OS: "darwin", Vendor: "apple"}, true + return Platform{Arch: arch, OS: osDarwin, Vendor: "apple"}, true } // kotlin: camelCase DSL names (e.g., linuxX64, macosArm64, mingwX64, iosArm64, androidNativeArm64) @@ -354,8 +361,8 @@ func decomposeKotlin(idx *indices, s string) (Platform, bool) { // maven: os-arch (e.g., linux-x86_64, osx-aarch_64, windows-x86_64) func decomposeMaven(idx *indices, s string) (Platform, bool) { - parts := strings.SplitN(s, "-", 2) - if len(parts) != 2 { + parts := strings.SplitN(s, "-", splitTwo) + if len(parts) != splitTwo { return Platform{}, false } osName := resolveOS(idx, Maven, parts[0]) diff --git a/translate_test.go b/translate_test.go index 9cb6dd3..800d4e9 100644 --- a/translate_test.go +++ b/translate_test.go @@ -136,7 +136,7 @@ func TestTranslateRoundTrip(t *testing.T) { if p.Arch != "x86_64" { t.Errorf("round-trip Arch = %q, want x86_64", p.Arch) } - if p.OS != "linux" { + if p.OS != osLinux { t.Errorf("round-trip OS = %q, want linux", p.OS) } })