Cross-compiling (sometimes called cross-building) is the act of compiling code for an architecture other than that of the computer doing the compiling, e.g. building an i386 package on an amd64 laptop. For the wider topic of supporting multiple architectures, see CategoryMultiarch.
Background
Why cross-compile?
Cross-compiling lets you...
bootstrap new architectures like loong64 or riscv64
- build on beefy hardware for slow hardware
- bootstrap a customized debian
- build for platforms where the build would otherwise exhaust the 32-bit address space
- rebuild half the archive after a security incident was introduced in a package months ago
test the time64_t transition before it happens
reproduce arch-specific FTBFS errors on your own hardware
- develop, test and deploy everywhere with the same toolchain/OS
Nomenclature
This page uses system-centric nomenclature. That should be familiar to users of GNU autotools, but may be confusing if you're used to the object-centric nomenclature used by other tools.
Specific terms:
- build architecture
the architecture of the chroot/dpkg/compiler's executable; i.e. the architecture of the build system (called host by cmake, kernel, etc.)
- host architecture
the architecture of objects produced by the compiler; i.e. the architecture of the system that will host these objects (called target or sometimes build elsewhere)
- target architecture
if the produced object is a compiler, this is the architecture of objects it produces; i.e. the compilation target of the built program
For example, imagine you are using an amd64 laptop, cross-compiling packages for an i386 architecture, and the specific package you're cross-compiling is gcc-s390x-linux-gnu. That means amd64 is your build architecture, i386 is your host architecture, and s390x is your target architecture.
For more information, see dpkg-architecture terms and multiarch tuples.
Cross-build overview
To do a cross build, a number of things need to happen:
You need the right compilers that can run on the build architecture, but produce binaries for the host architecture. For C or C++ programs, this typically means installing the crossbuild-essential-<architecture> package, which depends on the needed gcc cross-compilers. These packages are installed from the regular Debian archives for the build architecture, no multiarch involved.
You need to install the host arch version of some build dependencies (such as libraries linked against). This uses Multiarch for installing packages directly from the host arch. See MultiarchCross for some more details about this.
The package build must be told about the host architecture, typically by running dpkg-buildpackage --host-arch=<architecture>. This sets DEB_HOST_ARCH for the rules file.
The rules file must handle the cross build properly. Typically this involves debhelper arranging to set variables like $CC and the actual build using these variables to find its compiler. A lot of this happens automatically, but sometimes special handling is needed (e.g. when the build compiles helper binaries that need to run during the build).
Steps 1-3 are typically handled by the build system (such as sbuild or pbuilder). Step 4 might need some changes in the package itself.
Doing a cross build
Building packages with a package build tool is always a good idea, but is especially important when cross-compiling:
- you don't end up with lots of build-deps and foreign architecture packages in your main system
- there is no risk of corrupting your system when something gets swapped to the wrong architecture
there are fewer conflicts due to packages lacking Multi-Arch: same
To cross-compile packages with sbuild, run `sbuild --host=<arch>`. This can run with the same sbuild chroot used to compile packages regularly.
To cross-compile packages with pbuilder, run pbuilder --host-arch=<ARCH>. This can run with the same pbuilder environment used to compile packages regularly.
Alternatively, you can create an environment with --architecture <arch>, then run pbuilder --architecture=<ARCH>, but that will use a full host-architecture environment (which might need QEMU to run binaries at all), so is not an actual cross build.
To cross-compile packages with git-buildpackage, run gbp buildpackage --git-pbuilder-options="--host-arch=<ARCH>". This can run with the same pbuilder environment used to compile packages regularly.
Again, you can also create an environment with ARCH=<arch>, then run gbp buildpackage --git-arch=<ARCH>, but that uses full host-architecture environment and not an actual cross build.
Build without a build environment
Building without a build environment may be useful in some edge cases, or to learn how the build process works. It is not recommended for general use.
First, add multiarch support for your system. You may be able to skip this step if you're building a package with no architecture-specific dependencies, like a kernel or a bootloader.
Then, download the source package, install dependencies and cross-compile it:
sudo apt-get install build-essential crossbuild-essential-<architecture>
sudo apt-get build-dep --host-architecture <architecture> <package>
cd <package>-<version>
CONFIG_SITE=/etc/dpkg-cross/cross-config.<architecture> \
DEB_BUILD_OPTIONS=nocheck \
dpkg-buildpackage --host-arch <architecture> -Pcross,nocheck
Build for Raspberry Pi OS
For purposes of cross-compiling, Raspberry Pi OS is a brand name shared by two different distributions - an arm64 distribution based on Debian, and an armhf distribution based on Raspbian. Cross-compiling packages for arm64 works like any other Debian architecture, but cross-compiling for armhf can cause strange run-time errors because Raspbian is subtly incompatible with Debian.
If you need to compile packages for an armhf Raspberry Pi, you will need to create a chroot by hand. That means constructing a chroot for your target distribution and architecture, enabling the ability to run foreign binaries transparently, and adding packages for building packages to the chroot. The exact steps depend on your package build tool - for pbuilder, see create a pbuilder Pi environment.
Preparing your package for cross-compilation
In a lot of cases (and when using debhelper), cross-compilation will work out of the box without further changes needed. See also CrossBuildPackagingGuidelines for more info.
Using variable for compiler names
Call your compiler using a variable like $(CC) or $(CXX), not a specific program name like gcc or g++. Either use standard variables names or document the variables. If possible, try your code in another compiler like clang.
Your project will usually be compiled on an amd64 PC, but can be cross-compiled for all of Debian's other architectures. Cross-compilation builds of gcc have filenames like aarch64-linux-gnu-gcc, so Debian needs to call the right command when cross-compiling.
Debian's build system sets the well-known CC and CXX environment variables when cross-compiling, but packagers can add non-standard variables in debian/rules if necessary.
Compiling your code in another compiler can be a good way to find subtle bugs. For example, gcc and clang often adapt each other's code tests, so fixing a clang-only warning today could avoid a gcc error tomorrow.
The following standard variables are available by default:
Replace... |
... with |
ar |
$(AR) |
as |
$(AS) |
cc |
$(CC) |
ctangle |
$(CTANGLE) |
cweave |
$(CWEAVE) |
g++ |
$(CXX) |
f77 |
$(F77) |
ld |
$(LD) |
lex |
$(LEX) |
lint |
$(LINT) |
m2c |
$(M2C) |
make |
$(MAKE) |
cc |
$(OBJC) |
pc |
$(PC) |
tangle |
$(TANGLE) |
tex |
$(TEX) |
texi2dvi |
$(TEXI2DVI) |
weave |
$(WEAVE) |
yacc |
$(YACC) |
Other binutils commands (like strip or readelf) need to be replaced with variables, but do not have standard names.
Cross-compilation-specific rules in debian/rules
Debian's build system handles most cross-compilation issues transparently, but complex debian/rules files may need to know:
- are we cross-compiling?
- can we run code for the host architecture?
- should we run tests?
The difference between the first two is subtle - for example, if we cross-compile from amd64 to i386, we can still can run i386 host code even though the build and host architectures are different. The difference with the third one is procedural - tests might be disabled for cross-compilation reasons, or the user might just ask to skip them. See also user-mode emulation.
To check whether we are currently cross-compiling, add something like this to debian/rules:
ifneq ($(DEB_BUILD_ARCH),$(DEB_HOST_ARCH))
# We are building on one architecture, for a host of a different architecture
endif
To check whether host code can be executed, check whether the DEB_BUILD_PROFILES environment variable contains cross:
ifneq (,$(filter cross,$(DEB_BUILD_PROFILES))
# "cross" profile detected - cannot execute host code
endif
To check whether to run tests, check whether the DEB_BUILD_OPTIONS environment variable contains nocheck:
ifneq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))
# "nocheck" option detected - do not run tests
endif
Convert architecture names
You can use dpkg-architecture (in dpkg-dev) to convert between Debian architectures (like amd64), GNU system types (like x86_64-linux-gnu), and various other dpkg-architecture terms:
# Prints amd64:
dpkg-architecture -tx86_64-linux-gnu -qDEB_HOST_ARCH
# Prints x86_64-linux-gnu:
dpkg-architecture -aamd64 -qDEB_TARGET_MULTIARCH
For more output options, see dpkg-architecture variables.
Simulate debhelper
Debhelper is the recommended system for building Debian packages. You might like to step through actions it takes, so you can learn how the build process works.
You can look at the code for how debhelper does cross-compiling.
The dpkg_architecture_value() function returns values that can be found in the output of the dpkg-architecture command.
See also
Qt cross-architecture development in Debian by Enrico Zini
