2013-06-06
Why building RPMs for a different architecture is a hard problem
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.