Standard Disclaimer: as with all posts in Nerd Food, this is a summary of my notes and experience on the subject. Its likely there will be incorrect bits of information so don't start building your personal nuclear power station using this article. Or if you do, don't blame me.
Compiling a compiler has become a necessity for all of us living in
the bleeding edge of C++-11. After all, who wants to wait for the next
stable release of INSERT_FAVOURITE_COMPILER
and then for
INSERT_FAVOURITE_DISTRIBUTOR
to start packaging them! We've already
covered how to build and install Clang from source and use it to
compile and run a trivial hello world; it seems only fitting to do the
same thing for GCC.
A couple of notes on the content that follows:
- the post focuses more on Linux but we also try to give some pointers for Windows (MinGW) and MacOSX. Testing was done on Windows XP and OSX Lion respectively.
- we're targeting version 4.7 but you should be able to apply these instructions to svn trunk builds too - assuming you have the correct versions of the dependencies. We may cover that specific scenario on a later post.
- we are - of course - assuming that you are using an older version of GCC to build GCC. You could try to use Clang or another compiler, but we didn't.
Prerequisites
Rather unfortunately, one of the requirements for building a compiler is a working compiler. In addition, there is always a bit of fiddling required. Lets go through each platform in turn.
Debian Testing
Debian is going through the multi-arch transition at the moment, which
complicates things. For instance, for reasons unknown we need to
install 32-bit glibc
development headers - even when building on 64-bit. The easiest way to do so is to install multi-platform support
for the system GCC:
sudo apt-get install g++-multilib
In addition, its probably best to set the LIBRARY_PATH
so we can
tell the linker about it:
export LIBRARY_PATH=/usr/lib/x86_64-linux-gnu
If you don't, you'll probably experience weird C runt-time errors:
/usr/bin/ld: cannot find crt1.o: No such file or directory /usr/bin/ld: cannot find crti.o: No such file or directory collect2: ld returned 1 exit status
MacOSX
Grab XCode - or even better, just the Command Line Tools bundle - and make sure its installed and working. If all has gone well, the following should work when you open up a terminal:
gcc --version
MinGW
We need to get a compiler on Windows before we can compile. We'll only cover the MinGW scenario. First get the main installer from MinGW and run it. During the setup choose the following options:
- Repository catalogues: download latest repository catalogues.
- Set destination location: Choose something like
C:\MinGWRoot
. The post-fix root is actually important because we will usec:\mingw
later. Basically, choose any name other than\mingw
on the chosen given drive. - Select Components: make sure "C Compiler", "C++ Compiler", "MSYS Basic System" and "MinGW Developer Toolkit" are ticked.
Click install and wait - it will take a while as it downloads all the packages. When finished, start the MinGW shell from the Start Menu and make sure the following works:
gcc --version
The next thing to do - a bit of a hack really - is to create an
"identity" "mount". This is another way of saying that the MSYS1
root is actually the same as the windows root. The "cleanest" way to
do this is to use junction
which you can easily grab from
SysInternals:
mingw-get install msys-wget wget http://live.sysinternals.com/junction.exe junction c:\\mingw C://MinGWRoot/msys/1.0
Now set the linker path pointing it to the identity mount:
export LIBRARY_PATH="/mingw/lib"
If you don't, you may experience the following error:
C:\MinGWRoot\mingw32\bin\ld.exe: cannot find dllcrt2.o: No such file or directory collect2.exe: error: ld returned 1 exit status make[3]: *** [libgcc_s.dll] Error 1 make[3]: Leaving directory `/home/marco/gcc-4.7.0_obj/i686-pc-mingw32/libgcc'
Building and Installing
Let's get started with the actual compilation. First we need to install the dependencies; you can peruse the prerequisites page for more details. I'm normally lazy and go with GMP, MPFR and MPC. This means we're not getting all the Graphite goodies which require PPL2.
The correct order of dependencies is as follows: GCC depends on MPC, which depends on MPFR, which depends on GMP; so the order of installation is:
- GMP
- MPFR
- MPC
- GCC
So we start by installing GMP (replace the --jobs 2
flag with the
number of cores available on your machine):
wget ftp://ftp.gmplib.org/pub/gmp-5.0.4/gmp-5.0.4.tar.bz2 tar xjf gmp-5.0.4.tar.bz2 cd gmp-5.0.4 ./configure --prefix=${HOME}/local make --jobs 2 make install cd ..
If you are on a recent version of Linux you can use the brand-spanking xaf
incantation of tar
, which unpacks any archive type; in the
interest of backwards compatibility we're sticking with the old
invocations here.
Now we can install MPFR:
wget http://www.mpfr.org/mpfr-current/mpfr-3.1.0.tar.bz2 tar xjf mpfr-3.1.0.tar.bz2 cd mpfr-3.1.0 ./configure --prefix=${HOME}/local --with-gmp=${HOME}/local make --jobs 2 make install cd ..
Note that we are using --with-gmp
instead of using the traditional
CFLAGS
and LDFLAGS
. This is a pattern followed through on GCC
configuration; I highly recommend you follow it as I'm sure a lot of
other things are happening on the background other than setting those
environment variables. In fact you may want to even make sure these
variables are not set to avoid any weird compilation problems.
MPC is installed next:
wget http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz tar xf mpc-0.9.tar.gz cd mpc-0.9 ./configure --prefix=${HOME}/local --with-gmp=${HOME}/local \ --with-mpfr=${HOME}/local make --jobs 2 make install cd ..
Finally, we can now install GCC. Up til now we've been lazy and done in-source builds. This is normally frowned upon, but doesn't have any major consequences - that is, with the exception of GCC. The documentation states explicitly that this is not a good idea:
First, we highly recommend that GCC be built into a separate directory from the sources which does not reside within the source tree. This is how we generally build GCC; building where srcdir == objdir should still work, but doesn't get extensive testing; building where objdir is a subdirectory of srcdir is unsupported.
We're not that brave so we'll follow the recommendation. We compile GCC as follows:
wget http://ftp.gnu.org/gnu/gcc/gcc-4.7.0/gcc-4.7.0.tar.bz2 tar xjf gcc-4.7.0.tar.bz2 mkdir gcc-4.7.0_obj cd gcc-4.7.0_obj ../gcc-4.7.0/configure --prefix=${HOME}/local \ --enable-ld=yes --disable-nls --with-gmp=${HOME}/local \ --with-mpfr=${HOME}/local --with-mpc=${HOME}/local \ --program-suffix=-4.7 --enable-checking=release --enable-languages=c,c++ make --jobs 2 make install
Lets see in detail the more important GCC configuration options:
- disable-nls: unless you are into internationalisation, you don't really need NLS. This shaves off a bit of build time.
- enable-checking: lets make a few checks - no one wants a compiler that generates broken code.
- enable-languages: we are only interested in C/C++, so no point in building ADA, Java, etc.
- enable-ld: we're being old-school here and using traditional
ld
instead ofgold
, the new linker written in C++.
On the whole, these basic instructions are sufficient to build GCC on Linux, MacOSX and Windows (MinGW). However, when you leave Linux there is always a bit of platform-specific fiddling required. And to be fair, Debian testing also had a couple of wrinkles.
Compiling Hello World
In order to prove that we have a valid setup, lets create a trivial hello world, with a hint of C++-11:
#include <iostream> int main(void) { auto hello = std::string("hello world"); std::cout << hello << std::endl; }
First lets make sure we have our prefix directory on the path:
export PATH=$PATH:${HOME}/local
Now we can then compile with our shinny new GCC:
g++-4.7 -c hello.cpp -std=c++11 g++-4.7 -o hello hello.o -std=c++11
Running Hello World
Running hello world is a bit trickier. We now need to tell the program loader about it and this varies from operative system to operative system.
Debian
Due to the multi-arch changes, we need to point the program loader directly to the arch specific directory:
export LD_LIBRARY_PATH=${HOME}/local/lib64
To verify that your binaries are setup correctly, use ldd
:
$ ldd hello linux-vdso.so.1 => (0x00007fffda7ff000) libstdc++.so.6 => /home/marco/local/lib64/libstdc++.so.6 (0x00007f6931fa2000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6931cf8000) libgcc_s.so.1 => /home/marco/local/lib64/libgcc_s.so.1 (0x00007f6931ae2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f693175b000) /lib64/ld-linux-x86-64.so.2 (0x00007f69322d1000)
Note that both the C++ run-time and the GCC's shared object were picked up from the correct location.
MacOSX
Apple - thinking differently as usual - doesn't use binutils
. This
means ld
, gold
, as
etc are all a bit different. Darwin doesn't
use ELF like every other sensible UNIX but it uses MACH-O instead - a
bad nerd pun, if I ever seen one. In general this shouldn't really
affect you - except you can't use familiar tools such as ldd
etc.
Lets start by getting the program loader to point to our lib
directory:
export DYLD_LIBRARY_PATH=${HOME}/local/lib
To verify that your binaries are setup correctly, use otool
:
otool -L hello hello: /Users/marco/local/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.17.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0) /Users/marco/local/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
Here you can see that the C++ run-time and the GCC dynamic library where picked up from the correct location.
MinGW
On Windows the program loader path is not important, it's the main
PATH
variable you have to worry about3. Thus the DLLs must be
on the PATH
rather than on the usual variables for the program
loader path. To do so:
$ export PATH=${HOME}/local/bin:${PATH}
Notice we placed our directory first in the path - this is to avoid picking up the wrong DLLs from the stock compiler. Running produces the desired output:
$ ./hello.exe hello world
To double-check we are indeed using the correct DLLs we need to
install Dependency Walker. I installed mine to C:\packages
- hey,
why not. You can then run:
$ c:/packages/depends.exe
And you should see something akin to this:
Dependency walker with hello world
Uninstalling
Word of caution: notably, GCC isn't built with uninstall in mind. It
doesn't support the unistall
target some tarballs provide and many
dicussions strongly advise against its complete manual uninstall. This
is why we chose a careful location to install it, so we can simple do:
rm -rf ${HOME}/local
Conclusions
As you can see its really easy to build GCC from source, install it and start using it to compile C++-11 programs. In future we will cover how to compile auxiliary libraries such as boost with C++-11 support so you can use the full power of the language.
Footnotes:
1 : I won't go in to too much detail on the finer points between MinGW and MSYS and so on; if you are interested, check the post MinGW, Cygwin and Wine.
2 I had one or two problems when compiling PPL in the past so I gave up on it, and haven't noticed significant optimisation problems yet. Of course, if this was a production compiler I certainly would compile it with PPL.
3 Or better yet, the windows loader uses only one path variable for both the executables and the shared libraries.
No comments:
Post a Comment