[{"data":1,"prerenderedAt":1704},["ShallowReactive",2],{"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns":3,"\u002Freports\u002Fplatform-strings":8},["Island",4],{"key":5,"result":6},"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns",{"head":7},{},{"id":9,"title":10,"authors":11,"body":13,"canonicalUrl":1690,"canonicalWebsiteName":1691,"category":1692,"date":1693,"description":1694,"extension":1695,"featured":1696,"fullWidthLayout":1696,"image":1697,"imageAlt":1697,"location":1697,"meta":1698,"metaImage":1697,"navigation":1699,"path":1700,"seo":1701,"stem":1702,"venue":1697,"venueUrl":1697,"__hash__":1703},"reports\u002Freports\u002Fplatform-strings.md","Platform Strings",[12],"andrew",{"type":14,"value":15,"toc":1668},"minimark",[16,41,46,82,123,144,148,172,201,210,233,237,272,304,357,363,367,391,412,461,503,507,531,550,578,597,601,618,659,662,666,688,703,721,725,738,750,757,783,787,795,844,895,899,951,955,985,1005,1027,1044,1048,1051,1256,1302,1306,1331,1339,1343,1346,1353,1357,1377,1403,1422,1426,1449,1472,1481,1500,1522,1580,1584,1587,1633],[17,18,19,20,24,25,28,29,32,33,36,37,40],"p",{},"Ask a dozen ecosystems what platform you're running on and you'll get a dozen different answers. An M1 Mac compiling a library is ",[21,22,23],"code",{},"aarch64-apple-darwin"," to LLVM, ",[21,26,27],{},"arm64-darwin"," to RubyGems, ",[21,30,31],{},"darwin\u002Farm64"," to Go, ",[21,34,35],{},"macosx_11_0_arm64"," to Python wheels, and ",[21,38,39],{},"darwin-arm64"," to npm, all describing the same chip on the same OS. Each naming scheme was designed for its own context with its own constraints, and every tool that needs to work across ecosystems ends up maintaining a translation table between them.",[42,43,45],"h3",{"id":44},"gnu-target-triples","GNU target triples",[17,47,48,49,52,53,62,63,70,71],{},"The format ",[21,50,51],{},"cpu-vendor-os"," dates to the early 1990s GNU autoconf toolchain. Per Bothner wrote ",[54,55,59],"a",{"href":56,"rel":57},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Fautoconf\u002Fmanual\u002Fautoconf-2.68\u002Fhtml_node\u002FSpecifying-Target-Triplets.html",[58],"nofollow",[21,60,61],{},"config.guess"," in 1992 to detect the build system's architecture. ",[54,64,67],{"href":65,"rel":66},"https:\u002F\u002Fgcc.gnu.org\u002Finstall\u002Fconfigure.html",[58],[21,68,69],{},"config.sub"," normalized the output using a long list of known CPUs and operating systems. The \"triple\" described three things: what CPU, what vendor made the hardware, and what OS it runs.",[72,73,74],"sup",{},[54,75,81],{"href":76,"ariaDescribedBy":77,"dataFootnoteRef":79,"id":80},"#user-content-fn-triple",[78],"footnote-label","","user-content-fnref-triple","1",[17,83,84,85,90,91,94,95,94,98,101,102,107,108,111,112,94,115,118,119,122],{},"GCC adopted this for ",[54,86,89],{"href":87,"rel":88},"https:\u002F\u002Fgcc.gnu.org\u002Fonlinedocs\u002Fgccint\u002FConfigure-Terms.html",[58],"cross-compilation",", where the build machine, host machine, and target machine might all differ. The vendor field (",[21,92,93],{},"pc",", ",[21,96,97],{},"apple",[21,99,100],{},"unknown",") is mostly decorative for the compiler itself but serves as a namespace to avoid collisions when the same arch-os pair needs different behavior. LLVM inherited the format through ",[54,103,106],{"href":104,"rel":105},"https:\u002F\u002Fclang.llvm.org\u002Fdocs\u002FCrossCompilation.html",[58],"Clang's cross-compilation support",", using ",[21,109,110],{},"\u003Carch>\u003Csub>-\u003Cvendor>-\u003Csys>-\u003Cenv>"," with the fourth field encoding ABI details like ",[21,113,114],{},"gnu",[21,116,117],{},"musl",", or ",[21,120,121],{},"msvc",".",[17,124,125,126,131,132,135,136,139,140,143],{},"ARM naming has been a persistent source of confusion. The architecture ARM calls \"AArch64\" is what Apple calls \"arm64\" and what LLVM accepts as both. A ",[54,127,130],{"href":128,"rel":129},"https:\u002F\u002Fgroups.google.com\u002Fg\u002Fllvm-dev\u002Fc\u002FPIBNR1EE9R0",[58],"Clang bug"," meant ",[21,133,134],{},"--target=aarch64-apple-ios"," and ",[21,137,138],{},"--target=arm64-apple-ios"," produced different results. ARM has used AArch64 consistently since the ARMv8 announcement in 2011, but Apple and the Linux kernel adopted ",[21,141,142],{},"arm64"," instead, and both names persist everywhere downstream.",[42,145,147],{"id":146},"go","Go",[17,149,150,151,154,155,158,159,164,165,122],{},"Go uses two environment variables rather than a combined string: ",[21,152,153],{},"GOOS=darwin GOARCH=arm64"," or ",[21,156,157],{},"GOOS=linux GOARCH=amd64",", with no vendor or ABI field. The ",[54,160,163],{"href":161,"rel":162},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Finstall\u002Fsource",[58],"canonical values"," are maintained in the Go source tree in ",[54,166,169],{"href":167,"rel":168},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Finternal\u002Fsyslist\u002Fsyslist.go",[58],[21,170,171],{},"syslist.go",[17,173,174,175,178,179,182,183,188,189,192,193,196,197,200],{},"This design traces back to Plan 9, where the ",[21,176,177],{},"$objtype"," environment variable selected the target architecture and ",[21,180,181],{},"mk"," used it to pick the right compiler. Go's creators (Rob Pike and Ken Thompson, both Plan 9 veterans) carried forward the idea that ",[54,184,187],{"href":185,"rel":186},"https:\u002F\u002F9p.io\u002Fsys\u002Fdoc\u002Fcomp.html",[58],"a single environment variable should select the build target",". The early Go compilers even used Plan 9's letter-based naming: ",[21,190,191],{},"8g"," for the x86 compiler, ",[21,194,195],{},"6g"," for amd64, ",[21,198,199],{},"5g"," for ARM.",[17,202,203,204,209],{},"Go can afford two flat variables because it statically links everything. It doesn't need to express which vendor made the hardware or which C library the system uses, because ",[54,205,208],{"href":206,"rel":207},"https:\u002F\u002Feli.thegreenplace.net\u002F2024\u002Fbuilding-static-binaries-with-go-on-linux\u002F",[58],"Go programs don't link against a C library by default",". CGo changes this, and when it does, cross-compilation gets harder. That's the tradeoff: the simple model works because Go opted out of the C ecosystem.",[17,211,212,213,216,217,220,221,223,224,229,230,232],{},"Go chose ",[21,214,215],{},"amd64"," over ",[21,218,219],{},"x86_64"," following Debian and Plan 9 conventions. This caused confusion early on, with users on Intel hardware wondering if ",[21,222,215],{}," downloads would work for them. The Go team eventually ",[54,225,228],{"href":226,"rel":227},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fissues\u002F3426",[58],"relabeled downloads"," as \"x86 64-bit\" while keeping the internal ",[21,231,215],{}," naming.",[42,234,236],{"id":235},"nodejs","Node.js",[17,238,239,240,135,243,246,247,94,250,94,253,256,257,260,261,94,264,94,266,256,269,122],{},"Node exposes ",[21,241,242],{},"process.platform",[21,244,245],{},"process.arch",", with platform values like ",[21,248,249],{},"darwin",[21,251,252],{},"linux",[21,254,255],{},"win32",", and ",[21,258,259],{},"freebsd",", and architecture values like ",[21,262,263],{},"x64",[21,265,142],{},[21,267,268],{},"ia32",[21,270,271],{},"arm",[17,273,274,276,277,279,280,282,283,285,286,288,289,291,292,297,298,300,301,303],{},[21,275,255],{}," for Windows and ",[21,278,263],{}," for 64-bit x86 both come from existing conventions that Node inherited rather than chose. ",[21,281,255],{}," is the Windows API subsystem name, used even on 64-bit Windows because the Win32 API kept its name, so ",[21,284,242],{}," returns ",[21,287,255],{}," on a machine that hasn't been 32-bit for a decade. ",[21,290,263],{}," is the name Microsoft and ",[54,293,296],{"href":294,"rel":295},"https:\u002F\u002Fv8.dev\u002F",[58],"V8"," use for the architecture, following the Windows SDK convention rather than the Linux ",[21,299,219],{}," or Debian ",[21,302,215],{}," convention.",[17,305,306,307,314,315,135,318,321,322,325,326,331,332,94,335,338,339,342,343,135,345,347,348,135,350,352,353,356],{},"npm's ",[54,308,311],{"href":309,"rel":310},"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv7\u002Fconfiguring-npm\u002Fpackage-json\u002F",[58],[21,312,313],{},"package.json"," has ",[21,316,317],{},"os",[21,319,320],{},"cpu"," fields (",[21,323,324],{},"{\"os\": [\"darwin\", \"linux\"], \"cpu\": [\"x64\", \"arm64\"]}",") that filter which platforms a package can install on, but npm itself has no built-in binary distribution mechanism, so the community invented one. Tools like ",[54,327,330],{"href":328,"rel":329},"https:\u002F\u002Fgithub.com\u002Fevanw\u002Fesbuild\u002Fblob\u002Fmain\u002Flib\u002Fnpm\u002Fnode-platform.ts",[58],"esbuild"," publish platform-specific binaries as scoped packages (",[21,333,334],{},"@esbuild\u002Fdarwin-arm64",[21,336,337],{},"@esbuild\u002Flinux-x64",") listed as ",[21,340,341],{},"optionalDependencies"," of a wrapper package, with ",[21,344,317],{},[21,346,320],{}," fields on each so npm silently skips the ones that don't match. The wrapper package then uses ",[21,349,242],{},[21,351,245],{}," at runtime to ",[21,354,355],{},"require()"," the right one. This pattern, popularized by esbuild and adopted by SWC and others, works but it's a convention built on top of npm's dependency resolution, not a feature npm designed for the purpose.",[17,358,359,360,362],{},"The Node scheme has no way to express libc version, OS version, or ABI, which is fine for most of the JavaScript ecosystem where packages are pure JavaScript. The cost shows up at the edges: native addons that need different builds for glibc vs musl Linux have to encode that information outside the platform string, and the ",[21,361,341],{}," pattern offers no help there.",[42,364,366],{"id":365},"python-wheels","Python wheels",[17,368,369,370,375,376,379,380,383,384,386,387,390],{},"Python's ",[54,371,374],{"href":372,"rel":373},"https:\u002F\u002Fpeps.python.org\u002Fpep-0425\u002F",[58],"wheel platform tags"," encode the most information of any ecosystem. A wheel filename like ",[21,377,378],{},"numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.whl"," contains the Python version (",[21,381,382],{},"cp312","), the ABI tag (",[21,385,382],{},"), and the platform tag (",[21,388,389],{},"manylinux_2_17_x86_64",").",[17,392,393,394,400,401,404,405,407,408,411],{},"The platform tag comes from ",[54,395,397],{"href":372,"rel":396},[58],[21,398,399],{},"distutils.util.get_platform()"," (removed in Python 3.12 along with the rest of ",[21,402,403],{},"distutils",") with hyphens and periods replaced by underscores. On macOS it encodes the minimum OS version: ",[21,406,35],{}," means \"macOS 11 or later on arm64.\" On Windows it's ",[21,409,410],{},"win_amd64",". On Linux it encodes the glibc version.",[17,413,414,415,420,421,424,425,430,431,434,435,430,440,443,444,449,450,453,454,457,458,122],{},"The manylinux story is its own saga. ",[54,416,419],{"href":417,"rel":418},"https:\u002F\u002Fpeps.python.org\u002Fpep-0513\u002F",[58],"PEP 513"," introduced ",[21,422,423],{},"manylinux1"," (glibc 2.5) so that compiled wheels could run on most Linux distributions. Then came ",[54,426,429],{"href":427,"rel":428},"https:\u002F\u002Fpeps.python.org\u002Fpep-0571\u002F",[58],"PEP 571"," for ",[21,432,433],{},"manylinux2010"," (glibc 2.12), then ",[54,436,439],{"href":437,"rel":438},"https:\u002F\u002Fpeps.python.org\u002Fpep-0599\u002F",[58],"PEP 599",[21,441,442],{},"manylinux2014"," (glibc 2.17). Each required a new PEP. ",[54,445,448],{"href":446,"rel":447},"https:\u002F\u002Fpeps.python.org\u002Fpep-0600\u002F",[58],"PEP 600"," finally created a pattern, ",[21,451,452],{},"manylinux_${GLIBCMAJOR}_${GLIBCMINOR}_${ARCH}",", so future glibc versions don't need new PEPs. The old names became aliases: ",[21,455,456],{},"manylinux1_x86_64"," is ",[21,459,460],{},"manylinux_2_5_x86_64",[17,462,463,464,469,470,473,474,477,478,481,482,485,486,94,488,94,491,494,495,497,498,500,501,122],{},"Python needs all this because wheels contain compiled C extensions that link against system libraries. A wheel built on a system with glibc 2.34 may call functions that don't exist on a system with glibc 2.17. The tag encodes the minimum compatible glibc version so pip can select the right wheel. ",[54,465,468],{"href":466,"rel":467},"https:\u002F\u002Fpeps.python.org\u002Fpep-0656\u002F",[58],"PEP 656"," added ",[21,471,472],{},"musllinux"," tags for Alpine Linux and other musl-based distributions, which most web developers encounter when they try to ",[21,475,476],{},"pip install"," a compiled package inside an Alpine Docker container and discover that ",[21,479,480],{},"manylinux"," wheels won't work there. The architecture field uses the ",[21,483,484],{},"uname"," convention (",[21,487,219],{},[21,489,490],{},"aarch64",[21,492,493],{},"i686","), which means no ",[21,496,215],{},", no ",[21,499,142],{},", and no ",[21,502,263],{},[42,504,506],{"id":505},"rubygems","RubyGems",[17,508,509,510,513,514,94,517,94,519,522,523,530],{},"RubyGems uses ",[21,511,512],{},"cpu-os"," pairs: ",[21,515,516],{},"x86_64-linux",[21,518,27],{},[21,520,521],{},"x86_64-linux-musl",". The format comes from ",[54,524,527],{"href":525,"rel":526},"https:\u002F\u002Fdocs.ruby-lang.org\u002Fen\u002Fmaster\u002FGem\u002FPlatform.html",[58],[21,528,529],{},"Gem::Platform",", which parses the string into cpu, os, and version components.",[17,532,533,534,546,547,549],{},"For years the Linux version field was unused. Then the musl libc question arrived. Alpine Linux uses musl instead of glibc, and a native extension compiled against glibc won't run on musl. RubyGems ",[54,535,538,539,135,542,545],{"href":536,"rel":537},"https:\u002F\u002Fgithub.com\u002Frubygems\u002Frubygems\u002Fpull\u002F5852",[58],"added ",[21,540,541],{},"linux-musl",[21,543,544],{},"linux-gnu"," platform variants"," starting in RubyGems 3.3.22. The matching logic has a special case: on Linux, \"no version\" defaults to ",[21,548,114],{},", but when matching a gem platform against the runtime platform, it acts as a wildcard.",[17,551,552,557,558,561,562,565,566,569,570,122],{},[54,553,556],{"href":554,"rel":555},"https:\u002F\u002Fgithub.com\u002Frake-compiler\u002Frake-compiler",[58],"rake-compiler-dock"," handles cross-compilation of native gems, and its platform naming has its own conventions. ",[21,559,560],{},"x64-mingw-ucrt"," targets Ruby 3.1+ on Windows (which switched to the UCRT runtime), while ",[21,563,564],{},"x64-mingw32"," targets Ruby 3.0 and earlier. Platform names ending in ",[21,567,568],{},"-linux"," are ",[54,571,574,575],{"href":572,"rel":573},"https:\u002F\u002Fgithub.com\u002Frake-compiler\u002Frake-compiler-dock\u002Fissues\u002F117",[58],"treated as aliases for ",[21,576,577],{},"-linux-gnu",[17,579,580,581,586,587,590,591,596],{},"RubyGems is now working on a more expressive system inspired by Python's wheels. Samuel Giddins has been building ",[54,582,585],{"href":583,"rel":584},"https:\u002F\u002Fblog.rubygems.org\u002F2025\u002F08\u002F21\u002Fjuly-rubygems-updates.html",[58],"experimental support for tag-based platform matching",", using a filename format of ",[21,588,589],{},"{gem_name}-{version}-{ruby tag}-{abi tag}-{platform tag}.gem2",". The proposed dimensions for platform matching are Ruby ABI, OS, OS version, CPU architecture, libc implementation, and libc version. This is ",[54,592,595],{"href":593,"rel":594},"https:\u002F\u002Ftraveling.engineer\u002Fposts\u002Fgoals-for-binary-gems\u002F",[58],"almost exactly the same set of dimensions"," that Python's wheel tags evolved to cover, arrived at independently.",[42,598,600],{"id":599},"debian-multiarch-tuples","Debian multiarch tuples",[17,602,603,604,609,610,613,614,617],{},"Debian uses ",[54,605,608],{"href":606,"rel":607},"https:\u002F\u002Fwiki.debian.org\u002FMultiarch\u002FTuples",[58],"multiarch tuples"," as directory names for architecture-specific library paths. ",[21,611,612],{},"\u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002F"," holds 64-bit x86 libraries, ",[21,615,616],{},"\u002Fusr\u002Flib\u002Faarch64-linux-gnu\u002F"," holds ARM64 libraries. The format is based on normalized GNU triplets but Debian chose its own canonical forms.",[17,619,620,621,623,624,627,628,630,631,634,635,630,638,641,642,646,647,650,651,654,655,658],{},"The Debian architecture name ",[21,622,215],{}," maps to the multiarch tuple ",[21,625,626],{},"x86_64-linux-gnu",". The architecture name ",[21,629,142],{}," maps to ",[21,632,633],{},"aarch64-linux-gnu",". ",[21,636,637],{},"armhf",[21,639,640],{},"arm-linux-gnueabihf",". That last one is notable: the hard-float\u002Fsoft-float distinction was originally supposed to go in the vendor field, which is what ",[54,643,645],{"href":606,"rel":644},[58],"GCC developers recommended",". But the vendor field is semantically private, not meant for cross-distribution use, so Debian instead appended ",[21,648,649],{},"hf"," to the ABI component: ",[21,652,653],{},"gnueabihf"," vs ",[21,656,657],{},"gnueabi",". The naming was argued over for months.",[17,660,661],{},"Multiarch exists to solve co-installation: running 32-bit and 64-bit libraries side by side on the same system. The tuple goes into the filesystem path, so it has to be a valid directory name, stable across releases, and unique per ABI. This is a different set of constraints than a compiler target triple. GCC and Debian independently developed tuple formats that look similar but diverge in the details, because they're optimizing for different things.",[42,663,665],{"id":664},"rust","Rust",[17,667,668,669,634,674,94,677,94,679,682,683,122],{},"Rust uses target triples that look like LLVM triples but are ",[54,670,673],{"href":671,"rel":672},"https:\u002F\u002Fdoc.rust-lang.org\u002Frustc\u002Fplatform-support.html",[58],"curated and normalized",[21,675,676],{},"x86_64-unknown-linux-gnu",[21,678,23],{},[21,680,681],{},"x86_64-pc-windows-msvc",". Where LLVM's triples are sprawling and sometimes inconsistent, Rust maintains an explicit list organized into ",[54,684,687],{"href":685,"rel":686},"https:\u002F\u002Fdoc.rust-lang.org\u002Frustc\u002Ftarget-tier-policy.html",[58],"tiers",[17,689,690,691,696,697,702],{},"Tier 1 targets are \"guaranteed to work\" with automated testing on every commit. As of 2025, ",[54,692,695],{"href":693,"rel":694},"https:\u002F\u002Fblog.rust-lang.org\u002F2024\u002F10\u002F17\u002FRust-1.82.0.html",[58],"aarch64-apple-darwin reached Tier 1"," in Rust 1.82 while ",[54,698,701],{"href":699,"rel":700},"https:\u002F\u002Fblog.rust-lang.org\u002F2025\u002F08\u002F19\u002Fdemoting-x86-64-apple-darwin-to-tier-2-with-host-tools\u002F",[58],"x86_64-apple-darwin dropped to Tier 2"," in Rust 1.90, reflecting Apple Silicon's dominance. Tier 2 targets build but may not pass all tests. Tier 3 targets are community-maintained.",[17,704,705,710,711,714,715,720],{},[54,706,709],{"href":707,"rel":708},"https:\u002F\u002Frust-lang.github.io\u002Frfcs\u002F0131-target-specification.html",[58],"RFC 0131"," established that Rust target triples map to but aren't identical to LLVM triples. A Rust target specification is a JSON file with an ",[21,712,713],{},"llvm-target"," field that can differ from the Rust-facing name. This lets Rust present clean, consistent names to users while translating to whatever LLVM expects internally. The ",[54,716,719],{"href":717,"rel":718},"https:\u002F\u002Fgithub.com\u002Fbytecodealliance\u002Ftarget-lexicon",[58],"target-lexicon"," crate from the Bytecode Alliance provides parsing and matching for these triples.",[42,722,724],{"id":723},"zig","Zig",[17,726,727,728,731,732,734,735,737],{},"Zig's default Windows target is ",[21,729,730],{},"x86_64-windows-gnu",", which looks like a contradiction. Here ",[21,733,114],{}," means MinGW-w64, not Linux. Zig ships MinGW-w64 headers so it can compile C code on Windows without requiring Visual Studio, and MinGW-w64 is binary-compatible with MSVC at the ABI level. Ballmer called Linux a cancer in 2001. Twenty-four years later, the practical way to cross-compile Windows binaries from Linux is a compiler that defaults to ",[21,736,114],{}," as the ABI. MinGW exists because for years the only way to target Windows without paying for Visual Studio was to build your own GCC cross-compiler.",[17,739,740,741,746,747,122],{},"Zig inherited LLVM's target triples but is actively redesigning them. An ",[54,742,745],{"href":743,"rel":744},"https:\u002F\u002Fgithub.com\u002Fziglang\u002Fzig\u002Fissues\u002F20690",[58],"accepted proposal"," by Alex Ronne Petersen would turn triples into quadruples, splitting the C library choice (API) from the ABI into separate components: ",[21,748,749],{},"\u003Carch>-\u003Cos>-\u003Capi>-\u003Cabi>",[17,751,752,753,756],{},"The proposal includes what it calls \"a fairly exhaustive survey of the ISA and ABI landscape,\" and the scale of the problem becomes clear quickly. RISC-V alone defines eight distinct ABIs (ilp32, ilp32f, ilp32d, ilp32e, lp64, lp64f, lp64d, lp64q). PowerPC has multiple ABIs (SVR4, EABI, Apple, ELFv1, ELFv2, AIX) plus variations in ",[21,754,755],{},"long double"," representation. LoongArch is \"the only architecture I'm aware of to have done the sane thing\" and put the ABI information into the ABI component from the start; the current triple format can't express most of these combinations cleanly.",[17,758,759,760,762,763,135,766,762,769,772,773,776,777,782],{},"Under the proposed scheme, ",[21,761,633],{}," becomes ",[21,764,765],{},"aarch64-linux-gnu-lp64",[21,767,768],{},"powerpc64le-linux-musl",[21,770,771],{},"powerpc64le-linux-musl-elfv2+ldbl64",", with the ",[21,774,775],{},"+"," syntax letting ABI options compose like feature flags. The proposal quotes ",[54,778,781],{"href":779,"rel":780},"https:\u002F\u002Fziglang.org\u002Flearn\u002Foverview\u002F",[58],"Zig's design philosophy",": \"Edge cases matter\" and \"Avoid local maximums,\" arguing that just because GNU triples are ubiquitous doesn't mean they're good. It's the same lesson Python learned from the other direction: it took four PEPs across five years to get manylinux right, discovering at each step that the problem space was bigger than the previous design assumed. Zig is trying to get it right from the compiler side before the package ecosystem calcifies around a format that can't express what it needs to.",[42,784,786],{"id":785},"conan-and-vcpkg","Conan and vcpkg",[17,788,789,790,794],{},"C and C++ have ",[54,791,793],{"href":792},"\u002Fideas\u002Fthe-c-shaped-hole-in-package-management","no canonical package registry",", so the two main C\u002FC++ package managers each invented their own platform identification from scratch.",[17,796,797,802,803,808,809,94,812,94,815,94,818,821,822,825,826,135,828,830,831,836,837,135,840,843],{},[54,798,801],{"href":799,"rel":800},"https:\u002F\u002Fconan.io\u002F",[58],"Conan"," doesn't use platform strings at all. It uses ",[54,804,807],{"href":805,"rel":806},"https:\u002F\u002Fdocs.conan.io\u002F2\u002Freference\u002Fconfig_files\u002Fsettings.html",[58],"hierarchical settings",": ",[21,810,811],{},"os=Macos",[21,813,814],{},"arch=armv8",[21,816,817],{},"compiler=apple-clang",[21,819,820],{},"compiler.version=15",". The settings are separate key-value pairs rather than a combined string, which means Conan never had to decide on a separator or field order. It also means Conan calls ARM64 ",[21,823,824],{},"armv8",", adding a third name for the architecture alongside ",[21,827,490],{},[21,829,142],{},". For cross-compilation, Conan 2 uses ",[54,832,835],{"href":833,"rel":834},"https:\u002F\u002Fdocs.conan.io\u002F2\u002Ftutorial\u002Fconsuming_packages\u002Fcross_building_with_conan.html",[58],"dual profiles"," (",[21,838,839],{},"--profile:build",[21,841,842],{},"--profile:host",") rather than encoding build and target in a single string.",[17,845,846,851,852,859,860,94,863,94,866,94,869,872,873,875,876,879,880,154,882,885,886,891,892,122],{},[54,847,850],{"href":848,"rel":849},"https:\u002F\u002Fvcpkg.io\u002F",[58],"vcpkg"," borrowed the word \"triplet\" but simplified the format to ",[54,853,856],{"href":854,"rel":855},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fvcpkg\u002Fconcepts\u002Ftriplets",[58],[21,857,858],{},"arch-os"," with optional suffixes: ",[21,861,862],{},"x64-windows",[21,864,865],{},"arm64-osx",[21,867,868],{},"x64-linux",[21,870,871],{},"x64-windows-static",". There's no vendor or ABI field, and vcpkg uses ",[21,874,263],{}," (the Windows SDK convention) and ",[21,877,878],{},"osx"," rather than ",[21,881,249],{},[21,883,884],{},"macos",". The ",[54,887,890],{"href":888,"rel":889},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fvcpkg\u002Fusers\u002Ftriplets",[58],"documentation"," cites the Android NDK's naming as inspiration for custom triplets, which is itself a variation on GNU triples with an API level suffix like ",[21,893,894],{},"aarch64-linux-android21",[42,896,898],{"id":897},"net",".NET",[17,900,901,902,907,908,911,912,94,915,94,918,94,921,924,925,930,931,135,934,937,938,940,941,943,944,943,947,950],{},".NET has ",[54,903,906],{"href":904,"rel":905},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fdotnet\u002Fcore\u002Frid-catalog",[58],"Runtime Identifiers"," (RIDs) that follow an ",[21,909,910],{},"os[-version]-arch"," pattern: ",[21,913,914],{},"linux-x64",[21,916,917],{},"win-arm64",[21,919,920],{},"osx-arm64",[21,922,923],{},"linux-musl-x64",". The format puts OS first, which is the opposite of most other schemes. Starting with .NET 8, Microsoft ",[54,926,929],{"href":927,"rel":928},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fdotnet\u002Fcore\u002Fcompatibility\u002Fdeployment\u002F8.0\u002Frid-asset-list",[58],"strongly recommends"," portable RIDs without version numbers, but version-specific RIDs like ",[21,932,933],{},"win10-x64",[21,935,936],{},"osx.13-arm64"," still exist for backward compatibility. The RID system includes a compatibility fallback graph: ",[21,939,920],{}," falls back to ",[21,942,878],{}," which falls back to ",[21,945,946],{},"unix",[21,948,949],{},"any",". NuGet uses these RIDs to select platform-specific assets from packages.",[42,952,954],{"id":953},"others","Others",[17,956,957,962,963,94,966,968,969,974,975,94,978,94,981,984],{},[54,958,961],{"href":959,"rel":960},"https:\u002F\u002Fswiftinit.org\u002Fdocs\u002Fswift-package-manager\u002Fbasics\u002Ftriple",[58],"Swift Package Manager"," uses LLVM target triples directly (",[21,964,965],{},"arm64-apple-macosx15.0",[21,967,676],{},"), inheriting both the format and its quirks without adding new ones. ",[54,970,973],{"href":971,"rel":972},"https:\u002F\u002Fkotlinlang.org\u002Fdocs\u002Fnative-target-support.html",[58],"Kotlin Multiplatform"," wraps LLVM triples in camelCase Gradle target names (",[21,976,977],{},"linuxX64",[21,979,980],{},"macosArm64",[21,982,983],{},"iosSimulatorArm64",") that are friendlier to type but map one-to-one to underlying triples.",[17,986,987,988,993,994,154,997,1000,1001,1004],{},"Java doesn't have a standard platform string format because most Java code doesn't need one. When it does, the ",[54,989,992],{"href":990,"rel":991},"https:\u002F\u002Fgithub.com\u002Ftrustin\u002Fos-maven-plugin",[58],"os-maven-plugin"," normalizes platform detection into a classifier string like ",[21,995,996],{},"linux-x86_64",[21,998,999],{},"osx-aarch_64",", adding an underscore to ",[21,1002,1003],{},"aarch_64"," that no other ecosystem uses.",[17,1006,1007,1012,1013,94,1016,94,1019,1022,1023,1026],{},[54,1008,1011],{"href":1009,"rel":1010},"https:\u002F\u002Fdocs.brew.sh\u002FBottles",[58],"Homebrew"," names its bottle builds using macOS marketing names: ",[21,1014,1015],{},"arm64_sonoma",[21,1017,1018],{},"arm64_ventura",[21,1020,1021],{},"ventura"," (Intel implied). On Linux it's ",[21,1024,1025],{},"x86_64_linux",". This makes Homebrew the only package manager that encodes the OS release name rather than a version number, though bottles built for older versions work fine on newer macOS releases.",[17,1028,1029,1034,1035,1037,1038,135,1040,1043],{},[54,1030,1033],{"href":1031,"rel":1032},"https:\u002F\u002Fnixos.org\u002F",[58],"Nix"," uses simple ",[21,1036,858],{}," pairs like ",[21,1039,516],{},[21,1041,1042],{},"aarch64-darwin",", clean and minimal but unable to distinguish between glibc and musl Linux in the system string.",[42,1045,1047],{"id":1046},"comparison","Comparison",[17,1049,1050],{},"The same four platforms, named by each ecosystem:",[1052,1053,1054,1075],"table",{},[1055,1056,1057],"thead",{},[1058,1059,1060,1063,1066,1069,1072],"tr",{},[1061,1062],"th",{},[1061,1064,1065],{},"64-bit x86 Linux",[1061,1067,1068],{},"ARM64 macOS",[1061,1070,1071],{},"64-bit x86 Windows",[1061,1073,1074],{},"ARM64 Linux",[1076,1077,1078,1094,1109,1123,1136,1149,1163,1175,1189,1205,1218,1231,1243],"tbody",{},[1058,1079,1080,1084,1087,1089,1091],{},[1081,1082,1083],"td",{},"GCC\u002FLLVM",[1081,1085,1086],{},"x86_64-pc-linux-gnu",[1081,1088,23],{},[1081,1090,681],{},[1081,1092,1093],{},"aarch64-unknown-linux-gnu",[1058,1095,1096,1098,1101,1103,1106],{},[1081,1097,147],{},[1081,1099,1100],{},"linux\u002Famd64",[1081,1102,31],{},[1081,1104,1105],{},"windows\u002Famd64",[1081,1107,1108],{},"linux\u002Farm64",[1058,1110,1111,1113,1115,1117,1120],{},[1081,1112,236],{},[1081,1114,914],{},[1081,1116,39],{},[1081,1118,1119],{},"win32-x64",[1081,1121,1122],{},"linux-arm64",[1058,1124,1125,1127,1129,1131,1133],{},[1081,1126,366],{},[1081,1128,389],{},[1081,1130,35],{},[1081,1132,410],{},[1081,1134,1135],{},"manylinux_2_17_aarch64",[1058,1137,1138,1140,1142,1144,1146],{},[1081,1139,506],{},[1081,1141,516],{},[1081,1143,27],{},[1081,1145,560],{},[1081,1147,1148],{},"aarch64-linux",[1058,1150,1151,1154,1156,1159,1161],{},[1081,1152,1153],{},"Debian",[1081,1155,626],{},[1081,1157,1158],{},"(N\u002FA)",[1081,1160,1158],{},[1081,1162,633],{},[1058,1164,1165,1167,1169,1171,1173],{},[1081,1166,665],{},[1081,1168,676],{},[1081,1170,23],{},[1081,1172,681],{},[1081,1174,1093],{},[1058,1176,1177,1180,1182,1185,1187],{},[1081,1178,1179],{},"Zig (current)",[1081,1181,626],{},[1081,1183,1184],{},"aarch64-macos-none",[1081,1186,730],{},[1081,1188,633],{},[1058,1190,1191,1193,1196,1199,1202],{},[1081,1192,801],{},[1081,1194,1195],{},"os=Linux, arch=x86_64",[1081,1197,1198],{},"os=Macos, arch=armv8",[1081,1200,1201],{},"os=Windows, arch=x86_64",[1081,1203,1204],{},"os=Linux, arch=armv8",[1058,1206,1207,1209,1211,1213,1215],{},[1081,1208,850],{},[1081,1210,868],{},[1081,1212,865],{},[1081,1214,862],{},[1081,1216,1217],{},"arm64-linux",[1058,1219,1220,1222,1224,1226,1229],{},[1081,1221,898],{},[1081,1223,914],{},[1081,1225,920],{},[1081,1227,1228],{},"win-x64",[1081,1230,1122],{},[1058,1232,1233,1235,1237,1239,1241],{},[1081,1234,1033],{},[1081,1236,516],{},[1081,1238,1042],{},[1081,1240,1158],{},[1081,1242,1148],{},[1058,1244,1245,1247,1249,1252,1254],{},[1081,1246,1011],{},[1081,1248,1025],{},[1081,1250,1251],{},"arm64_sequoia",[1081,1253,1158],{},[1081,1255,1158],{},[17,1257,1258,1259,94,1261,94,1263,1265,1266,94,1268,94,1270,1272,1273,1275,1276,94,1278,1280,1281,94,1283,1286,1287,94,1289,1280,1292,1295,1296,1298,1299,1301],{},"The same four platforms yield three names for 64-bit x86 (",[21,1260,219],{},[21,1262,215],{},[21,1264,263],{},"), four for ARM64 (",[21,1267,490],{},[21,1269,142],{},[21,1271,824],{},", and Maven's ",[21,1274,1003],{},"), three for macOS (",[21,1277,249],{},[21,1279,884],{},"\u002F",[21,1282,878],{},[21,1284,1285],{},"macosx",", plus Homebrew's version-specific names), and two for Windows (",[21,1288,255],{},[21,1290,1291],{},"windows",[21,1293,1294],{},"win","). RubyGems is interesting here because it uses both ARM64 names: ",[21,1297,27],{}," on macOS (following Apple's convention) but ",[21,1300,1148],{}," on Linux (following the kernel's convention). Two different names for the same architecture within a single ecosystem, while Conan sidesteps the entire format question by not using strings at all.",[42,1303,1305],{"id":1304},"why-everything-diverges","Why everything diverges",[17,1307,1308,1309,1311,1312,1314,1315,1317,1318,1321,1322,1324,1325,1327,1328,1330],{},"The architecture naming splits trace back to who each ecosystem inherited from. Go took ",[21,1310,215],{}," from Plan 9 and Debian, both of which used AMD's name since AMD designed the 64-bit extension to x86. Node got ",[21,1313,263],{}," from V8, which followed the Windows SDK convention. Python's ",[21,1316,219],{}," comes straight from ",[21,1319,1320],{},"uname -m"," on Linux via ",[21,1323,399],{},". Debian itself uses ",[21,1326,215],{}," as the architecture name but ",[21,1329,626],{}," as the multiarch tuple, because the two serve different purposes.",[17,1332,1333,1334,879,1336,1338],{},"The structural differences run deeper and trace to what each ecosystem actually ships. Go statically links by default, so it never needed a vendor or ABI field, while Python wheels contain compiled C extensions that link against system libraries and ended up encoding the glibc version out of necessity. Most npm packages are pure JavaScript, which is why Node's platform strings never grew libc or OS version fields. Rust curates its triple list with a tier system because it wants to guarantee that specific targets work with specific levels of CI coverage. Conan gave up on strings entirely in favor of structured key-value settings, avoiding the parsing and separator problems but making it harder to use where a single identifier is expected, like a filename or URL path. .NET's RIDs put OS first (",[21,1335,914],{},[21,1337,868],{},") because the runtime's fallback graph cares more about OS compatibility than architecture when selecting assets.",[42,1340,1342],{"id":1341},"dimensions","Dimensions",[17,1344,1345],{},"A platform identifier that fully describes a compilation target seems to need at least five dimensions: CPU architecture (x86_64, aarch64, riscv64), operating system (linux, darwin, windows), OS version (macOS 11+, sometimes implicit), ABI or calling convention (gnu, musl, msvc, eabihf), and libc implementation and version (glibc 2.17, musl 1.2, Linux-specific but critical for binary compatibility). Five is a lower bound. Zig's ABI survey suggests the real number is higher once you start cataloguing calling convention variations across architectures, and none of these dimensions account for CPU feature levels (AVX2, SSE4.2) that matter for optimized builds.",[17,1347,1348,1349,1352],{},"Different ecosystems cover different subsets depending on what problems they need to solve. Go and Node get by with just arch and OS, while Python needs four dimensions because wheels contain compiled C extensions that care about OS version and glibc compatibility. Conan's structured settings cover four or five dimensions depending on how you count compiler metadata, and Rust sits somewhere in between with three or four. The GNU\u002FLLVM triple format has slots for all five but doesn't enforce consistency in how they're filled. Zig's quadruple proposal is the most explicit attempt I've seen, with the fourth component separating the libc choice (API) from the calling convention (ABI), though the RISC-V and PowerPC examples in the proposal suggest that even this may not be enough without the ",[21,1350,1351],{},"+feature"," extension syntax.",[42,1354,1356],{"id":1355},"prior-art","Prior art",[17,1358,1359,1364,1365,1370,1371,1376],{},[54,1360,1363],{"href":1361,"rel":1362},"https:\u002F\u002Fgithub.com\u002Farchspec\u002Farchspec",[58],"archspec",", extracted from ",[54,1366,1369],{"href":1367,"rel":1368},"https:\u002F\u002Fspack.io\u002F",[58],"Spack",", models CPU microarchitecture naming as a directed acyclic graph. Its ",[54,1372,1375],{"href":1373,"rel":1374},"https:\u002F\u002Fgithub.com\u002Farchspec\u002Farchspec-json\u002Fblob\u002Fmaster\u002Fcpu\u002Fmicroarchitectures.json",[58],"JSON database"," tracks which microarchitectures are compatible with which, including feature sets like AVX2 and SSE4.2 and x86-64 microarchitecture levels (v2, v3, v4). It's probably the most rigorous treatment of the \"which CPU can run binaries compiled for which other CPU\" question, but it's silent on OS, libc, and ABI.",[17,1378,1379,1380,94,1383,1386,1387,1389,1390,1393,1394,1398,1399,1402],{},"Python's manylinux system (",[54,1381,419],{"href":417,"rel":1382},[58],[54,1384,448],{"href":446,"rel":1385},[58],") took a different slice of the problem, encoding glibc version into wheel platform tags. Four PEPs across five years to get from ",[21,1388,423],{}," to the general ",[21,1391,1392],{},"manylinux_x_y"," pattern. Ruby's ",[54,1395,1397],{"href":593,"rel":1396},[58],"binary gems RFC"," arrived at nearly the same set of dimensions: Ruby ABI, OS, OS version, CPU architecture, libc implementation, libc version. The proposed ",[21,1400,1401],{},".gem2"," filename format mirrors Python's wheel naming, and I haven't found evidence that either project drew directly from the other. Independent convergence on the same dimensions is arguably stronger evidence that those dimensions are the right ones than if one had simply copied the other's homework.",[17,1404,1405,1406,1410,1411,1415,1416,1421],{},"Zig's ",[54,1407,1409],{"href":743,"rel":1408},[58],"target quadruple proposal"," goes deeper on ABI enumeration than anything else I've found, cataloging calling convention variations across RISC-V, PowerPC, MIPS, and LoongArch. It's focused on compiler targets rather than package management, so it doesn't touch the libc version compatibility question that Python and Ruby spent years on. The Bytecode Alliance's ",[54,1412,719],{"href":1413,"rel":1414},"https:\u002F\u002Fcrates.io\u002Fcrates\u002Ftarget-lexicon",[58]," crate parses and matches Rust\u002FLLVM triples specifically, and the ",[54,1417,1420],{"href":1418,"rel":1419},"https:\u002F\u002Fcrates.io\u002Fcrates\u002Fplatforms",[58],"platforms"," crate maintains the tier list, but neither attempts to generalize across ecosystems.",[42,1423,1425],{"id":1424},"user-agents","User agents",[17,1427,1428,1429,1434,1435,1438,1439,1444,1445,1448],{},"Platform strings remind me of browser user agent strings, which went through a similar process of rational local decisions producing global incoherence. ",[54,1430,1433],{"href":1431,"rel":1432},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc1945",[58],"RFC 1945"," defined the User-Agent header in 1996 with a simple grammar: product name, slash, version. NCSA Mosaic sent ",[21,1436,1437],{},"NCSA_Mosaic\u002F2.0 (Windows 3.1)",". Netscape Navigator, codenamed ",[54,1440,1443],{"href":1441,"rel":1442},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMozilla_(mascot)",[58],"\"Mozilla\""," (a portmanteau of \"Mosaic\" and \"Godzilla\"), sent ",[21,1446,1447],{},"Mozilla\u002F1.0 (Win3.1)",". Netscape supported frames; Mosaic didn't. Web developers started checking for \"Mozilla\" in the user agent and sending frames-based pages only to browsers that matched.",[17,1450,1451,1452,1457,1458,1461,1462,1465,1466,1471],{},"When Internet Explorer 2 shipped with frame support, it couldn't get the frames-based pages because it wasn't Mozilla. Microsoft's solution was to declare IE \"",[54,1453,1456],{"href":1454,"rel":1455},"https:\u002F\u002Fwebaim.org\u002Fblog\u002Fuser-agent-string-history\u002F",[58],"Mozilla compatible","\": ",[21,1459,1460],{},"Mozilla\u002F1.22 (compatible; MSIE 2.0; Windows 95)",". Since most sniffers only checked the prefix, IE passed and got the right pages. Then Konqueror's KHTML engine was being blocked by sites that sniffed for Gecko, so it added ",[21,1463,1464],{},"(KHTML, like Gecko)"," to its string. Apple forked KHTML to make WebKit and Safari needed to pass checks for both Gecko and KHTML, so Safari's user agent claimed to be Mozilla, said its engine was \"like Gecko,\" and referenced KHTML. When Chrome shipped in 2008 using WebKit, it inherited all of this and ",[54,1467,1470],{"href":1468,"rel":1469},"https:\u002F\u002Fhumanwhocodes.com\u002Fblog\u002F2010\u002F01\u002F12\u002Fhistory-of-the-user-agent-string\u002F",[58],"added its own token",":",[1473,1474,1479],"pre",{"className":1475,"code":1477,"language":1478},[1476],"language-text","Mozilla\u002F5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit\u002F525.13\n    (KHTML, like Gecko) Chrome\u002F0.2.149.27 Safari\u002F525.13\n","text",[21,1480,1477],{"__ignoreMap":79},[17,1482,1483,1484,1487,1488,1493,1494,1499],{},"Every token except ",[21,1485,1486],{},"Chrome"," is a compatibility claim. It's not Mozilla, not Safari, and its engine descends from KHTML but is no longer KHTML. Chrome has since ",[54,1489,1492],{"href":1490,"rel":1491},"https:\u002F\u002Fwww.chromium.org\u002Fupdates\u002Fua-reduction\u002F",[58],"frozen most of the string"," to reduce fingerprinting, replacing it with structured ",[54,1495,1498],{"href":1496,"rel":1497},"https:\u002F\u002Fwicg.github.io\u002Fua-client-hints\u002F",[58],"Client Hints"," that servers can request individually. But the old string persists because too much code parses it.",[17,1501,1502,1503,1506,1507,1280,1509,1511,1512,1517,1518,1521],{},"Platform strings aren't adversarial in the same way, but they share the path-dependency. Every tool that works across ecosystems maintains its own mapping between formats. ",[54,1504,330],{"href":328,"rel":1505},[58]," maps Node's ",[21,1508,242],{},[21,1510,245],{}," to package names, ",[54,1513,1516],{"href":1514,"rel":1515},"https:\u002F\u002Fcibuildwheel.pypa.io\u002F",[58],"cibuildwheel"," maps Python platform tags to CI matrix entries, and ",[54,1519,556],{"href":554,"rel":1520},[58]," maps RubyGems platforms to GCC cross-compilation targets. These mappings are maintained independently, and discrepancies between them surface as bugs in specific platform combinations.",[17,1523,1524,1525,1530,1531,1536,1537,1542,1543,1548,1549,94,1552,94,1555,1558,1559,1561,1562,1564,1565,1568,1569,1572,1573,1576,1577,122],{},"In the spirit of ",[54,1526,1529],{"href":1527,"rel":1528},"https:\u002F\u002Fxkcd.com\u002F927\u002F",[58],"XKCD 927",", I've started building ",[54,1532,1535],{"href":1533,"rel":1534},"https:\u002F\u002Fgithub.com\u002Fgit-pkgs\u002Fplatforms",[58],"git-pkgs\u002Fplatforms"," as an attempt at a shared translation layer. The ",[54,1538,1541],{"href":1539,"rel":1540},"https:\u002F\u002Fgithub.com\u002Fgit-pkgs\u002Fplatforms\u002Fblob\u002Fmain\u002FSPEC.md",[58],"spec"," defines canonical names and parse\u002Fformat rules, and the ",[54,1544,1547],{"href":1545,"rel":1546},"https:\u002F\u002Fgithub.com\u002Fgit-pkgs\u002Fplatforms\u002Ftree\u002Fmain\u002Fdata",[58],"mapping data"," lives in three JSON files (",[21,1550,1551],{},"arches.json",[21,1553,1554],{},"oses.json",[21,1556,1557],{},"platforms.json",") that could be consumed by any language without taking a Go dependency. Writing the mapping data has been a good way to discover just how many special cases exist: RubyGems using ",[21,1560,142],{}," on macOS but ",[21,1563,490],{}," on Linux, Rust calling RISC-V ",[21,1566,1567],{},"riscv64gc"," while everyone else uses ",[21,1570,1571],{},"riscv64",", Debian spelling little-endian MIPS as ",[21,1574,1575],{},"mipsel"," while Go uses ",[21,1578,1579],{},"mipsle",[42,1581,1583],{"id":1582},"alignment","Alignment",[17,1585,1586],{},"The same platform identification problem keeps getting solved because the answers don't seem to travel well. Python's manylinux and Ruby's binary gems RFC converge on the same dimensions but use different names, Zig's ABI research seems directly relevant to Rust's target specification work but lives in a different issue tracker, and archspec's microarchitecture DAG could probably inform platform matching beyond Spack but as far as I can tell nobody else uses it.",[17,1588,1589,1590,1595,1596,1599,1600,94,1603,1599,1606,94,1609,1599,1612,256,1615,1618,1619,1624,1625,135,1627,1629,1630,1632],{},"Even ",[54,1591,1594],{"href":1592,"rel":1593},"https:\u002F\u002Fgithub.com\u002Fpackage-url\u002Fpurl-spec",[58],"PURL",", which solved the \"which package\" identity problem across ecosystems, punts on platform. Each PURL type defines its own qualifiers: ",[21,1597,1598],{},"pkg:deb"," uses ",[21,1601,1602],{},"arch",[21,1604,1605],{},"pkg:gem",[21,1607,1608],{},"platform",[21,1610,1611],{},"pkg:conda",[21,1613,1614],{},"subdir",[21,1616,1617],{},"pkg:npm"," has no platform qualifier at all. The values use whatever conventions each ecosystem already has, with no normalization. There's been ",[54,1620,1623],{"href":1621,"rel":1622},"https:\u002F\u002Fgithub.com\u002Fpackage-url\u002Fpurl-spec\u002Fissues\u002F186",[58],"ongoing pressure"," from the security community to standardize ",[21,1626,1602],{},[21,1628,1608],{}," qualifiers across types so that vulnerability scanners don't need the massive mapping files that tools like ",[21,1631,1516],{}," currently maintain, but the discussions have been open since 2022 without resolution. The one standard that was supposed to unify package identity across ecosystems left platform identification as an exercise for each type definition.",[1634,1635,1638,1644],"section",{"className":1636,"dataFootnotes":79},[1637],"footnotes",[1639,1640,1643],"h2",{"className":1641,"id":78},[1642],"sr-only","Footnotes",[1645,1646,1647],"ol",{},[1648,1649,1651,1652,1654,1655,1660,1661],"li",{"id":1650},"user-content-fn-triple","The name \"triple\" stuck even after a fourth field got added. ",[21,1653,1086],{}," has four components but everyone still calls it a triple. See ",[54,1656,1659],{"href":1657,"rel":1658},"https:\u002F\u002Fmcyoung.xyz\u002F2025\u002F04\u002F14\u002Ftarget-triples\u002F",[58],"\"What the Hell Is a Target Triple?\""," for more on this naming. ",[54,1662,1667],{"href":1663,"ariaLabel":1664,"className":1665,"dataFootnoteBackref":79},"#user-content-fnref-triple","Back to reference 1",[1666],"data-footnote-backref","↩",{"title":79,"searchDepth":1669,"depth":1669,"links":1670},2,[1671,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689],{"id":44,"depth":1672,"text":45},3,{"id":146,"depth":1672,"text":147},{"id":235,"depth":1672,"text":236},{"id":365,"depth":1672,"text":366},{"id":505,"depth":1672,"text":506},{"id":599,"depth":1672,"text":600},{"id":664,"depth":1672,"text":665},{"id":723,"depth":1672,"text":724},{"id":785,"depth":1672,"text":786},{"id":897,"depth":1672,"text":898},{"id":953,"depth":1672,"text":954},{"id":1046,"depth":1672,"text":1047},{"id":1304,"depth":1672,"text":1305},{"id":1341,"depth":1672,"text":1342},{"id":1355,"depth":1672,"text":1356},{"id":1424,"depth":1672,"text":1425},{"id":1582,"depth":1672,"text":1583},{"id":78,"depth":1669,"text":1643},"https:\u002F\u002Fnesbitt.io\u002F2026\u002F02\u002F17\u002Fplatform-strings","nesbitt.io","package-management","2026-02-17","An M1 Mac is aarch64-apple-darwin, arm64-darwin, darwin\u002Farm64, or macosx_11_0_arm64 depending on which tool you ask.","md",false,null,{},true,"\u002Freports\u002Fplatform-strings",{"title":10,"description":1694},"reports\u002Fplatform-strings","eTEIyKD8OxUGQ1SxqpE7-BJ4QXriX0MQQdAzbAeE0nI",1780596102874]