Crossenv: A Virtual Environments for Cross-Compiling Python Extension Modules¶
Note
This documentation is in the early stages.
Porting a Python app to an embedded device can be complicated. Once you have Python built for your system, you may find yourself needing to include many third-party libraries. Pure-Python libraries usually just work, but many popular libraries rely on compiled C code, which can be challenging to build.
This package is a tool for cross-compiling extension modules. It creates a
special virtual environment such that pip
or setup.py
will cross
compile packages for you, often with no further work on your part.
It can be used to:
Build binary wheels, for installation on target.
Install packages to a directory for upload or inclusion in a firmware image.
Note
While this tool can cross-compile most Python packages, it can’t solve all the problems of cross-compiling, and it can’t make cross-compiling a completely pain-free process. In some cases manual intervention may still be necessary.
This tool requires Python 3.5 or higher (host and build). Significant work has gone into cross-compiling Python in newer versions, and many of the techniques needed to do the cross compilation properly are not available on older releases.
This tool currently only supports Linux build machines.
Introduction¶
Requirements¶
Crossenv requires Python 3.5 or higher (host and build). Significant work has gone into cross-compiling Python in newer versions, and many of the techniques needed to do the cross compilation properly are not available on older releases.
Crossenv currently only supports Linux build machines. Other operating systems may work, but they are untested and unsupported.
Vocabulary¶
There is no standard vocabulary for the pieces that go into cross-compiling, and different resources will often use contradictory terms. To prevent confusion we use the GNU terminology exclusively, which is used by Python itself.
Host |
The machine you are building for. (Android, iOS, other embedded systems.) |
Build |
The machine you are building on. (Probably your desktop.) |
Host-python |
The compiled Python binary and libraries that run on Host |
Build-python |
The compiled Python binary and libraries that run on Build. |
Cross-python |
Build-python, configured specially to build packages that can be run with Host-python. This tool creates Cross-python. |
How it works¶
Python makes a note of the compiler and compiler flags used when it was built.
(This information can be viewed by running python3 -m sysconfig
.) When
distutils
or setuptools
attempts to create an extension module, they
compile the extension using these recorded values along with reported
information about the currently running system.
Cross-python creates a virtual environment that, when activated, tricks
Build-Python into reporting all system information exactly as Host-python
would. When done correctly, a side effect of this is that distutils
and
setuptools
will cross-compile when building packages. All of the normal
packaging machinery still works correctly, so dependencies, ABI tags, and so
forth all work as expected.
Installation¶
Crossenv can be installed using pip (using build-python):
$ pip install crossenv
“Quick” Start¶
Cross compiling can be challenging, and crossenv is focused only on one particular piece. As such, this section is not a complete guide.
Build build-python¶
Don’t trust a build-python that you didn’t build yourself. The version of python that comes with a Linux distribution is usually patched by the maintainers in ways that are subtly incompatible with the stock Python source. Normally this isn’t an issue, but when using crossenv, build-python will end up running some of host-python’s (unpatched) code. The end result is one of many obscure errors.
Build-python and host-python must be the exact same version. As above, one may need to execute the other’s bytecode, which only works if they have the same version.
For general build instructions refer to the Python Developer’s Guide. You
don’t need a debug build, so configure --prefix=/path/to/build-python
is
usually enough.
At a minimum, you need zlib and openssl to build. Since build-python only
exists to build packages, you can often get away with leaving most optional
components disabled. It’s usually sufficient to build just enough to get pip
working, which requires the ssl
and zlib
modules. (More complicated
builds may require more. It depends very much on your specific requirements.)
Build or obtain host-python¶
In this quick start we assume you are building host-python yourself. In other cases you may be targeting a pre-built system image. A pre-built image has it’s own challenges, which are covered elsewhere.
You will need to build any host dependencies beforehand. So, for example, if you want host-python to be able to communicate over a network, you may need to cross-compile OpenSSL. Building these dependencies is beyond the scope of this project.
Building host-python requires a working build-python. We recommend putting
build-python in your $PATH
for the configure script to find. Here is
an example of a configure command used for testing crossenv against an ARM
host:
$ PATH=/path/to/build-python/bin:$PATH \
./configure --prefix=/path/to/host-python \
--host=arm-linux-musleabihf \
--build=x86_64-linux-gnu \
--without-ensurepip \
ac_cv_buggy_getaddrinfo=no \
ac_cv_file__dev_ptmx=yes \
ac_cv_file__dev_ptc=no
$ make
$ make install
The –host option specifies the host triplet, such as aarch64-linux-gnu
.
Python will expect a compiler in you $PATH
named
aarch64-linux-gnu-gcc
, but this can be overridden by passing
CC=/path/to/cc
on the command line. You can use CFLAGS
and LDFLAGS
to point Python to any dependencies it needs.
The ac_cv_*
arguments are to set information about the system that
configure isn’t able to determine when cross compiling. The first,
ac_cv_buggy_getaddrinfo=no
allows IPv6, and the other two are for the
benefit of os.openpty. You may not need any of this functionality, but you
still need to supply the parameters.
Make the cross environment¶
First install crossenv:
$ /path/to/build-python/bin/pip3 install crossenv
Build the cross-virtual environment:
$ /path/to/build-python/bin/python3 -m crossenv \
/path/to/host-python/bin/python3 \
cross_venv
Activate the cross-virtual environment:
$ . ./cross_venv/bin/activate
Build something:
(cross) $ pip install numpy
(cross) $ python setup.py install
Packages that you need at build time are best installed in build-python explicitly:
(cross) $ build-pip install cffi
(cross) $ pip install bcrypt
(cross) $ build-pip install wheel
(cross) $ python setup.py bdist_wheel
Use the resulting packages¶
It’s up to you to incorporate your cross-compiled module into your project. It
might be easiest to create a wheel, and then unzip it at the right location. If
you did a pip install you can find the installed libraries at
cross_venv/cross/lib/pythonVERSION/site-packages
.
Cross-Compiling Environment¶
This section documents the changes to the Python environment beyond just making
cross-python
behave like host-python
.
Environment variables¶
Crossenv sets PYTHON_CROSSENV
to a non-empty value.
The sys
module¶
-
sys.
cross_compiling
¶ Set to
True
incross-python
. May be used like so:if getattr(sys, 'cross_compiling', False): ...
-
sys.
build_path
¶ Analagous to
sys.path
, but applies when importing packages frombuild-python
. This path is searched just before the entries onsys.path
that point to the Python standard library. This means thatsys.build_path
is preferred when loading modules from the standard library, but prepending tosys.path
still works as expected.
The os
module¶
-
os.
uname
()¶ In addition to returning
host-python
’s information, it always reports thenode
as “build
”.
The platform
module¶
-
platform.
uname
()¶ In addition to returning
host-python
’s information, it always reports thenode
as “build
”.