Advanced noarch topics
Building noarch packages on other platforms
By default, noarch packages are built on Linux, and all dependencies must be available on Linux.
If a noarch package cannot be built on Linux, one or more noarch_platforms can be provided in conda-forge.yml.
For example, a package that can only be built on Windows can use the following conda-forge.yml:
noarch_platforms:
- win-64
A more complex example is pywin32-on-windows, which is built both on Linux and Windows, with build_number offsets to create a pair of packages.
noarch packages with OS-specific dependencies
It is possible to build noarch packages with runtime requirements that depend on the target OS
(Linux, Windows, MacOS), regardless the architecture (amd64, ARM, PowerPC, etc). This approach
relies on two concepts:
- Virtual packages.
Prefixed with a double underscore, they are used by conda to represent system properties as
constraints for the solver at install-time. We will use
__linux,__winor__osx, which are only present when the running platform is Linux, Windows, or MacOS, respectively.__unixis present in both Linux and MacOS. Note that this feature is only fully available on conda 4.10 or above. conda-forge.yml's noarch_platforms option.
The idea is to generate different variants of a noarch package.
While all of these variants are noarch and therefore are visible on all platforms, they feature a dependency on the OS-specific virtual package such as __linux that make them not installable on other platforms.
The resolver automatically selects the variant with the compatible virtual package, or throws an error if no compatible package can be found.
Let's say you have a pure Python package, perfectly eligible for noarch: python, but on Windows it requires windows-only-dependency.
A platform-specific recipe could look like the following:
- v0 (meta.yaml)
- v1 (recipe.yaml)
name: package
source:
# ...
build:
number: 0
requirements:
# ...
run:
- python
- numpy
- windows-only-dependency # [win]
name: package
source:
# ...
build:
number: 0
requirements:
# ...
run:
- python
- numpy
- if: win
then: windows-only-dependency
With this recipe, the build matrix will include separate builds for every supported platform, and for every supported Python version on every platform.
All the built package will have the same contents, and only win-64 packages will have different dependencies (though they still will be duplicated across all Python versions).
We can get it down to two packages if replace it with this other approach:
- v0 (meta.yaml)
- v1 (recipe.yaml)
name: package
source:
# ...
build:
number: 0
noarch: python
requirements:
host:
- python {{ python_min }}.*
# ...
run:
- python >={{ python_min }}
- numpy
- __unix # [unix]
- __win # [win]
- windows-only-dependency # [win]
name: package
source:
# ...
build:
number: 0
noarch: python
requirements:
host:
- python {{ python_min }}.*
# ...
run:
- python >={{ python_min }}
- numpy
- if: unix
then:
- __unix
else:
- __win
- windows-only-dependency
Do not forget to enumerate all platforms in your selectors, including virtual package dependencies! For OS-specific builds, the solver requires virtual packages to be present in the runtime requirements in order to select the correct package for your platform.
For example, if you have a windows-only-dependency as in the example above, but you
forget to enumerate the unix selector and virtual package, the result will be two noarch
builds: the Windows build will include a virtual package (__win), and the other
will include no virtual packages. The latter package, without
windows-only-dependency, can be selected by the solver on any OS, including Windows.
By default, conda-forge will only build noarch packages on a linux_64 CI runner, so
only the unix selector would be true. However, we can change this behaviour using
the noarch_platforms option in conda-forge.yml:
noarch_platforms:
- linux_64
- win_64
Once rerendered, this will result in two CI jobs.
If you need conditional dependencies on all three operating systems, this is how you do it:
- v0 (meta.yaml)
- v1 (recipe.yaml)
name: package
source:
# ...
build:
number: 0
noarch: python
requirements:
# ...
run:
- python >={{ python_min }}
- numpy
- __linux # [linux]
- __osx # [osx]
- __win # [win]
- linux-only-dependency # [linux]
- osx-only-dependency # [osx]
- windows-only-dependency # [win]
name: package
source:
# ...
build:
number: 0
noarch: python
requirements:
# ...
run:
- python >={{ python_min }}
- numpy
- if: linux
then:
- __linux
- linux-only-dependency
- if: osx
then:
- __osx
- osx-only-dependency
- if: win
then:
- __win
- windows-only-dependency
noarch_platforms:
- linux_64
- osx_64
- win_64
Again, remember to rerender after adding / modifying these files so the changes are applied.
noarch variants for different Python version ranges
Normally, a noarch: python package is built in a single variant for all supported Python versions.
However, in some cases you actually need to build two or more different variants, for example to account for dependencies that are conditional to Python versions.
In these cases, it is possible to parametrize the python_min parameter.
Before employing this solution, please consider whether you actually need to build multiple variants.
For example, common backport packages such as backports.zstd or typing_extensions can safely be listed as a dependency irrespective of Python version.
If your package uses different lower bounds conditionally to Python versions, you can usually use the highest bound to account for all versions.
python_min:
- "3.10"
- "3.14"
This will result in separate build configurations being created for different python_min values.
The parts of recipe that apply conditionally to Python version will need to be made conditional to the python_min value.
Additionally, the upper python version cap will also need to be adjusted conditionally.
Optionally, you may want to adjust the build string for readability.
- v0 (meta.yaml)
- v1 (recipe.yaml)
build:
number: {{ build_number }}
string: py{{ python_min | replace('.', '') }}_h{{ PKG_HASH }}_{{ build_number }}
noarch: python
script: {{ PYTHON }} -m pip install . -vv
requirements:
host:
- pip
- python {{ python_min }}.*
- setuptools
run:
- python >={{ python_min }} # [python_min == "3.14"]
- python >={{ python_min }},<3.14 # [python_min != "3.14"]
- onnxruntime >=1.24 # [python_min == "3.14"]
- onnxruntime >=1.20,<1.24 # [python_min != "3.14"]
build:
number: ${{ build_number }}
string: py${{ python_min | version_to_buildstring }}_h${{ hash }}_${{ build_number }}
noarch: python
script: ${{ PYTHON }} -m pip install . -vv
requirements:
host:
- pip
- python ${{ python_min }}.*
- setuptools
run:
- if: match(python_min, '>=3.14')
then:
- python >=${{ python_min }}
- onnxruntime >=1.24
else:
- python >=${{ python_min }},<3.14
- onnxruntime >=1.20,<1.24