Why building RPMs for a different architecture is a hard problem

June 6, 2013

I was recently grumbling about how difficult it is to build 32-bit x86 RPMs on a 64-bit x86 Fedora system. The short version is that despite the presence of various things to help cross-building, like setarch and rpmbuild --target=..., the only way I've found to cross-build binary RPMs is to use mock to create an entire minimal 32-bit Fedora install in a buildroot and then use that minimal install to recompile your RPM. At one level this sounds absurd: I'm shuffling around hundreds of megabytes of packages and disk space to create the 32-bit Fedora buildroot when my 64-bit install is already fully capable (at least in theory) of building 32-bit programs. But once I started thinking about it, I realized that cross-building RPMs is actually a really hard problem that is basically impossible to get right. The problem is that cross-compiling things in general is very fragile.

My system doesn't have a duplicate set of 32-bit programs; all of its normal programs are 64-bit ones. To cross-compile and cross-build properly, every program that might write architecture-dependent data needs to detect that it is running in a cross-building environment and then write 32-bit data instead of native 64-bit data. This is not just the C and C++ compilers, it may also include things like Python writing bytecode files and the like. In fact, in some situations it's impossible to make this work without a 32-bit version of a system program (or huge local hacks to the program). As a result it's very easy for something to quietly go wrong with a cross-build process such that a 64-bit something sneaks into what is theoretically a 32-bit RPM. Various sorts of explosions are going to ensue when this binary RPM is actually installed.

(Nor can RPM reliably detect this, since it doesn't necessarily know what's 64-bit dependent in a package's files. At best the RPM build process could catch obvious cases it knows about.)

One way to put this is that cross-building requires all of the 64-bit programs involved in building the package to faithfully emulate their 32-bit selves. This is both non-trivial and not something that many projects are even interested in. Setting up an entire environment with the 32-bit versions of everything actually is the simpler answer in the general case. The 32-bit versions are basically guaranteed to do the right thing since they're not emulating anything, they're just being their natural selves.

(This is in part the kind of thing I write down to get it all straight in my head. I'm sure it's nothing new to people who worry about cross-compiling and cross-building packages, and this particular case gets off easy since you can at least still run 32-bit x86 programs on a 64-bit x86 system, which is a lot more than you get in many other cross-compilation situations.)

Sidebar: the hard and the impossible situations

The hard situation is when the package being built interrogates a system program for some core configuration information or compilation option, like 'how big is a <X> data object'. To be a faithful 32-bit emulation the program has to lie, to say that ints are 32 bits when in fact they're really 64 bits. Many programs aren't going to do this (whether the data is generated on the fly at runtime or saved at build time and then just produced for anyone who asks).

The impossible situation is when the package being built compiles an extension for something that supports them (such as Perl or Python) and then tries to load the newly compiled extension into the system program during the build process. To cross build properly the compiled extension must be a 32-bit object, not a 64-bit one, but now you're trying to load a 32-bit extension into a 64-bit program.

(One of many ways that this can happen is a commendable desire on the package's part to make sure that its newly compiled extension module actually works. You test the extension module by, well, loading it and seeing if it works properly.)

Both of these work right if the 'system' program actually is the 32-bit version instead of the 64-bit version.

Written on 06 June 2013.
« The case against blog sidebars
My current understanding of 'software defined networking' »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Jun 6 01:04:01 2013
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.