Skip to content

Commit 7633c96

Browse files
authored
Merge pull request #1985 from jaimergp/cross-compile
Add more details about cross-compilation
2 parents 6bd084f + efbb19d commit 7633c96

File tree

2 files changed

+223
-9
lines changed

2 files changed

+223
-9
lines changed

sphinx/src/maintainer/knowledge_base.rst

Lines changed: 222 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,102 @@ A package that needs all five compilers would define
303303
there's no need to specify ``libgcc`` or ``libgfortran``. There are additional informations about how conda-build 3 treats
304304
compilers in the `conda docs <https://docs.conda.io/projects/conda-build/en/stable/resources/compiler-tools.html>`__.
305305

306+
.. _cross-compilation:
307+
306308
Cross-compilation
307309
-----------------
308310

309-
For some other architectures (like ARM), packages can be built natively on that architecture or they can be cross-compiled.
310-
In other words built on a different common architecture (like x86_64) while still targeting the original architecture (ARM).
311-
This helps one leverage more abundant CI resources in the build architecture (x86_64).
311+
conda-forge defaults to native builds of packages for x86_64 on Linux, macOS and Windows, because
312+
that's the architecture powering the default CI runners. Other architectures are supported too,
313+
but they are not guaranteed to have native builds. In those platforms where we can't provide native
314+
CI runners, we can still resort to either cross-compilation or emulation.
315+
316+
Cross-compiling means building a package for a different architecture than the one the build process
317+
is running on. Given how abundant x86_64 runners are, most common cross-compilation setups will target
318+
non-x86_64 architectures from x86_64 runners.
319+
320+
Cross-compilation terminology usually distinguishes between two types of machine:
321+
322+
- Build: The machine running the building process.
323+
- Host: The machine we are building packages for.
324+
325+
.. note::
326+
327+
Some cross-compilation documentation might also distinguish between a third type of machine, the
328+
target machine. You can read more about it in `this Stack Overflow question
329+
<https://stackoverflow.com/questions/47010422/cross-compilation-terminologies-build-host-and-target>`__.
330+
For the purposes of conda-forge, we'll consider the target machine to be the same as the host.
331+
332+
.. _cross_compilation_howto:
333+
334+
How to enable cross-compilation
335+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
336+
337+
Cross-compilation settings depend on the ``build_platform`` and ``target_platform`` conda-build
338+
variables:
339+
340+
- ``build_platform``: The platform on which ``conda-build`` is running, which defines the ``build``
341+
environment in ``$BUILD_PREFIX``.
342+
- ``target_platform``: The platform on which the package will be installed. Defines the platform of
343+
the ``host`` environment in ``$PREFIX``. Defaults to the value of ``build_platform``.
344+
345+
To change the value of ``target_platform`` and enable cross-compilation, you must use
346+
the :ref:`build_platform` mapping in ``conda-forge.yml`` and then :ref:`rerender
347+
<dev_update_rerender>` the feedstock. This will generate the appropriate CI workflows and
348+
conda-build input metadata. See also :ref:`test` for how to skip the test phase when
349+
cross-compiling. Provided the requirements metadata and build scripts are written correctly, the
350+
package should just work. However, in some cases, it'll need some adjustments; see examples below
351+
for some common cases.
352+
353+
.. note::
354+
355+
The ``build_platform`` and ``target_platform`` variables are exposed as environment variables in
356+
the build scripts (e.g. ``$build_platform``), and also as Jinja variables in the ``meta.yaml``
357+
selectors (e.g. ``# [build_platform != target_platform]``).
358+
359+
In addition to these two variables, there are some more environment variables that are set by
360+
conda-forge's automation (e.g. ``conda-forge-ci-setup``, compiler activation packages, etc) that
361+
can aid in cross-compilation setups:
362+
363+
- ``CONDA_BUILD_CROSS_COMPILATION``: set to ``1`` when ``build_platform`` and ``target_platform``
364+
differ.
365+
- ``CONDA_TOOLCHAIN_BUILD``: the autoconf triplet expected for build platform.
366+
- ``CONDA_TOOLCHAIN_HOST``: the autoconf triplet expected for host platform.
367+
- ``CMAKE_ARGS``: arguments needed to cross-compile with CMake. Pass it to ``cmake`` in your build
368+
script.
369+
- ``MESON_ARGS``: arguments needed to cross-compile with Meson. Pass it to ``meson`` in your build
370+
script. Note a `cross build definition file <https://mesonbuild.com/Cross-compilation.html>`__ is
371+
automatically created for you too.
372+
- ``CC_FOR_BUILD``: C compilers targeting the build platform.
373+
- ``CXX_FOR_BUILD``: C++ compilers targeting the build platform.
374+
- ``CROSSCOMPILING_EMULATOR``: Path to the ``qemu`` binary for the host platform. Useful for running
375+
tests when cross-compiling.
376+
377+
This is all supported by two main conda-build features introduced in version 3:
378+
379+
- How `requirements metadata
380+
<https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#requirements-section>`__
381+
is expressed in ``meta.yaml``, which distinguishes between ``build`` and ``host`` platforms.
382+
- The ``compiler()`` Jinja function and underlying `conventions for the compiler packages
383+
<https://docs.conda.io/projects/conda-build/en/latest/resources/compiler-tools.html>`__.
384+
385+
Placing requirements in build or host
386+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
387+
388+
The rule of the thumb is:
312389

313-
A package needs to make a few changes in their recipe to be compatible with cross-compilation. Here are a few examples.
390+
- If it needs to run during the build, it goes in ``build``.
391+
- If it needs to be available on the target host, it goes in ``host``.
392+
- If both conditions are true, it belongs in both.
393+
394+
However, there are some exceptions to this rule; most notably Python cross-compilation
395+
(:ref:`see below <python_cross_compilation>`).
396+
397+
Cross-compilation examples
398+
^^^^^^^^^^^^^^^^^^^^^^^^^^
399+
400+
A package needs to make a few changes in their recipe to be compatible with cross-compilation. Here
401+
are a few examples.
314402

315403
A simple C library using autotools for cross-compilation might look like this:
316404

@@ -323,18 +411,59 @@ A simple C library using autotools for cross-compilation might look like this:
323411
- pkg-config
324412
- gnuconfig
325413
326-
In the build script, it would need to update the config files and guard any tests when cross-compiling:
414+
In the build script, it would need to update the config files and guard any tests when
415+
cross-compiling:
327416

328417
.. code-block:: sh
329418
330419
# Get an updated config.sub and config.guess
331420
cp $BUILD_PREFIX/share/gnuconfig/config.* .
332421
333422
# Skip ``make check`` when cross-compiling
334-
if [[ "${CONDA_BUILD_CROSS_COMPILATION}" != "1" ]]; then
423+
if [[ "${CONDA_BUILD_CROSS_COMPILATION:-}" != "1" || "${CROSSCOMPILING_EMULATOR:-}" != "" ]]; then
335424
make check
336425
fi
337426
427+
A simple C++ library using CMake for cross-compilation might look like this:
428+
429+
.. code-block:: yaml
430+
431+
requirements:
432+
build:
433+
- {{ compiler("cxx") }}
434+
- cmake
435+
- make
436+
437+
In the build script, it would need to update ``cmake`` call and guard any tests when cross-compiling:
438+
439+
.. code-block:: sh
440+
441+
# Pass ``CMAKE_ARGS`` to ``cmake``
442+
cmake ${CMAKE_ARGS} ..
443+
444+
# Skip ``ctest`` when cross-compiling
445+
if [[ "${CONDA_BUILD_CROSS_COMPILATION:-}" != "1" || "${CROSSCOMPILING_EMULATOR:-}" != "" ]]; then
446+
ctest
447+
fi
448+
449+
Similarly, with Meson, the ``meta.yaml`` needs:
450+
451+
.. code-block:: yaml
452+
453+
requirements:
454+
build:
455+
- {{ compiler("c") }}
456+
- {{ compiler("cxx") }}
457+
- meson
458+
- make
459+
460+
And this in ``build.sh``:
461+
462+
.. code-block:: sh
463+
464+
# Pass ``MESON_ARGS`` to ``meson``
465+
meson ${MESON_ARGS} builddir/
466+
338467
A simple Python extension using Cython and NumPy's C API would look like so:
339468

340469
.. code-block:: yaml
@@ -360,6 +489,90 @@ but merely to provide a starting point with some guidelines. Please look at `oth
360489

361490
.. _other recipes for more examples: https://github.com/search?q=org%3Aconda-forge+path%3Arecipe%2Fmeta.yaml+%22%5Bbuild_platform+%21%3D+target_platform%5D%22&type=code
362491

492+
.. _python_cross_compilation:
493+
494+
Details about cross-compiled Python packages
495+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
496+
497+
Cross-compiling Python packages is a bit more involved than other packages. The main pain point is
498+
that we need an executable Python interpreter (i.e. ``python`` in ``build``) that knows how to
499+
provide accurate information about the target platform. Since this is not officially supported, a
500+
series of workarounds are required to make it work. Refer to `PEP720
501+
<https://peps.python.org/pep-0720/>`__ or `the discussion in this issue
502+
<https://github.com/conda-forge/conda-forge.github.io/issues/1841>`__ for more information.
503+
504+
In practical terms, for conda-forge, this results into two extra metadata bits that are needed in
505+
``meta.yaml``:
506+
507+
- Adding ``cross-python_{{ target_platform }}`` in ``build`` requirements, provided by the
508+
`cross-python-feedstock <https://github.com/conda-forge/cross-python-feedstock>`__. This is a
509+
wrapper for the ``crossenv`` Python interpreters with `some activation logic that adjust some of
510+
the crossenv workarounds
511+
<https://github.com/conda-forge/cross-python-feedstock/blob/main/recipe/activate-cross-python.sh>`__
512+
so they work better with the conda-build setup.
513+
- Copying some Python-related packages from ``host`` to ``build`` with a ``[build_platform !=
514+
target_platform]`` selector:
515+
516+
- ``python`` itself, to support ``crossenv``.
517+
- Non-pure Python packages (i.e. they ship compiled libraries) that need to be present while the
518+
package is being built, like ``cython`` and ``numpy``.
519+
520+
In the terms of the `PEP720 <https://peps.python.org/pep-0720/>`__, the conda-forge setup
521+
implements the "faking the target environment" approach. More specifically, this will result in the
522+
following changes before the builds scripts run:
523+
524+
- A modified ``crossenv`` installation in ``$BUILD_PREFIX/venv``, mimicking the architecture of
525+
``$PREFIX``.
526+
- Forwarder binaries in ``$BUILD_PREFIX/bin`` that point to the ``crossenv`` installation.
527+
- Symlinks that expose the ``$BUILD_PREFIX`` site-packages in the ``crossenv`` installation, which
528+
is also included in ``$PYTHONPATH``.
529+
- A copy of all ``$PREFIX`` site-packages to ``$BUILD_PREFIX`` (except the compiled libraries).
530+
531+
All in all, this results in a setup where ``conda-build`` can run a ``$BUILD_PREFIX``-architecture
532+
``python`` interpreter that can see the packages in ``$PREFIX`` (with the compiled bits provided by
533+
their corresponding counterparts in ``$BUILD_PREFIX``) and sufficiently mimic that target
534+
architecture.
535+
536+
.. _emulation:
537+
538+
Emulated builds
539+
---------------
540+
541+
When cross-compilation is not possible, one can resort to emulation. This is a technique that uses
542+
a virtual machine (`QEMU <https://www.qemu.org/>`__) to emulate the target platform, which has a
543+
significant overhead. However, ``conda-build`` will see the target platform as native, so very
544+
little changes are usually needed in the recipe.
545+
546+
To enable emulated builds, you must use the :ref:`provider` mapping in ``conda-forge.yml``.
547+
This key maps a ``build_platform`` to a ``provider`` that will be used to emulate the platform.
548+
``conda-smithy`` will know how to detect whether the provider supports that platform natively or
549+
requires emulation, and will adjust the appropriate CI steps to ensure that QEMU runs the process.
550+
Ensure changes are applied by :ref:`rerendering <dev_update_rerender>` the feedstock.
551+
552+
Note that only Linux architectures are currently supported via emulation.
553+
554+
.. warning::
555+
556+
Emulated builds are very slow and incur an additional strain on conda-forge CI resources.
557+
Whenever possible, please consider cross-compilation instead. Only use emulated builds as a last
558+
resort.
559+
560+
Emulation examples
561+
^^^^^^^^^^^^^^^^^^
562+
563+
Configure ``conda-forge.yml`` to emulate ``linux-ppc64le``, but use native runners for ``linux-64``
564+
and ``linux-aarch64``. This works because ``linux-ppc64le`` is not natively supported by Azure, so
565+
``conda-smithy`` will add QEMU steps to emulate it. However, ``linux-64`` and ``linux-aarch64`` are
566+
natively supported by Azure and Travis CI, respectively, so no emulation is needed.
567+
568+
.. code-block:: yaml
569+
570+
provider:
571+
linux_aarch64: travis
572+
linux_ppc64le: azure
573+
linux_64: azure
574+
575+
363576
Rust Nightly
364577
------------
365578

@@ -430,13 +643,13 @@ When should CDTs be used?
430643
2. When a conda packaged library will not work properly.
431644
For example: a new ``glibc`` package means we would have to edit the elf interpreter of
432645
all the conda package binaries.
433-
646+
434647
What's are some good examples?
435648
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
436649

437650
1. The OpenCL loader (``ocl-icd`` together with ``ocl-icd-system``) provides an OpenCL
438651
loading library. The loader will look at OpenCL implementations given in
439-
``$CONDA_PREFIX/etc/OpenCL/vendors``.
652+
``$CONDA_PREFIX/etc/OpenCL/vendors``.
440653
For example: Pocl is a conda packaged implementation that runs OpenCL on the CPU. Vendor
441654
specific implementations like the NVIDIA OpenCL or ROCm OpenCL are not conda packaged, so we
442655
have to rely on the system. By installing ``ocl-icd-system`` we enable the loader to look at
@@ -1705,7 +1918,7 @@ Apple Silicon builds
17051918
17061919
The new Apple M1 processor is the first Apple Silicon supported by conda-forge
17071920
`osx-arm64 <https://github.com/conda-forge/conda-forge.github.io/issues/1126>`__ builds.
1708-
For new builds to be available, via cross-compilation, a migration is required for
1921+
For new builds to be available, via :ref:`cross-compilation <cross-compilation>`, a migration is required for
17091922
the package and its dependencies. These builds are experimental as many of them are untested.
17101923
17111924
To request a migration for a particular package and all its dependencies:

sphinx/src/user/introduction.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ conda-forge is a community effort that tackles these issues:
3434
- Care is taken that all packages are up-to-date.
3535
- Common standards ensure that all packages have compatible versions.
3636
- By default, we build packages for macOS, Linux AMD64 and Windows AMD64.
37+
Other architectures are also available on request (e.g. Apple Silicon, PowerPC, Linux ARM).
3738
- Many packages are updated by multiple maintainers with an easy option to become a maintainer.
3839
- An active core developer team is trying to also maintain abandoned packages.
3940

0 commit comments

Comments
 (0)