1"use strict";(globalThis.webpackChunkcf_infra_docs=globalThis.webpackChunkcf_infra_docs||[]).push([[20021],{49519(e,n,t){t.r(n),t.d(n,{assets:()=>h,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var o=t(18731),a=t(74848),r=t(28453);const i={authors:["isuruf"],tags:["infrastructure"]},s="Noarch variant packages for Python packages on conda-forge",h={authorsImageUrls:[void 0]},c=[];function l(e){const n={a:"a",code:"code",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.p,{children:"We introduce noarch variants for python packages on conda-forge\nthat have compiled extensions but with pure python reference\nimplementations to make life easier for early adopters of\nnew python variants."}),"\n",(0,a.jsx)(n.p,{children:"conda-forge packages have always been batteries included. When\na package has some build options turned off by default to reduce\ndependencies, we have enabled these options to give the most\nfunctionality and performance to our users."}),"\n",(0,a.jsxs)(n.p,{children:["In the Python world, some packages are written in C/C++/Cython\nto get the most performance out of a package. However these packages\nsometimes have a reference implementation written in Python. The Python\nreference implementation is a good way to check the C/C++/Cython\ncode against a much simpler python implementation and is also\nuseful for platforms like PyPy where the C/C++/Cython implementation\ncan be slower than the Python reference implementation due to the\nemulation of the Python C/C++ API by PyPy. For example for the Cython\npackage, setting the ",(0,a.jsx)(n.code,{children:"CYTHON_NO_COMPILE"})," environment variable\nwhen building the Cython wheel itself, it will use the Python reference\nimplementation. The only way to figure out if a package has a Python\nreference implementation is to look at the library's source code\nto see if ",(0,a.jsx)(n.code,{children:"extensions"})," are optional."]}),"\n",(0,a.jsx)(n.p,{children:"To support platforms like PyPy, some packages build wheels with\ncompiled extensions for the platforms that are\nknown to be more performant with the compiled extension, but also\nprovide a universal pure Python wheel for the other platforms.\nThis also provides a way for new Python versions and variants\nlike the free-threading Python build to use these packages by the\nearly adopters of these Python versions."}),"\n",(0,a.jsx)(n.p,{children:"On conda-forge we usually have compiled Python packages, but provide\nno reference implementation. This means early adopters of new Python\nversions need to wait for the conda-forge bot managed by @conda-forge/bot\nteam to start the migration and rebuild the packages. For example the\nfree-threading Python 3.13 build is still paused as\nconda-forge has decided to focus on the default (GIL enabled)\nPython 3.13 build first while upstream packages work on\nsupporting free-threading.\nAnother issue is that some packages have cyclic dependencies at build\nor test time and this requires some manual handling."}),"\n",(0,a.jsxs)(n.p,{children:["We have been adding ",(0,a.jsx)(n.code,{children:"noarch: python"})," variants for some feedstocks\nso that the compiled extension has higher priority and the pure\nPython extension has lower priority, which makes the conda solver\nuse the ",(0,a.jsx)(n.code,{children:"noarch: python"})," variant if no suitable compiled variant\nis available. One issue is that the linter might not like selectors\non noarch recipes. We added an option"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-yaml",children:"linter:\n skip:\n - lint_noarch_selectors\n"})}),"\n",(0,a.jsxs)(n.p,{children:["to ",(0,a.jsx)(n.code,{children:"conda-forge.yml"})," that will make the linter skip this warning/error."]}),"\n",(0,a.jsxs)(n.p,{children:["We build the two variants using a ",(0,a.jsx)(n.code,{children:"recipe/conda_build_config.yaml"}),"\nwith the contents,"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-yaml",children:"use_noarch:\n- true # [linux64]\n- false\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Then in ",(0,a.jsx)(n.code,{children:"recipe/meta.yaml"})," we make the following changes"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-yaml",children:"build:\n noarch: python # [use_noarch]\n track_features: # [use_noarch]\n - pyyaml_no_compile # [use_noarch]\n\nrequirements:\n build:\n - {{ compiler('c') }}\n - {{ stdlib(\"c\") }}\n host:\n - python # [not use_noarch]\n - python {{ python_min }}.* # [use_noarch]\n - setuptools\n - pip\n run:\n - python # [not use_noarch]\n - python >={{ python_min }}.* # [use_noarch]\n - yaml\n\ntest:\n requires:\n - pip\n - python {{ python_min }}.* # [use_noarch]\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Finally in the build script, we use the env variable ",(0,a.jsx)(n.code,{children:"use_noarch"}),"\nto set an option to force the extension to be pure python.\nIn the case of pyyaml, we can force that by setting the env variable\n",(0,a.jsx)(n.code,{children:"PYYAML_NO_LIBYAML"}),". A ",(0,a.jsx)(n.code,{children:"recipe/build.sh"})," might look like,"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:'if [[ "$use_noarch" == "true" ]]; then\n export PYYAML_NO_LIBYAML=1\nfi\n$PYTHON -m pip install .\n'})}),"\n",(0,a.jsx)(n.p,{children:"We list some PRs here as a reference for conda-forge maintainers who\nwant to experiment."}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.a,{href:"https://github.com/conda-forge/pyyaml-feedstock/pull/55",children:"pyyaml"})}),"\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.a,{href:"https://github.com/conda-forge/coverage-feedstock/pull/123",children:"coverage"})}),"\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.a,{href:"https://github.com/conda-forge/cython-feedstock/pull/147",children:"cython"})}),"\n",(0,a.jsx)(n.li,{children:(0,a.jsx)(n.a,{href:"https://github.com/conda-forge/aiohttp-feedstock/pull/99",children:"aiohttp"})}),"\n"]})]})}function d(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(l,{...e})}):l(e)}},28453(e,n,t){t.d(n,{R:()=>i,x:()=>s});var o=t(96540);const a={},r=o.createContext(a);function i(e){const n=o.useContext(r);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),o.createElement(r.Provider,{value:n},e.children)}},18731(e){e.exports=JSON.parse('{"permalink":"/blog/2024/10/15/python-noarch-variants","editUrl":"https://github.com/conda-forge/conda-forge.github.io/tree/refs/heads/main/blog/2024-10-15-python-noarch-variants.md","source":"@site/blog/2024-10-15-python-noarch-variants.md","title":"Noarch variant packages for Python packages on conda-forge","description":"We introduce noarch variants for python packages on conda-forge","date":"2024-10-15T00:00:00.000Z","tags":[{"inline":true,"label":"infrastru
1cture","permalink":"/blog/tags/infrastructure"}],"readingTime":3.12,"hasTruncateMarker":true,"authors":[{"name":"Isuru Fernando","title":"Member of conda-forge/core","url":"https://github.com/isuruf","imageURL":"https://github.com/isuruf.png","key":"isuruf","page":null}],"frontMatter":{"authors":["isuruf"],"tags":["infrastructure"]},"unlisted":false,"prevItem":{"title":"Announcing the new recipe format on conda-forge","permalink":"/blog/2025/02/27/conda-forge-v1-recipe-support"},"nextItem":{"title":"Python 3.13 builds on conda-forge","permalink":"/blog/2024/09/26/python-313"}}')}}]);
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.