ben.eficium is the blog of Ben Slack, citizen and principal consultant at Systems Xpert.
All posts are Copyright © Ben Slack on the date of publishing.


14 March 2016

FreeBSD cross-compiling with gcc and poudriere

I've been experimenting with Raspberry Pi using FreeBSD for a while now.

To build the ports I'm using qemu and poudriere on a dedicated build machine, as a) compiling on the rpi takes so long, and b) I have a few of them doing various tasks, so having a dedicated package server makes sense.

I've had to use the HEAD version of FreeBSD (currently 11.0-CURRENT) to get it working with the SD cards I bought to host the rpi OS.

My problem is getting a handful of ports to build with clang - the default C compiler for FreeBSD from version 10. The compile errors are happening with poudriere on the build machine and on the rpis themselves.

IMHO the move from gcc to clang was premature, but as a non-contributor to the FreeBSD project, I'm not complaining.

Overview
The offending ports that won't (or don't) build at time of writing are:

devel/libatomic_ops
sysutils/lsof

These are dependencies for a lot of other ports, so not being able to build them is a showstopper. No doubt there are plenty of other ports that won't build for the armv6 architecture with clang.

Via roundabout diagnosis, I discovered that all these ports do build with gcc but not with clang when compiling directly on one of my rpis. Hence the need to build using gcc.

So, the question then becomes - how do you build using poudriere and gcc? This is not trivial - as attested by the "Using gcc under poudriere" thread on FreeBSD Forums.

The solution suggested here by Mega56 is not ideal. Editing the poudriere port scripts with changes that shouldn't be in the master is never a good idea. For a start you'd have to make the edits after each upgrade, which would be a real pain.

The better solution I came up with was to install gcc in the poudriere model or root jail, a copy of which is made to a "reference" jail each time a poudriere "bulk" build is started. This is far easier than any of the other suggested solutions in the thread. Some of the suggestions, such as simply creating a new poudriere jail - won't work at all.

Then just add the USE_GCC?= yes variable into the Makefile for each port that requires gcc compiling. This has to be done every time you upgrade the ports, but if required you could easily write a script with sed to automate the task. For the two ports I am using this is not really worthwhile.

Getting the gcc compiler installed in the model jail is a complex process, but remember it only has to be completed once. It's set and forget.

The Details
This explanation assumes the following:
  • You've set up a qemu and poudriere cross-compile environment for the armv6 architecture. I used the instructions provided at Doug's Domain to get this set up. Big thanks to Doug Vetter - who is awesome.
  • You have read the man pages and basic doco for the qemu and poudriere ports.
  • My poudriere home is located at /home/poudriere, my jail is called "110armv6" (as I'm using FreeBSD 11.0-CURRENT) and I am using the "default" ports tree. Adjust the instructions for your own environment - am sure you can work out the detail.
Build the gcc packages
Rather than building the lang/gcc port inside the master jail we can use a standard poudriere build to make the packages. We can then install them in the master jail using FreeBSDs pkg manager. If there are other ports you'd find useful in the master jail, then add them also. I think Perl and Python are good candidates.

If you haven't already done so, you should probably get the latest ports tree for poudriere by issuing the following:

# poudriere ports -u

I use a port list file /usr/local/etc/poudriere.d/pkg.list for poudriere to compute bulk builds. I strongly recommend you do similar and ensure the line lang/gcc and any other ports you want in the master jail are listed in your equivalent file.

Issue the standard poudriere "bulk" command and gcc will build (under clang of course).

# cd /usr/local/etc/poudriere.d
# poudriere bulk -vv -j 110armv6 -f ./pkg.list

I always use the verbose debugging options -vv.

Hopefully your gcc port will build without issue. I have never experienced a problem with this step.

Set up the QEMU environment
The great thing about qemu is that once the environment is set up you can simply chroot to the poudriere master jail and all the emulation is handled under the hood.

However, you first have to mount a few directories and the device tree into the master file system in order to access a few dependencies. To do this I used the instructions "Using qemu-user to chroot and bootstrap other architectures on #FreeBSD" on The Ignorant Hack's blog. Big thanks to the Hack.

First, mount the device tree to the /dev folder in the jail's file system.

# mkdir /home/poudriere/jails/110armv6/dev
# mount -t devfs devfs /home/poudriere/jails/110armv6/dev/

Then we mount the packages folder so we can install from the poudriere built gcc packages we built earlier.

# mkdir /home/poudriere/jails/110armv6/packages
# mount -t nullfs /home/poudriere/data/packages/110armv6-default/All \
/home/poudriere/jails/110armv6/packages/


N.B. if you do a ports update and rebuild between these instructions, the symbolic link to the All packages directory will change, so you'll have to unmount and re-mount the packages directory in the master jail.

Install the package management tool in the master jail
We'll need to install the pkg package manager into the jail. Unfortunately, the jail does not have networking as is, so we'll have to get the package manually from the FreeBSD package site.

Get the latest package for the armv6 architecture. I've put it into the jail's /root directory, but it can go anywhere.

# cd /home/poudriere/jails/110armv6/root
# fetch http://pkg.FreeBSD.org/FreeBSD:11:armv6/latest/Latest/pkg.txz

Now we can chroot to the master jail.

# chroot /home/poudriere/jails/110armv6

And install the FreeBSD package manager. We need to turn off signature checking also.

# cd ~

# setenv SIGNATURE_TYPE "none"
# pkg add -f pkg.txz

Install gcc into the master jail - note the actual package name will be whatever current version you've built earlier.

# cd /packages
# pkg add -f gcc-4.8.5_2.txz

And exit the chrooted environment.

# exit

Edit the port Makefiles
You need to add the USE_GCC?= yes variable into the Makefile for each port you wish to compile with gcc. Do this for the poudriere ports tree located at /home/poudriere/ports/default.

N.B. you need to do this on a port by port basis. Putting the generic instructions into /usr/local/etc/poudriere.d/make.conf does not work. You get the error Mega56 complains of in the post mentioned above, i.e.

====>> Computing deps for devel/binutils
====>> DEBUG: devel/binutils depends on devel/binutils
====>> Error: devel/binutils incorrectly depends on itself ...

Which is how I managed to Google that post (and come up with this solution) in the first place.

Build the ports with gcc
Poudriere should now build the offending ports with gcc. I issue the command to build each separately. This just avoids building everything else that may need updating listed in my pkg.list file.

For example:

# poudriere bulk -vv -j 110armv6 devel/libatomic_ops

Once you've successfully built the ports that require gcc compilation, you will be able to build ports that depend on them (with the standard clang compiler) using a standard poudriere bulk build.

Have some coffee
Or a cold beer. You've earned it and your Raspberry Pi should now be able to install your pre-built packages from your poudriere package server.

No comments: