nixpkgs/maintainers/docs/cross.txt

274 lines
8 KiB
Text
Raw Normal View History

Setting up a cross compiler with Nix
"Cross compilation" means compiling a program on one machine for another
type of machine. A typical use of cross compilation is to compile programs
for embedded devices. These devices often don't have the computing power
and memory to compile programs natively.
For a fully working cross compiler the following are needed:
* cross binutils: assembler, archiver, linker, etcetera that understand
the format of the target system
* cross compiler: a compiler that can generate binary code and object files
for the target platform
* cross C library: a library to link object files with to create fully
functional programs
Cross compilers are difficult to set up. A lot of people report that they
cannot succeed in building a cross toolchain successfully. The answers
usually consist of "download this pre-built toolchain", which is equally
unhelpful.
A toolchain is set up in five steps:
1. build binutils to that can run on the host platform, but generate code
for the target platform
2. build Linux kernel headers for the target platform
3. build a minimal C only version of GCC, that can run on the host platform
and generate code for the target platform
4. build a C library for the target platform. This includes the dynamic
linker, C library, etc.
5. build a full GCC
****
NB:
Keep in mind that many programs are not very well suited for cross
compilation. Either they are not intended to run on other platforms,
because the code is highly platform specific, or the configuration proces
is not written with cross compilation in mind.
Nix will not solve these problems for you!
***
This document describes to set up a cross compiler to generate code for
arm-linux with uClibc and runs on i686-linux. The "stdenv" used is the
default from the standard Nix packages collection.
Step 1: build binutils for arm-linux in the stdenv for i686-linux
---
{stdenv, fetchurl, noSysDirs}:
stdenv.mkDerivation {
name = "binutils-2.16.1-arm";
builder = ./builder.sh;
src = fetchurl {
url = http://ftp.nluug.nl/gnu/binutils/binutils-2.16.1.tar.bz2;
md5 = "6a9d529efb285071dad10e1f3d2b2967";
};
inherit noSysDirs;
configureFlags = "--target=arm-linux";
}
---
This will compile binutils that will run on i686-linux, but knows the
format used by arm-linux.
Step 2: build kernel headers for the target architecture
default.nix for kernel-headers-arm:
---
{stdenv, fetchurl}:
assert stdenv.system == "i686-linux";
stdenv.mkDerivation {
name = "linux-headers-2.6.13.4-arm";
builder = ./builder.sh;
src = fetchurl {
url = http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.13.4.tar.bz2;
md5 = "94768d7eef90a9d8174639b2a7d3f58d";
};
}
---
builder.sh for kernel-headers-arm:
---
source $stdenv/setup
buildPhase() {
make include/linux/version.h
}
buildPhase=buildPhase
installPhase() {
mkdir $out
mkdir $out/include
#cd $out/include
#ln -s asm-arm asm
make include/asm ARCH=arm
cp -prvd include/linux include/asm include/asm-arm include/asm-generic $out/include
echo -n > $out/include/linux/autoconf.h
}
installPhase=installPhase
genericBuild
---
Step 3: build a minimal GCC
Extra/different parameters include the target platform and the kernel
headers argument (this needs a major cleanup, as well as the name, it
needs to be different!). Profiled compilers are disabled. The tarball
used here is just gcc-core. For some reason it doesn't install nicely
if the whole tarball is used (or is this some braino on my side? -- AH).
Only C is used, because for other languages (such as C++) extra libraries
need to be compiled, for which libraries compiled for the target system
are needed.
There is a bit of evilness going on. The cross compiled utilities need
to be either copied to or be linked from the output tree of the compiler.
(Is this really true? Back this up with arguments! -- AH)
Symbolic links are not something we want inside the Nix store.
---
{ stdenv, fetchurl, noSysDirs
, langC ? true, langCC ? true, langF77 ? false
, profiledCompiler ? false
, binutilsArm
, kernelHeadersArm
}:
assert langC;
stdenv.mkDerivation {
name = "gcc-4.0.2-arm";
builder = ./builder.sh;
src = fetchurl {
url = ftp://ftp.nluug.nl/pub/gnu/gcc/gcc-4.0.2/gcc-core-4.0.2.tar.bz2;
md5 = "f7781398ada62ba255486673e6274b26";
#url = ftp://ftp.nluug.nl/pub/gnu/gcc/gcc-4.0.2/gcc-4.0.2.tar.bz2;
#md5 = "a659b8388cac9db2b13e056e574ceeb0";
};
# !!! apply only if noSysDirs is set
patches = [./no-sys-dirs.patch ./gcc-inhibit.patch];
inherit noSysDirs langC langCC langF77 profiledCompiler;
buildInputs = [binutilsArm];
inherit kernelHeadersArm binutilsArm;
platform = "arm-linux";
}
---
---
source $stdenv/setup
export NIX_FIXINC_DUMMY=$NIX_BUILD_TOP/dummy
mkdir $NIX_FIXINC_DUMMY
if test "$noSysDirs" = "1"; then
if test "$noSysDirs" = "1"; then
# Figure out what extra flags to pass to the gcc compilers
# being generated to make sure that they use our glibc.
if test -e $NIX_GCC/nix-support/orig-glibc; then
glibc=$(cat $NIX_GCC/nix-support/orig-glibc)
# Ugh. Copied from gcc-wrapper/builder.sh. We can't just
# source in $NIX_GCC/nix-support/add-flags, since that
# would cause *this* GCC to be linked against the
# *previous* GCC. Need some more modularity there.
extraCFlags="-B$glibc/lib -isystem $glibc/include"
extraLDFlags="-B$glibc/lib -L$glibc/lib -Wl,-s \
-Wl,-dynamic-linker,$glibc/lib/ld-linux.so.2"
# Oh, what a hack. I should be shot for this.
# In stage 1, we should link against the previous GCC, but
# not afterwards. Otherwise we retain a dependency.
# However, ld-wrapper, which adds the linker flags for the
# previous GCC, is also used in stage 2/3. We can prevent
# it from adding them by NIX_GLIBC_FLAGS_SET, but then
# gcc-wrapper will also not add them, thereby causing
# stage 1 to fail. So we use a trick to only set the
# flags in gcc-wrapper.
hook=$(pwd)/ld-wrapper-hook
echo "NIX_GLIBC_FLAGS_SET=1" > $hook
export NIX_LD_WRAPPER_START_HOOK=$hook
fi
export NIX_EXTRA_CFLAGS=$extraCFlags
export NIX_EXTRA_LDFLAGS=$extraLDFlags
export CFLAGS=$extraCFlags
export CXXFLAGS=$extraCFlags
export LDFLAGS=$extraLDFlags
fi
else
patches=""
fi
preConfigure=preConfigure
preConfigure() {
# Determine the frontends to build.
langs="c"
if test -n "$langCC"; then
langs="$langs,c++"
fi
if test -n "$langF77"; then
langs="$langs,f77"
fi
# Cross compiler evilness
ensureDir $out
ensureDir $out/arm-linux
ensureDir $out/arm-linux/bin
ln -s $binutilsArm/arm-linux/bin/as $out/arm-linux/bin/as
ln -s $binutilsArm/arm-linux/bin/ld $out/arm-linux/bin/ld
ln -s $binutilsArm/arm-linux/bin/ar $out/arm-linux/bin/ar
ln -s $binutilsArm/arm-linux/bin/ranlib $out/arm-linux/bin/ranlib
# Perform the build in a different directory.
mkdir ../build
cd ../build
configureScript=../$sourceRoot/configure
configureFlags="--enable-languages=$langs --target=$platform --disable-threads --disable-libmudflap --disable-shared --with-headers=$kernelHeadersArm/include --disable-multilib"
}
postInstall=postInstall
postInstall() {
# Remove precompiled headers for now. They are very big and
# probably not very useful yet.
find $out/include -name "*.gch" -exec rm -rf {} \; -prune
# Remove `fixincl' to prevent a retained dependency on the
# previous gcc.
rm -rf $out/libexec/gcc/*/*/install-tools
}
#if test -z "$profiledCompiler"; then
#makeFlags="bootstrap"
#else
#makeFlags="profiledbootstrap"
#fi
genericBuild
---
Step 4: build a C library for the target platform.
The previous steps are enough to compile a C library. In our case we take
uClibc. It's intended to be a small sized replacement for glibc. It is widely
used in embedded environments.