If one is stuck on an old linux distribution, but needs a newer compiler, one possible solution is to compile clang manually. Note that clang is shipped with llvm, so we are going to build llvm.
Also, building software on an old linux distribution is one of the way of creating portable software that can run on a variety of target systems. Your binary compiled on debian 8 will run on any system that has glibc >= 2.19, like:
In this post, we'll assume the target distribution is debian 8.0, which reached EOL on June 30, 2020. One can adapt the following instruction to older or different distributions. If one is on macOS, macports is a better alternative, as it's easier and faster than manually compiling clang.
The target distribution ships with gcc 4.9.2, and llvm 7.1.0 is the latest version that can be compiled with that compiler. Per the documentation, this is the required software for building it:
Also, we'll use ninja instead of gnu make to speed up the build.
So, we'll install the basic packages needed for building software:
apt-get install build-essential ninja-build python2.7
Llvm 7.1.0 requires cmake >= 3.4.3, unfortunately debian 8 ships with cmake 3.0.2. So we need to upgrade cmake first.
We'll use $HOME/llvm
as a working directory:
mkdir -p ~/llvm
cd ~/llvm
Then we download cmake's source code:
# If using curl
curl -LO 'https://github.com/Kitware/CMake/releases/download/v3.23.3/cmake-3.23.3.tar.gz'
# If using wget
wget 'https://github.com/Kitware/CMake/releases/download/v3.23.3/cmake-3.23.3.tar.gz'
And extract it:
tar xf cmake-3.23.3.tar.gz
cd cmake-3.23.3
We are going to disable the use of openssl in our custom cmake build, because we don't need fetch_content
and our target distribution ships with an outdated openssl anyway. So the build instructions are:
./bootstrap --parallel=$(nproc) --prefix=/usr/local -- -DCMAKE_USE_OPENSSL=OFF
make -j$(nproc)
sudo make install
hash -r
Finally, clean the build directory because we don't need it anymore:
cd ~/llvm
rm -r cmake-3.23.3
Go to the download page for the target version, and download: LLVM source code, Clang source code, compiler-rt source code, libc++ source code, libc++abi source code, LLD Source code. We'll put everything in our working directory:
cd ~/llvm
# If using curl
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/llvm-7.1.0.src.tar.xz'
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/cfe-7.1.0.src.tar.xz'
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/clang-tools-extra-7.1.0.src.tar.xz'
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/compiler-rt-7.1.0.src.tar.xz'
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/libcxx-7.1.0.src.tar.xz'
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/libcxxabi-7.1.0.src.tar.xz'
curl -LO 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/lld-7.1.0.src.tar.xz'
# If using wget
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/llvm-7.1.0.src.tar.xz'
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/cfe-7.1.0.src.tar.xz'
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/clang-tools-extra-7.1.0.src.tar.xz'
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/compiler-rt-7.1.0.src.tar.xz'
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/libcxx-7.1.0.src.tar.xz'
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/libcxxabi-7.1.0.src.tar.xz'
wget 'https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/lld-7.1.0.src.tar.xz'
This is straightforward, we just extract each tarball to the current directory:
for i in *.tar.xz; do tar xf "$i"; done
Llvm require each of its components to live in specific parts of the source tree, so it can detect them at build time and build them. So invoke the following commands to move them into place (adapt to your specific llvm version if needed):
mv cfe-7.1.0.src llvm-7.1.0.src/tools/clang
mv clang-tools-extra-7.1.0.src llvm-7.1.0.src/tools/clang/tools/extra
mv compiler-rt-7.1.0.src llvm-7.1.0.src/projects/compiler-rt
mv libcxx-7.1.0.src llvm-7.1.0.src/projects/libcxx
mv libcxxabi-7.1.0.src llvm-7.1.0.src/projects/libcxxabi
mv lld-7.1.0.src llvm-7.1.0.src/tools/lld
If the following, we are building llvm in Release mode, into /usr/local
, using ./Build
as a build directory, using ninja as a build system, and we're going to disable building documentation, examples, and tests:
cd ~/llvm/llvm-7.1.0.src
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-B Build \
-G Ninja \
-DLLVM_INCLUDE_DOCS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
.
cmake --build Build
sudo cmake --build Build --target install
hash -r
If all goes well, llvm and clang should be installed into /usr/local
. We can check that with the following commands:
llvm-config --version
clang --version
We can also try compiling a small executable with clang to test whether it works fine:
printf "#include<stdio.h>\nint main() {\nprintf(\"It works\\\\n\");\nreturn 0;\n}\n" > /tmp/hello.c
clang -o /tmp/hello /tmp/hello.c
/tmp/hello
rm /tmp/hello{,.c}
If the above printed It works
, then the llvm installation has succeeded.
But wait! there's more. We can also check whether llvm's C++ support was correctly installed and is working fine for compiling C++17 code:
cat <<EOF>/tmp/hello17.cpp
#include <iostream>
int main() { std::cout << u8"C++17 works\n"; return 0; }
EOF
clang++ -o /tmp/hello17 -std=gnu++17 -stdlib=libc++ /tmp/hello17.cpp
/tmp/hello17
rm /tmp/hello17{,.cpp}
If the above printed C++17 works
then the clang++ installation is functional.
Finally, we can clean our working directory because it's not needed anymore:
cd ~
rm -r ~/llvm