1"use strict";(globalThis.webpackChunkconda_dot_org||=[]).push([[34668],{77754(e,s,n){n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"ceps/cep-0033","title":"CEP 33 - Version literals and their ordering","description":"Title Version literals and their ordering","source":"@site/learn/ceps/cep-0033.md","sourceDirName":"ceps","slug":"/ceps/cep-0033","permalink":"/learn/ceps/cep-0033","draft":false,"unlisted":false,"editUrl":"https://github.com/conda-incubator/conda-dot-org/tree/main/learn/ceps/cep-0033.md","tags":[{"inline":true,"label":"ceps","permalink":"/learn/tags/ceps"}],"version":"current","frontMatter":{"sidebar_label":"CEP 33","tags":["ceps"]},"sidebar":"community","previous":{"title":"CEP 32","permalink":"/learn/ceps/cep-0032"},"next":{"title":"CEP 34","permalink":"/learn/ceps/cep-0034"}}');var r=n(74848),o=n(28453);const t={sidebar_label:"CEP 33",tags:["ceps"]},a="CEP 33 - Version literals and their ordering",l={},c=[{value:"Abstract",id:"abstract",level:2},{value:"Motivation",id:"motivation",level:2},{value:"Specification",id:"specification",level:2},{value:"Version literals",id:"version-literals",level:3},{value:"Ordering",id:"ordering",level:3},{value:"Rationale",id:"rationale",level:2},{value:"Rejected ideas",id:"rejected-ideas",level:2},{value:"Backwards compatibility",id:"backwards-compatibility",level:2},{value:"Further work",id:"further-work",level:2},{value:"Examples",id:"examples",level:2},{value:"References",id:"references",level:2},{value:"Copyright",id:"copyright",level:2}];function d(e){const s={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"cep-33---version-literals-and-their-ordering",children:"CEP 33 - Version literals and their ordering"})}),"\n",(0,r.jsx)(s.table,{children:(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Title "}),(0,r.jsx)(s.td,{children:" Version literals and their ordering "})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Status "}),(0,r.jsx)(s.td,{children:" Accepted "})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Author(s) "}),(0,r.jsx)(s.td,{children:"\n Jaime Rodr\xedguez-Guerra <[email protected]>,\n Bas Zalmstra <[email protected]>\n"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Created "}),(0,r.jsx)(s.td,{children:" Sep 26, 2025 "})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Updated "}),(0,r.jsx)(s.td,{children:" Mar 4, 2026 "})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Discussion "}),(0,r.jsx)(s.td,{children:" https://github.com/conda/ceps/pull/132 "})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:" Implementation "}),(0,r.jsx)(s.td,{children:" https://github.com/conda/conda/blob/6614653b1d9bdbffcef55e338d3220daed70c7f8/conda/models/version.py#L52, https://github.com/conda/rattler/blob/rattler-v0.37.4/crates/rattler_conda_types/src/version/mod.rs#L141 "})]})]})}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:['The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in ',(0,r.jsx)(s.a,{href:"https://www.ietf.org/rfc/rfc2119.txt",children:"RFC2119"})," when, and only when, they appear in all capitals, as shown here."]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"abstract",children:"Abstract"}),"\n",(0,r.jsx)(s.p,{children:"This CEP describes version literals as used in the conda ecosystem, and their ordering."}),"\n",(0,r.jsx)(s.h2,{id:"motivation",children:"Motivation"}),"\n",(0,r.jsx)(s.p,{children:"The motivation of this CEP is mostly informative, but will also try to clarify some ambiguous details that should be homogenized across existing implementations."}),"\n",(0,r.jsx)(s.h2,{id:"specification",children:"Specification"}),"\n",(0,r.jsx)(s.h3,{id:"version-literals",children:"Version literals"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"/learn/ceps/cep-0026",children:"CEP 26"})," only discussed the type of characters that can be part of a version string (or literal), and its maximum length:"]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsx)(s.p,{children:"[...] ver
1sion strings MUST only consist of digits, periods, lowercase ASCII letters, underscores, plus symbols, and exclamation marks. The maximum length of a version string MUST NOT exceed 64 characters."}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["The present CEP ",(0,r.jsx)(s.em,{children:"extends"})," these rules with additional constraints:"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Version literals MUST be composed of alphanumeric characters ",(0,r.jsx)(s.code,{children:"[A-Za-z0-9]"}),", separated into segments by periods ",(0,r.jsx)(s.code,{children:"."})," and underscores ",(0,r.jsx)(s.code,{children:"_"}),". Dashes ",(0,r.jsx)(s.code,{children:"-"})," are historically allowed and interpreted as underscores, but SHOULD NOT be used because they break filename conventions."]}),"\n",(0,r.jsxs)(s.li,{children:["Consecutive runs of digits MUST NOT exceed a value of ",(0,r.jsx)(s.code,{children:"2^31-1"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Empty segments (i.e. two consecutive periods, or a period plus an underscore) SHOULD NOT be allowed."}),"\n",(0,r.jsxs)(s.li,{children:["A single trailing underscore MAY be used exceptionally for comparisons against ",(0,r.jsx)(s.code,{children:"openssl 1.x"}),"-like version schemes (e.g. ",(0,r.jsx)(s.code,{children:"1.0.1_ < 1.0.1a"}),")."]}),"\n",(0,r.jsxs)(s.li,{children:["A single epoch number (a positive integer followed by ",(0,r.jsx)(s.code,{children:"!"}),") MAY prefix the rest of the string."]}),"\n",(0,r.jsxs)(s.li,{children:["A single local version string MAY be added at the end, separated by a plus symbol ",(0,r.jsx)(s.code,{children:"+"}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"ordering",children:"Ordering"}),"\n",(0,r.jsx)(s.p,{children:"Before being compared, version literals MUST be parsed into a list of segments (with each segment being a list of components) as follows:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["They are first split into ",(0,r.jsx)(s.em,{children:"epoch"}),", ",(0,r.jsx)(s.em,{children:"main version"}),", and ",(0,r.jsx)(s.em,{children:"local version"})," at ",(0,r.jsx)(s.code,{children:"!"})," and ",(0,r.jsx)(s.code,{children:"+"})," respectively.\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["If there is no ",(0,r.jsx)(s.code,{children:"!"}),", the epoch is set to ",(0,r.jsx)(s.code,{children:"0"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["If there is no ",(0,r.jsx)(s.code,{children:"+"}),", the local version is empty."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(s.li,{children:["The main version part is then split into components at ",(0,r.jsx)(s.code,{children:"."}),", ",(0,r.jsx)(s.code,{children:"_"}),", and ",(0,r.jsx)(s.code,{children:"-"}),".\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Each component is split again into consecutive runs of numerals and non-numerals."}),"\n",(0,r.jsx)(s.li,{children:"Subcomponents containing only numerals are converted to integers."}),"\n",(0,r.jsxs)(s.li,{children:["Strings are converted to lowercase, with special treatment for ",(0,r.jsx)(s.code,{children:"dev"})," and ",(0,r.jsx)(s.code,{children:"post"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Trailing underscores are considered part of the preceding string, if any."}),"\n",(0,r.jsxs)(s.li,{children:["When a component starts with a letter, the fill value ",(0,r.jsx)(s.code,{children:"0"})," is inserted before the letter."]}),"\n",(0,r.jsx)(s.li,{children:"Leading zeros in a component are removed."}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.li,{children:"The epoch and main version segments are concatenated."}),"\n",(0,r.jsx)(s.li,{children:"The same is repeated for the local version part, and stored as a separate list of segments."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"For example:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-python",children:">>> parse(\"1.2g.beta15.rc\")\n[[0], [1], [2, 'g'], [0, 'beta', 15], [0, 'rc']], []\n>>> parse(\"1!2.15.1_ALPHA\")\n[[1], [2], [15], [1], [0, 'alpha']], []\n>>> parse(\"1!2.15.1alpha_\")\n[[1], [2], [15], [1, 'alpha_']], []\n>>> parse(\"1!2.15.1_alpha+1.2.3h123\")\n[[1], [2], [15], [1], [0, 'alpha']], [[1], [2], [3, 'h', 123]]\n"})}),"\n",(0,r.jsx)(s.p,{children:"The resulting list of components MUST be compared as follows:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Integers are compared numerically."}),"\n",(0,r.jsxs)(s.li,{children:["Strings are compared lexicographically, case-insensitive. The substring ",(0,r.jsx)(s.code,{children:"dev"})," is always smaller."]}),"\n",(0,r.jsxs)(s.li,{children:["Strings are considered smaller than integers, except for ",(0,r.jsx)(s.code,{children:"post"}),", which is always greater."]}),"\n",(0,r.jsxs)(s.li,{children:["When a component has no correspondent, the missing component is assumed to be ",(0,r.jsx)(s.code,{children:"0"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Local versions are only compared when the main versions are identical. A version without a local part is treated as having an implicit local version of 0."}),"\n"]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:["Warning:\nPre-releases markers are sensitive to leading zeros and periods. While ",(0,r.jsx)(s.code,{children:'"1.1.0" == "1.1.0.0" == "1.1"'}),', the rule "When a component starts with a letter, the fill value ',(0,r.jsx)(s.code,{children:"0"}),' is inserted" results\nin ',(0,r.jsx)(s.code,{children:'"1.1.0rc" == "1.1.rc" > "1.1rc"'}),". See ",(0,r.jsx)(s.a,{href:"https://github.com/conda/conda/issues/12568",children:"conda#12568"}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"rationale",children:"Rationale"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"dev"})," substring is handled differently to allow ",(0,r.jsx)(s.code,{children:"dev"}
1)," pre-releases to sort before alphas, betas, and release candidates."]}),"\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"post"})," substring is handled differently to allow ",(0,r.jsx)(s.code,{children:"post"})," releases to sort after any equivalent final release."]}),"\n",(0,r.jsxs)(s.li,{children:["Missing components are treated like ",(0,r.jsx)(s.code,{children:"0"})," to allow equivalences like ",(0,r.jsx)(s.code,{children:"'1.1' == '1.1.0'"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["The ",(0,r.jsx)(s.code,{children:"0"})," fill value is used in components starting with letters to keep numbers and strings in phase, resulting in ",(0,r.jsx)(s.code,{children:"'1.1.a1' == '1.1.0a1'"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["Consecutive runs of digits are limited to prevent integer overflow issues upon parsing. The upper bound is the maximum value for 32-bit unsigned integers because ",(0,r.jsxs)(s.a,{href:"https://learn.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp?view=msvc-180#sizes-of-built-in-types",children:["MSVC still defaults to that for ",(0,r.jsx)(s.code,{children:"int"})]}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"rejected-ideas",children:"Rejected ideas"}),"\n",(0,r.jsx)(s.p,{children:"conda's version ordering is often compared to Python's PEP 440 and following adjustments, but they are not the same specification. We chose not to incorporate many good ideas in that specification so this CEP represents the current state of the ecosystem. In the future, we may revisit some of these rules to accommodate for special cases in prerelease ordering and their synonyms."}),"\n",(0,r.jsx)(s.h2,{id:"backwards-compatibility",children:"Backwards compatibility"}),"\n",(0,r.jsxs)(s.p,{children:["This CEP ",(0,r.jsx)(s.em,{children:"extends"})," ",(0,r.jsx)(s.a,{href:"/learn/ceps/cep-0026",children:"CEP 26"})," with more details about version literals."]}),"\n",(0,r.jsx)(s.p,{children:"It respects existing implementations and does not break backwards compatibility."}),"\n",(0,r.jsx)(s.h2,{id:"further-work",children:"Further work"}),"\n",(0,r.jsx)(s.p,{children:"This CEP only standardizes the current behavior exhibited across most implementations. There are many edge cases that the authors would like to improve in future efforts. Examples include:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"alpha"})," == ",(0,r.jsx)(s.code,{children:"a"})," (and similar) suffix normalizations."]}),"\n",(0,r.jsxs)(s.li,{children:["Require version literals to start with a digit to avoid situations like ",(0,r.jsx)(s.code,{children:"v0.1 != 0.1"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Revise PEP440 normalization rules and study which ones we should adopt."}),"\n",(0,r.jsx)(s.li,{children:"Propose a stricter subset of these rules to reduce ambiguity."}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"examples",children:"Examples"}),"\n",(0,r.jsx)(s.p,{children:"The ordering specification results in the following versions sorted in this way:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-sh",children:" 0.4\n == 0.4.0\n< 0.4.1.rc\n == 0.4.1.RC # case-insensitive comparison\n< 0.4.1+local # 'local' < 0\n< 0.4.1+0.local # '0.local' < 0\n< 0.4.1\n == 0.4.1+0 # no local is the same as '+0'\n< 0.4.1+1.local # '1.local' > 0\n< 0.5a1\n< 0.5b3\n< 0.5C1\n< 0.5\n< 0.9.6\n< 0.960923\n< 1.0\n< 1.1dev1 # special case 'dev'\n< 1.1a1\n< 1.1.0dev1\n == 1.1.dev1 # 0 is inserted before string\n< 1.1.a1\n< 1.1.0rc1\n< 1.1.0.0\n == 1.1.0\n == 1.1\n< 1.1.post1 # special case 'post'\n == 1.1.0post1\n< 1.1post1\n< 1996.07.12\n< 1!0.4.1 # epoch increased from implicit 0\n< 1!3.1.1.6\n< 2!0.4.1\n"})}),"\n",(0,r.jsx)(s.p,{children:"Local versions are not very common but there are some examples:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-text",children:"$ conda search \"*[version='*+*']\"\nLoading channels: done\n# Name Version Build Channel\npy-sirius-ms 2.1+sirius6.0.3 pyhd8ed1ab_0 conda-forge\npy-sirius-ms 2.1+sirius6.0.4 pyhd8ed1ab_0 conda-forge\npy-sirius-ms 2.1+sirius6.0.5 pyh
1d8ed1ab_0 conda-forge\npy-sirius-ms 2.1+sirius6.0.6 pyhd8ed1ab_0 conda-forge\npy-sirius-ms 2.1+sirius6.0.7 pyhd8ed1ab_0 conda-forge\npy-sirius-ms 2.1+sirius6.0.7 pyhd8ed1ab_1 conda-forge\npy-sirius-ms 3.0+sirius6.1.0 pyhd8ed1ab_0 conda-forge\npy-sirius-ms 3.0.1+sirius6.1.0 pyhd8ed1ab_0 conda-forge\npy-sirius-ms 3.1+sirius6.1.1 pyhd8ed1ab_0 conda-forge\nr-sirius-ms 2.1+sirius6.0.4 r44h57928b3_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.4 r44h694c41f_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.4 r44ha770c72_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.5 r44h57928b3_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.5 r44h694c41f_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.5 r44ha770c72_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.6 r44h57928b3_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.6 r44h694c41f_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.6 r44ha770c72_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.7 r44h57928b3_0 conda-forge\nr-sirius-ms 2.1+sirius6.0.7 r44h57928b3_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.7 r44h694c41f_0 conda-forge\nr-sirius-ms 2.1+sirius6.0.7 r44h694c41f_1 conda-forge\nr-sirius-ms 2.1+sirius6.0.7 r44ha770c72_0 conda-forge\nr-sirius-ms 2.1+sirius6.0.7 r44ha770c72_1 conda-forge\nr-sirius-ms 3.0.1+sirius6.1.0 r44h57928b3_0 conda-forge\nr-sirius-ms 3.0.1+sirius6.1.0 r44h694c41f_0 conda-forge\nr-sirius-ms 3.0.1+sirius6.1.0 r44ha770c72_0 conda-forge\nr-sirius-ms 3.1+sirius6.1.1 r44h57928b3_0 conda-forge\nr-sirius-ms 3.1+sirius6.1.1 r44h694c41f_0 conda-forge\nr-sirius-ms 3.1+sirius6.1.1 r44ha770c72_0 conda-forge\nr-sirius-ms 3.1+sirius6.1.1 r45h57928b3_1 conda-forge\nr-sirius-ms 3.1+sirius6.1.1 r45h694c41f_1 conda-forge\nr-sirius-ms 3.1+sirius6.1.1 r45ha770c72_1 conda-forge\ntypst-test 0.0.0.post105+699b871 h6e96688_0 conda-forge\ntypst-test 0.0.0.post105+699b871 h6e96688_1 conda-forge\ntypst-test 0.0.0.post106+2b4e689 h6e96688_0 conda-forge\n"})}),"\n",(0,r.jsx)(s.h2,{id:"references",children:"References"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsxs)(s.a,{href:"https://docs.conda.io/projects/conda/en/25.7.x/user-guide/concepts/pkg-specs.html#version-ordering",children:[(0,r.jsx)(s.code,{children:"conda 25.7.x"})," docs on Version Ordering"]}),"."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsxs)(s.a,{href:"https://github.com/baszalmstra/cep-version-tests",children:["Comparison between ",(0,r.jsx)(s.code,{children:"conda"}),", ",(0,r.jsx)(s.code,{children:"rattler"})," and ",(0,r.jsx)(s.code,{children:"mamba"})," parsers"]}),"."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsxs)(s.a,{href:"https://github.com/conda/ceps/pull/60",children:["Draft CEP about disallowing ",(0,r.jsx)(s.code,{children:"*"})," in version literals"]}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"copyright",children:"Copyright"}),"\n",(0,r.jsxs)(s.p,{children:["All CEPs are explicitly ",(0,r.jsx)(s.a,{href:"https://creativecommons.org/publicdomain/zero/1.0/",children:"CC0 1.0 Universal"}),"."]})]})}function h(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,s,n){n.d(s,{R:()=>t,x:()=>a});var i=n(96540);const r={},o=i.createContext(r);function t(e){const s=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:s},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.