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-ppcWe 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.
October 17th, 2009 at 2:30 am
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?
October 17th, 2009 at 1:45 pm
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″.
October 17th, 2009 at 6:11 pm
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.
March 6th, 2010 at 6:35 pm
This is great! Thanks for your post. I am just starting django and this got me straight.
June 1st, 2010 at 1:22 pm
Do you know how to cross-compile pyserial package?
June 4th, 2010 at 9:47 am
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_eggThe egg will then be in the dist directory (might be in a sub directory of dist).
August 3rd, 2011 at 5:06 am
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.
December 26th, 2011 at 10:08 am
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.
December 26th, 2011 at 4:07 pm
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?
December 26th, 2011 at 5:06 pm
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).
December 26th, 2011 at 5:07 pm
I’ll be doing a talk on this stuff at PyCon 2012: https://us.pycon.org/2012/schedule/presentation/11/
December 27th, 2011 at 7:14 pm
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.
December 28th, 2011 at 3:14 am
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
December 28th, 2011 at 4:10 pm
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/”.
December 29th, 2011 at 3:49 am
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.
December 29th, 2011 at 9:19 am
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!
January 3rd, 2012 at 7:08 am
Chris: are you going to provide your slides for PyCon before the event itself?
May 8th, 2012 at 9:07 am
Chris,
do you have an idea, how to solve this issue: https://groups.google.com/d/topic/comp.lang.python/0rDWl2d8PDY/discussion
Thanks