Cross Compiling Python Extensions

If you are working with Python in an embedded environment, you will enventually want to build a Python extension with a cross tool chain. These instructions work both with Open Embedded and the DENX ELDK. If you need to go cross compile Python 2.5, 2.6 or 3.1. ELDK and Open Embedded include a version of Python pre-done for you. You will need a version of Python for the build system that matches your target enviroment.

On the host machine install setuptools and distutilscross. If you are using Python 2.6.3 you probably need to also install distribute which is a replacement for setuptools that also happens to fix a conflict between setuptools 0.6c9 and Python 2.6.3 (There is an update of setuptools coming soon too. See the update below). If you want to use virtualenv and distribute together, you might need to use virtualenv-distribute, a fork of virtualenv that uses distribute instead of setuptools.

Distutilscross is a package that uses setuptools to monkey patch an extra parameter, –cross-compile, into the distutils build command. The net result is that you can set your cross compile environment variables, plus PYTHONXCPREFIX and do python setup.py build -x to get your extensions to compile.

Lets take ELDK as an example.

$ export PYTHONXCPREFIX=/opt/eldk/ppc_4xxFP/usr
$ export CROSS_COMPILE=ppc_4xxFP-
$ export CC="${CROSS_COMPILE}gcc -pthread"
$ export LDSHARED="${CC} -shared"
$ export LDFLAGS="-L/opt/eldk/ppc_4xxFP/usr/lib -L/opt/eldk/ppc_4xxFP/lib"
$ python setup.py build -x bdist_egg --plat-name=linux-ppc

We always need to define PYTHONXCPREFIX and LDFLAGS. PYTHONXCPREFIX tells the -x (or –cross-compile) where your target built Python is. Mostly it is looking for the options used at build time from $PREFIX/lib/python2.x/config/Makefile. This will also set the compile up to use the target Python’s headers. We need to set LDFLAGS as above because Distutils always generates the link directory options as pointing to the Host Python’s install location.

We also need to set CC and LDSHARED because the compiler guessed out of the target Python Makefile is wrong for ELDK. If you are compiling C++ code you would also need to set the CXX environment variable.

Once you have the egg built, you can copy it over to a package directory (read an auto-indexed directory on a web server) and install it from your target using:

$ easy_install -f http://mylocalpackageplace/nest package_name

Concrete example time, what do you need in order to build the deps for and install TurboGears 2. You need the following packages:
zope.interface, Genshi, and simplejson. ELDK’s installation of Python 2.5 does not seem to come with sqlite so you will either need to do a new cross compile of python or get a separate install of the sqlite library. Once you have your binary eggs built using the above command copy them to your local http server, I’ll use 192.168.1.99. You need to follow the Manual Installation method for TurboGears but when you get to Install TurboGears 2 add “-f http://192.168.1.99/nest/” to the command line so that you get:

(tg2env)$ easy_install -i http://www.turbogears.org/2.0/downloads/current/index -f http://192.168.1.99/nest/ tg.devtools

This is sufficient for most normal extension building situations. Unfortunately there are some extensions that use autotools and python-config instead of distutils (dbus-python and omniORBpy come to mind). Autotools should let you override the critical settings but I would like to see the situation improve.

Let me know if you have ideas on how to make Python development in an embedded environment better.

Update: PJE, the author of setuptools, has given notice of the impending release of setuptools 6c10 which fixes many bugs, including the Python 2.6.3 compile bug. See his comment below.

18 Responses to “Cross Compiling Python Extensions”

  1. Michael Langford Says:

    Wow, this is a lot easier than it was a few years ago.

    What is your target platform (like the actual application, not the environment)
    ? What other ones are you going to target with this?

  2. PJ Eby Says:

    FYI, setuptools 0.6c10 includes a fix for the Python 2.6.3 problem (along with numerous other bug fixes), and will be released Monday. You can also get it now via SVN, using “easy_install setuptools==dev06″.

  3. Christopher Lambacher Says:

    Micheal: As you might be able to guess from my examples, I am working on a TurboGears 2 app on a linux-ppc target.

    Since I am an independent contractor, anything else I do would be based on customer needs.

  4. Dana Ard Says:

    This is great! Thanks for your post. I am just starting django and this got me straight.

  5. frank Says:

    Do you know how to cross-compile pyserial package?

  6. Christopher Lambacher Says:

    There is no need to cross build pyserial, it is a python only package (i.e. it does not contain any c/c++ code). If you want an egg you can use:

    python setup.py bdist_egg

    The egg will then be in the dist directory (might be in a sub directory of dist).

  7. fran Says:

    How I could cross-compile PyBluez module for ARM platform? I’ve been searching information on Internet but I haven’t found anything.

    Thanks in advance.

  8. James Le Cuirot Says:

    Does this still work for you? I’ve tried it on Gentoo (Python 2.6 and 2.7) and Ubuntu (Python 2.6) and it doesn’t load automatically as the -x option is missing. I’ve filled an issue at https://bitbucket.org/lambacck/distutilscross/issue/1 but it’s been a long time since there were any commits.

  9. James Le Cuirot Says:

    Ah it works if the package imports setuptools instead of distutils. I was trying it with rdiff-backup, which imports distutils. I gather entry points are specific to setuptools. Is there any way to make it magically work in this situation?

  10. Christopher Lambacher Says:

    Hi James, setuptools is required for distutils. You should be able to get non-setuptools packages to work using:

    python -c “import setuptools; execfile(‘setup.py’)” -x build

    You can substitute any other command for build as you require (including bdist_egg).

  11. Christopher Lambacher Says:

    I’ll be doing a talk on this stuff at PyCon 2012: https://us.pycon.org/2012/schedule/presentation/11/

  12. James Le Cuirot Says:

    Hi Chris. Sorry again for the noise but I’ve just made an important discovery. Whilst patching this feature into Python directly, I wondered where the default values for PREFIX and EXEC_PREFIX come from. It turns out you can already override these by setting the PYTHONHOME environment variable. The catch is that all the Python stuff you need to complete the build (e.g. setuptools) needs to be installed in the prefix as the only files it touches from the host are the native Python executable and library. If the build needs to execute any native Python extensions then this probably won’t work but I doubt any Python build system does this. In the Gentoo case, it will automatically install prerequisites like setuptools for you. I’ll play with this more tomorrow.

  13. Yegor Yefremov Says:

    Hi Chris,

    I’ve a problem cross-compiling netifaces from Alastair Houghton for ARM using Buildroot. I’ve installed distutilscross and specified all needed environment variables, but setuptools 0.6c11 doesn’t seem to be interested in specified CC. The -x option will be recognized, but all netifaces tests fail:

    running build
    Setting prefix
    Setting prefix
    running build_ext
    checking for getifaddrs… not found. (cached)
    checking for getnameinfo… not found. (cached)
    checking for socket IOCTLs… not found. (cached)

    That is how I configure netifaces for cross-compiling:

    define PYTHON_NETIFACES_BUILD_CMDS
    (cd $(@D);\
    CC=”$(TARGET_CC) -pthread” \
    LDSHARED=”$(TARGET_CC) -pthread -shared” \
    PYTHONXCPREFIX=”$(HOST_DIR)/usr/” \
    LDFLAGS=”$(HOST_DIR)/lib/ $(HOST_DIR)/usr/lib/” \
    $(HOST_DIR)/usr/bin/python setup.py build -x)
    endef

    Do you have any idea?

    Thank you in advance and merry Xmas and Happy New Year!

    P.S. such statement as –plat-name=linux-arm will be not recognized

  14. Christopher Lambacher Says:

    Yegor: It looks like you are missing -L options in your LDFLAGS. It should be LDFLAGS=”-L$(HOST_DIR)/lib/ -L$(HOST_DIR)/usr/lib/”.

  15. Yegor Yefremov Says:

    No. That didn’t help.

    define PYTHON_NETIFACES_BUILD_CMDS
    (cd $(@D); \
    CC=”$(TARGET_CC)” \
    CFLAGS=”$(TARGET_CFLAGS)” \
    PYTHONXCPREFIX=”$(HOST_DIR)/usr/” \
    LDSHARED=”$(TARGET_CC) -shared” \
    LDFLAGS=”-L$(HOST_DIR)/lib -L$(HOST_DIR)/usr/lib” \
    $(HOST_DIR)/usr/bin/python setup.py build -x)
    endef

    Build result:

    python-netifaces 0.6 Building
    (cd /home/user/Documents/projects/br-queue-python/output/build/python-netifaces-0.6; CC=”/home/user/Documents/projects/br-queue-python/output/host/usr/bin/arm-none-linux-gnueabi-gcc” CFLAGS=”-pipe -Os -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64″ PYTHONXCPREFIX=”/home/user/Documents/projects/br-queue-python/output/host/usr/” LDSHARED=”/home/user/Documents/projects/br-queue-python/output/host/usr/bin/arm-none-linux-gnueabi-gcc -shared” LDFLAGS=”-L/home/user/Documents/projects/br-queue-python/output/host/lib -L/home/user/Documents/projects/br-queue-python/output/host/usr/lib” /home/user/Documents/projects/br-queue-python/output/host/usr/bin/python setup.py build -x)
    running build
    Setting prefix
    Setting prefix
    running build_ext
    checking for getifaddrs… not found. (cached)
    checking for getnameinfo… not found. (cached)
    checking for socket IOCTLs… not found. (cached)
    checking for optional header files… netash/ash.h netatalk/at.h netax25/ax25.h neteconet/ec.h netipx/ipx.h netpacket/packet.h linux/irda.h linux/atm.h linux/llc.h linux/tipc.h linux/dn.h. (cached)
    checking whether struct sockaddr has a length field… no. (cached)
    checking which sockaddr_xxx structs are defined… at ax25 in in6 ipx un ash ec ll atmpvc atmsvc dn irda llc. (cached)
    building ‘netifaces’ extension
    /home/user/Documents/projects/br-queue-python/output/host/usr/bin/arm-none-linux-gnueabi-gcc -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -pipe -Os -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -fPIC -DHAVE_NETASH_ASH_H=1 -DHAVE_NETATALK_AT_H=1 -DHAVE_NETAX25_AX25_H=1 -DHAVE_NETECONET_EC_H=1 -DHAVE_NETIPX_IPX_H=1 -DHAVE_NETPACKET_PACKET_H=1 -DHAVE_LINUX_IRDA_H=1 -DHAVE_LINUX_ATM_H=1 -DHAVE_LINUX_LLC_H=1 -DHAVE_LINUX_TIPC_H=1 -DHAVE_LINUX_DN_H=1 -DHAVE_SOCKADDR_AT=1 -DHAVE_SOCKADDR_AX25=1 -DHAVE_SOCKADDR_IN=1 -DHAVE_SOCKADDR_IN6=1 -DHAVE_SOCKADDR_IPX=1 -DHAVE_SOCKADDR_UN=1 -DHAVE_SOCKADDR_ASH=1 -DHAVE_SOCKADDR_EC=1 -DHAVE_SOCKADDR_LL=1 -DHAVE_SOCKADDR_ATMPVC=1 -DHAVE_SOCKADDR_ATMSVC=1 -DHAVE_SOCKADDR_DN=1 -DHAVE_SOCKADDR_IRDA=1 -DHAVE_SOCKADDR_LLC=1 -I/home/user/Documents/projects/br-queue-python/output/host/usr/include/python2.7 -c netifaces.c -o build/temp.linux-i686-2.7/netifaces.o
    netifaces.c:143:6: error: #error You need to add code for your platform.
    netifaces.c:268:1: warning: ‘string_from_sockaddr’ defined but not used
    netifaces.c:360:1: warning: ‘add_to_family’ defined but not used
    error: command ‘/home/user/Documents/projects/br-queue-python/output/host/usr/bin/arm-none-linux-gnueabi-gcc’ failed with exit status 1

    I have such a premonition, that CC won’t be honored. For the test I just renamed the gcc binary and build_ext was not complaining during the test. The only thing that failed was netifaces.c compilation, because of absent compiler binary.

    The test_build method in netifaces setup.py has tree return possibilities:
    - no error
    - CompileError
    - DistutilsExecError

    But since the return value is boolean I don’t know which part failed. Print is not visible in this method. Will have to dig deeper.

  16. Yegor Yefremov Says:

    I’ve got it!!!

    The first problem with checks was, that setup.py while checking for headers has also tried to execute the binary. That was not so good to execute arm binary on x86 ;-)

    The second is trickier as it concerns library paths. During linking the only -L option I see is -L/usr/lib despite specifying LDFLAGS. This output of the linker_so at the biginning of setup.py:

    Linker_so: ['/home/user/Documents/projects/br-queue-python/output/host/usr/bin/arm-none-linux-gnueabi-gcc', '-shared', '-L/home/user/Documents/projects/br-queue-python/output/host/usr/arm-unknown-linux-gnueabi/sysroot/lib', '-L/home/user/Documents/projects/br-queue-python/output/host/usr/arm-unknown-linux-gnueabi/sysroot/usr/lib']

    at the end I solved this by providing library_dirs myself. Any idea?

    I wish you good luck for your PyCon talk. And I hope Python devs will understand the difference between compilation and cross-compilation. And some common framework linke autotools should be also available, so there is no need to write own checks.

    Thanks for helping. Happy New Year!

  17. Yegor Yefremov Says:

    Chris: are you going to provide your slides for PyCon before the event itself?

  18. Yegor Yefremov Says:

    Chris,

    do you have an idea, how to solve this issue: https://groups.google.com/d/topic/comp.lang.python/0rDWl2d8PDY/discussion

    Thanks

Leave a Reply