I have a confession to make: I’m a bit of a C++ compiler hoarder. As I write this, the CentOS-7 virtual machine on my workstation at home has eleven versions of GCC and ten versions of Clang installed. I like to have multiple versions of GCC and Clang available on my various Linux-based home and work systems so that I can easily compare old and new behavior, check for performance improvements and bug fixes, explore new language features, and retain the stability of previous compiler releases that have proven themselves.

However, there’s often a practical problem when trying to do this. The standard compiler installations blessed and distributed by the major Linux distributions typically install into the system directories /usr/bin and /usr/lib. That’s fine if you only need one version of GCC and/or Clang, but it doesn’t work so well when you want multiple versions. Upgrade packages usually replace the existing compiler, which is not the behavior we want. Packages that attempt to install additional new versions can sometimes overwrite the files of an existing installation, resulting in a misconfiguration of one or more versions.

There are also cases where it’s impermissible or undesirable to add or change files in the system directories. Company policy may prohibit you from installing software there (although this seems less and less likely in modern software development shops). Or perhaps you need to develop on a system that is configured as closely as possible to production systems, and so system directory changes are discouraged.

In any event, it would be great if there was a way to install an arbitrary number of C++ compilers on your system independently of each other, and with no changes to the standard system directories. It’s possible, but does require a little work. In short: you must build it yourself! As it turns out, GCC and Clang are remarkably flexible in how they can be configured and built, and we’re going to use that flexibility to our advantage.

In this post I’m going to describe a set of scripts I wrote for building GCC such that each installed version is completely independent of every other installed version, allowing you add or remove them at will without affecting the system directories.

I call this collection gcc-builder and it is available on GitLab. The process for using it is pretty straightforward, and usually goes something like this:

  1. Install prerequisites
  2. Clone the gcc-builder repo
  3. Customize some build variables
  4. Perform the build
  5. Stage the build
  6. Create an installation package (.tgz or .rpm) and install it
  7. Use the newly installed compiler
  8. Remove temporary build files

The central idea behind the scripts (besides automating lots of boring work) is to install each compiler in its own unique directory hierarchy with only its own files; no files from any other compiler are present. The GCC pre-build configuration process allows you to choose the intended installation directory, using the --prefix option. The scripts create a unique directory name based on the compiler’s version number and set --prefix to that name.

I’ve chosen /usr/local/gcc as the top-level directory into which individual GCC versions are installed. Each version is installed into its own subdirectory having the version number as its name. For example: GCC 6.4 would be installed into /usr/local/gcc/6.4.0; GCC 7.1 would be installed in /usr/local/gcc/7.1.0; and so on.

In the following sections, I’ll walk through the process in a bit more detail, using GCC 7.2 as an example. When the process is finished, the compiler will be installed into /usr/local/gcc/7.2.0.

Note that I’ll also use the environment variable BUILD_TOP_DIR to represent the work directory into which the gcc-builder repository will be cloned. After cloning, all of the scripts will thus be found in ${BUILD_TOP_DIR}/gcc-builder (assuming you use the default destination directory name when cloning).

Step 1: Install Prerequisites

You should have the usual GNU build and system tools installed. If you’re doing day-to-day C++ development on Linux, then it’s likely you already have all the tools listed below. You’ll need:

  • autoconf
  • automake
  • bash
  • binutils
  • bison
  • check
  • dejagnu
  • expect
  • flex
  • gcc
  • gcc-c++
  • gettext
  • libtool
  • lsb_release
  • make
  • patch
  • pkgconfig
  • tar
  • wget

If you want to package your compiler builds into RPM files, you’ll also need to install your distro’s RPM building tools; for RedHat/CentOS, they are:

  • rpm-build
  • rpm-sign

If you’re setting up a system based on RedHat/CentOS for the first time, the easiest way is to install your distro’s standard development tools package. For RedHat/CentOS, you can use yum to install the “Development Tools” group.

$ sudo yum group install "Development Tools"
$ sudo yum install redhat-lsb rpm-build rpm-sign check dejagnu expect

On Ubuntu, you can use apt-get to do something like this:

$ sudo apt-get install linux-headers-`uname -r` build-essential 
$ sudo apt-get lsb-release check dejagnu expect

At the time of this writing, gcc-builder only supports building compressed tarballs or RPM files. If there’s enough demand, or someone volunteers to help, I’ll add support for building DEB files.

Step 2: Clone the Repo

Clone the gcc-builder git repo and check out the branch for the major version of GCC that you want to build. As of this writing, the branches available for checkout are gcc5, gcc6, and gcc7. Each branch can build all the minor versions associated with the branch’s major version number; for example, the gcc6 branch can build GCC 6.1, 6.2, 6.3, and 6.4.

$ cd ${BUILD_TOP_DIR}
$ git clone https://gitlab.com/BobSteagall/gcc-builder.git
$ cd gcc-builder
$ git checkout gcc7

In this example we’re building GCC 7.2, so I checked out the gcc7 branch.

Step 3: Customize the Build Variables

Before building, you’ll need to customize at least one of the variables exported by gcc-build-vars.sh. In particular, the first variable at the top of that file, GCC_VERSION, must be edited to specify the version of GCC you want to download and build. Start up your favorite editor and open gcc-build-vars.sh, for example:

$ vi ./gcc-build-vars.sh

Near the top you’ll find the variable that specifies the GCC version number:

##- Customize this variable to specify the version of GCC 7 that you want
##  to download and build.
##
export GCC_VERSION=7.X.0

Use the same version number as the GCC release does (you can see all versions of GCC here). For our example build of GCC 7.2, this variable should be set to:

export GCC_VERSION=7.2.0

This variable must be set correctly for the other gcc-builder scripts to work. As mentioned above, with this definition our build will eventually be installed into /usr/local/gcc/7.2.0.

There are several other variables in the gcc-build-vars.sh file that can be customized to fine-tune your build, and they are described below in Other Build Variables.

Step 4: Perform the Build

This step consists of executing the build-gcc.sh script. This script manages the process downloading the required source packages, unpacking them, configuring them, building them, and running the compiler test suite that comes with the GCC distribution:

$ ./build-gcc.sh | tee build.log

Alternatively, if you want to save time and build GCC without running the tests, you can use:

$ ./build-gcc.sh -T | tee build.log

As you can see, I’m using the tee command to copy output from the build process into a log file. I find the log file to be pretty useful on those occasions where the build fails or does something wonky.

Step 5: Stage the Build

Assuming that the build succeeds, and you are satisfied with the test results, the next step is to run the stage-gcc.sh script to create a dummy installation that I call the staging area.

$ ./stage-gcc.sh

The staging area will be created in the ./dist subdirectory. The files installed there become the source for building whichever installation package you choose to create in Step 6. In our example here of building GCC 7.2.0, the ./dist subdirectory will contain the compiler in ./dist/usr/local/gcc/7.2.0 and some scripts for setting environment variables in ./dist/usr/local/bin.

Step 6: Make an Installation Package

Create a Compressed Tarball. If you want to create a compressed tarball for subsequent installations, run:

$ ./pack-gcc.sh

The resulting tarball will be in the ./packages subdirectory. Note that the tarball will have the Linux distro name as a substring in the tarball file name; on my CentOS-7 VM, the tarball file name is kewb-gcc720-CentOS-7-x86_64.tgz. To install the tarball:

$ cd /
$ sudo tar -zxvf ${BUILD_TOP_DIR}/gcc-builder/packages/kewb-gcc720-CentOS-7-x86_64.tgz

or, alternatively, from within the build directory:

$ sudo tar -zxvf ./packages/kewb-gcc720-CentOS-7-x86_64.tgz -C /

If you are satisfied that everything is working correctly, then at some point you’ll probably want to set ownership of the installed files to root:

$ cd /usr/local
$ sudo chown -R root:root gcc/7.2.0/
$ sudo chown root:root bin/*gcc720*

Installing from a compressed tarball is my preferred technique these days. It’s quick and easy to do the installation, it’s trivial to remove an installation, and absolutely no system configuration files, directories, or databases are affected.

Create an RPM. It’s also possible to create an RPM:

$ ./make-gcc-rpm.sh -v

The resulting RPM will be created in the ./packages subdirectory. On my CentOS-7 VM, the RPM file name is kewb-gcc720-0.el7.x86_64.rpm. You can install it using rpm or yum on the command line.

Step 7: Use Your Custom GCC Build

In order to use your new compiler installation, some path variables need to be adjusted. The simplest way to do this is source the setenv-for-gcc720.sh script that was created with the compiler and installed into /usr/local/bin.

Option A: Source the script /usr/local/bin/setenv-for-gcc-720.sh, which was installed as part of Step 6 above.

$ source /usr/local/bin/setenv-for-gcc720.sh

This script makes a copy of some existing path environment variables and then modifies those variables so that your compiler installation can be used. If you would like stop using your new compiler and restore the old path environment variables, you can source /usr/local/bin/restore-default-paths-gcc720.sh:

$ source /usr/local/bin/restore-default-paths-gcc720.sh

OR

Option B: Modify your PATH environment variable so that the directory $GCC_INSTALL_PREFIX/bin appears before the directory where your system default compiler is installed (which is usually in /usr/bin or /usr/local/bin). You will also need to modify your LD_LIBRARY_PATH environment variable so that the $GCC_INSTALL_PREFIX/lib and $GCC_INSTALL_PREFIX/lib64 directories appear first in that path. For example,

$ export PATH=/usr/local/gcc/7.2.0/bin:$PATH
$ export LD_LIBRARY_PATH=/usr/local/gcc/7.2.0/lib:/usr/local/gcc/7.2.0/lib64:$LD_LIBRARY_PATH

Step 8: Clean Up

After you have built and installed GCC, are satisfied it’s working, and have decided to keep it, you can remove all of the working directories using the clean-gcc.sh script:

$ ./clean-gcc.sh

This script will delete all of the unpacked source and build directories, the staging area ./dist, and the packages output directory ./packages.

Other Build Variables

There are several other build variables defined in gcc-build-vars.sh. Although they all have default values that I think are reasonable, you may want to change one or more of them. The following is a brief description the variables and their meanings.

Package Naming

The following two variables contribute to your installation’s name. The first specifies the installation package name, while the second sets the name of the GCC build triple.

##- Customize variable this to name the installation; the custom name is
##  displayed when a user invokes gcc/g++ with the -v or --version flags.
##
export GCC_PKG_NAME='KEWB Computing Build'

##- Customize this variable to define the middle substring of the GCC build
##  triple.
##
export GCC_CUSTOM_BUILD_STR=kewb

The effects of customizing these variables can be seen by running gcc -v:

[dev2 ~]$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/gcc/7.2.0/libexec/gcc/x86_64-kewb-linux-gnu/7.2.0/lto-wrapper
Target: x86_64-kewb-linux-gnu
Configured with: /space/zbuild/gcc-builder/gcc-7.2.0/configure -v --with-pkgversion='KEWB Computing Build' --prefix=/usr/local/gcc/7.2.0 --program-suffix= --enable-tls --enable-shared --enable-threads=posix --enable-__cxa_atexit --enable-clocale=gnu --enable-languages=c,c++ --enable-lto --enable-bootstrap --disable-nls --disable-multilib --disable-install-libiberty --disable-werror --with-system-zlib
Thread model: posix
gcc version 7.2.0 (KEWB Computing Build)

The text shown in green above shows the custom build triple (x86_64-kewb-linux-gnu) and the custom package name (KEWB Computing Build).

Installation Location

The next variable is used to specify the directory in which your GCC build will be installed.

##- Customize this variable to specify where this version of GCC will be
##  installed.
##
export GCC_INSTALL_PREFIX=/usr/local/gcc/$GCC_VERSION

Given that we’ve set GCC_VERSION=7.2.0 in Step 2 above, from this we can see that the installation directory for our build will be /usr/local/gcc/7.2.0.

Timestamp

The next variable specifies a timestamp that is applied to all files in the final installation package (.tgz or .rpm).

##- Customize this variable to specify the installation's time stamp.
##
export GCC_TIME_STAMP=201708141000

It seems like a silly thing, but I like to have the files in my compiler installations all have the same timestamp, usually the day I complete the first full installation of that compiler.

Concurrent Build and Test

The next two variables specify the number of concurrent processes to be used by make when building and testing the installation.

##- Customize these variables if you want to change the arguments passed
##  to make that specify the number of jobs/processes used to build and
##  test GCC, respectively.
##
export GCC_BUILD_JOBS_ARG='-j8'
export GCC_TEST_JOBS_ARG='-j4'

I’ve had the fewest build problems and shortest overall build times when the number of build jobs is equal to the number of cores on my system and the number of test jobs is about half the number of cores on my system.

Custom Binutils Version

By default, gcc-builder will download and build a specific version of GNU binutils (version 2.26), and install as and ld from that build into the GCC installation’s directory hierarchy. In this way, the assember and linker used by your version of GCC will be the custom versions rather than the system versions.

##- Set this variable to YES if you want to embed the assember and linker
##  from a custom version of GNU Binutils (specified below) into the GCC
##  installation.  If you just want to use the system's assembler and linker,
##  then undefine this variable or set its value to "NO".
##
export GCC_USE_CUSTOM_BINUTILS=YES

This is useful when you want recent processor support from the compiler and assembler, or when the operating system you’re building GCC for is so old that its assember is obsolete.

Of course, you don’t have to use this version of binutils. If you’d like to use the system version of binutils, simply set GCC_USE_CUSTOM_BINUTILS to NO.

On the other hand, if you want to use a different non-system version of binutils for your compiler build, a few lines further down you will find the following:

##- These variables define the versions of binutils, GMP, MPC, and MPFR
##  used to build GCC.
##
export BU_VERSION=2.26
export GMP_VERSION=5.1.3
export MPC_VERSION=1.0.3
export MPFR_VERSION=3.1.4

Just change the value assigned to BU_VERSION to your desired binutils version number.

Important WARNING for Rebuilding

If you want to rebuild GCC after having built and installed it according to these directions, AND you are building GCC with a custom version of the GNU binutils (in other words, GCC_USE_CUSTOM_BINUTILS=YES as per above), AND you plan to re-install the rebuilt version into the same location as its predecessor, then it is absolutely imperative that you first perform one of the following three actions:

Option A: Remove the installation directory entirely; for example:

$ cd /usr/local/gcc
$ sudo rm -rf 7.2.0

OR

Option B: Rename the installation directory; for example:

$ cd /usr/local/gcc
$ sudo mv 7.2.0 7.2.0-old

OR

Option C: Rename the custom as and ld executables; for example:

$ cd /usr/local/gcc/7.2.0/libexec/gcc/x86_64-kewb-linux-gnu/7.2.0
$ sudo mv as as-old
$ sudo mv ld ld-old

Otherwise, the configure portion of the build process will find the custom as and ld executables in the GCC directory structure and build the crtbeginS.o startup file in a way that may be incompatible with your system’s default linker.

It is important that the compilation of GCC takes place using the system’s default binutils, and not the as and ld that are installed in the GCC 7.2.0 directory structure.

Summary

So that’s about it. Unless you need some unusual configuration options, this should work for you pretty much out-of-the-box. One quick note — these scripts are not meant to be used for building a cross-compiler.

Be prepared for the build to take a fair amount of time; on my Xeon-based workstation, building with six cores and testing with four cores takes about 90 minutes. On my newer Core i7-based system, building with eight cores and testing with four cores takes about an hour.

Once the build is done, I recommend a cup/glass/mug/stein/pitcher of your favorite beverage and enjoying the C++ goodness of GCC.

Thanks for stopping by.

–Bob

3 thoughts on “Building GCC on Linux

  • 2018-11-04 at 8:05 am
    Permalink

    Hi Bob,
    thanks for the awesome article.
    Currently, I’m trying to build and install gcc 6 or 7 in my Fedora. However, either manually build or using your repo and steps the build does not work due to this message:

    gcc-builder/gcc-7.3.0/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.cc:157:10: fatal error: sys/ustat.h: No such file or directory
    #include
    ^~~~~~~~~~~~~

    I got this messages in your after running ./stage-gcc.sh (Step 5). Do you have any hint to fix this error?

    Thanks,
    Yongkie

    Reply
    • 2018-11-04 at 11:05 am
      Permalink

      Hi Yonkie,
      This is new to me. If you’ve gotten to the point where you are ready to stage the build, then I assume that you were able to build successfully and the test pass rate is sufficient for your purposes.

      From your error, it looks like a file may be missing, either from the distribution, or from your system. Can you try running the stage command again, and save the output to a file, and send me the file ( send it to cpp AT bobsteagall DOT com)?

      I’m traveling today, so I may not be able to respond quickly.
      –Bob

      Reply
  • 2018-11-11 at 4:28 pm
    Permalink

    Thanks for your support and hint Bob!
    Yes, you are right. I’ve got the message in the build step already. I found out, in order to fix the above error the included header in the line 157 of the file sanitizer_platform_limits_posix.cc shall be removed, and its usage in the line 250.
    Currently, I use Fedora 29.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *