仓库源文站点原文

Clang 是一个基于 LLVM 的 C/C++ 编译器,与 GCC 相比有一些优势

如果 Linux 发行版比较新,可以直接用包管理器安装;但如果发行版比较老旧,就只能编译安装了。本文介绍一些编译安装 clang 的方法。

直接编译安装

Clang 是 LLVM 项目的一部分。LLVM 是一个编译器基础设施,它定义一种中间代码 (IR),并且能够将这种中间代码编译成各个平台 (x86, ARM, ...) 的机器码。这在 LLVM 中称为编译器后端。而 clang 则是 C/C++ 的编译器前端,负责将 C/C++ 代码编译成 LLVM 中间代码。因此要编译安装 clang,我们就要编译 LLVM。

我们进入 LLVM 官网的下载页面 https://releases.llvm.org/ 下载 LLVM 源码。LLVM 10.0.0 之后提供整合包 <ruby>llvm-project<rt>LLVM 全家桶</rt></ruby> 下载,包含 clang 在内的各种 LLVM 组件。以最新的 20.1.0 为例,我们直接下载 LLVM 20.1.0 并解压

curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.0/llvm-project-20.1.0.src.tar.xz
unxz llvm-project-20.1.0.src.tar.xz
tar -xf llvm-project-20.1.0.src.tar

LLVM 使用 CMake 构建,要求版本至少为 3.20.0。进入 llvm-project 目录,接着创建 build 目录,然后执行 cmake

cd llvm-project-20.1.0.src
mkdir build && cd build
cmake ../llvm -DCMAKE_BUILD_TYPE=Release \
              -DLLVM_ENABLE_PROJECTS='clang' \
              -DLLVM_ENABLE_RUNTIMES='compiler-rt;libcxx;libcxxabi;libunwind'

CMake 这一步可以传入各种参数,这里介绍一些常用的参数

如果 LLVM 的各个依赖项都没有问题、这一步成功后,便可执行 make 开始构建

make -j8 # 根据机器情况调整线程数

如果一切顺利,执行 sudo make install 即可完成安装。安装前可以执行 make check-clang 执行 clang 的测试用例,确认没有问题。

老旧发行版编译

要成功构建 LLVM 20.1.0,GCC 版本至少为 7.4。然而在一些老旧发行版(如 CentOS 7)中,GCC 版本并不能满足要求。为此我们需要先用系统的老版编译器编译一个新版编译器,再用新版编译器编译 LLVM。

# 下载 gcc-9.1.0
curl -LO https://ftp.gnu.org/gnu/gcc/gcc-9.1.0/gcc-9.1.0.tar.xz
unxz gcc-9.1.0.tar.xz
tar -xf gcc-9.1.0.tar

cd gcc-9.1.0

# 安装依赖
./contrib/download_prerequisites
./configure --prefix=${HOME}/toolchains # 安装到 ~/toolchains
make -j8
make install

这里我们编译的 gcc-9.1.0 是用于构建 LLVM 的“临时”编译器,我们不把它安装到系统目录 (/usr/local/),而是安装到 ~/toolchains。GCC 是系统编译器,不可随意升级,否则可能导致系统其它软件出现兼容性问题。

接着我们用刚刚编译的 gcc-9.1.0 构建 LLVM。

cd llvm-project-20.1.0.src
mkdir build && cd build
cmake ../llvm -DCMAKE_C_COMPILER=${HOME}/toolchains/bin/gcc \
              -DCMAKE_CXX_COMPILER=${HOME}/toolchains/bin/g++ \
              -DCMAKE_BUILD_TYPE=Release \
              -DLLVM_ENABLE_PROJECTS='clang' \
              -DLLVM_ENABLE_RUNTIMES='compiler-rt;libcxx;libcxxabi;libunwind'

LD_LIBRARY_PATH=${HOME}/toolchains/lib:${HOME}/toolchains/lib64 make -j8

这里我们用 -DCMAKE_C_COMPILER-DCMAKE_CXX_COMPILER 指定 C/C++ 编译器的完整路径,也就是 gcc-9.1.0 的安装路径 ~/toolchains/ 下的 bin/gccbin/g++。LLVM 构建过程中会执行编译出来的工具,这些工具都依赖于 gcc-9.1.0 的 C++ 运行库。因此我们要用环境变量 LD_LIBRARY_PATH 指定动态库路径,确保它们能正常运行。

因为这样构建的 LLVM 工具链都依赖于 gcc-9.1.0 的运行库,我们要设置好 LD_LIBRARY_PATH 才能正常运行它们。

$ bin/clang --version # 直接运行通常会出现 libstdc++ 不兼容的报错
bin/clang: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by bin/clang)

$ LD_LIBRARY_PATH=${HOME}/toolchains/lib:${HOME}/toolchains/lib64 bin/clang --version # 需要指定 gcc-9.1.0 的动态库路径
clang version 20.1.0
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/luyuhuang/llvm-project-20.1.0.src/build/bin

$ LD_LIBRARY_PATH=${HOME}/toolchains/lib:${HOME}/toolchains/lib64 ldd bin/clang
        linux-vdso.so.1 =>  (0x00007fffe95c9000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f470cbbb000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f470c9b3000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f470c7af000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f470c599000)
        libstdc++.so.6 => /home/luyuhuang/toolchains/lib64/libstdc++.so.6 (0x00007f470c1c0000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f470bebe000)
        libgcc_s.so.1 => /home/luyuhuang/toolchains/lib64/libgcc_s.so.1 (0x00007f470bca6000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f470b8e2000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f470cdd7000)

Bootstrap

前面使用 gcc-9.1.0 编译的 LLVM 工具链虽然可以运行,但是却依赖于 gcc-9.1.0 的运行库。由于系统的 C++ 运行库不能随意升级,我们不便将 gcc-9.1.0 的运行库安装到系统中。这里比较合适的做法是让 clang 做一次 bootstrap(自举),用 gcc-9.1.0 编译的 clang 构建 LLVM,并链接到 LLVM 的 C++ 运行库(也就是 libc++)。

cd llvm-project-20.1.0.src
mkdir build1 && cd build1 # 创建一个新的构建目录
cmake ../llvm -DCMAKE_C_COMPILER= $(realpath ../build)/bin/clang \      # 使用 ../build 目录下,用 gcc-9.1.0 编译的 clang 构建
              -DCMAKE_CXX_COMPILER= $(realpath ../build)/bin/clang++ \
              -DLLVM_ENABLE_LIBCXX=ON \                                 # 使用 LLVM 的 libc++
              -DCMAKE_BUILD_TYPE=Release \
              -DLLVM_ENABLE_PROJECTS='clang' \
              -DLLVM_ENABLE_RUNTIMES='compiler-rt;libcxx;libcxxabi;libunwind'

LD_LIBRARY_PATH=${HOME}/toolchains/lib:${HOME}/toolchains/lib64:$(realpath ../build)/lib/x86_64-unknown-linux-gnu make -j8

这里我们则是将编译器路径设置成前面用 gcc-9.1.0 编译的 clang 的路径。同时设置 -DLLVM_ENABLE_LIBCXX=ON 链接到 LLVM 的 libc++。最后注意环境变量 LD_LIBRARY_PATH 除了需要指定 gcc-9.1.0 的运行库路径之外,还需要指定前面 gcc-9.1.0 编译的 LLVM 的运行库路径。

构建完成后,执行 sudo make install 安装即可。由于 LLVM 20.1.0 的 C++ 运行库位于 lib/x86_64-unknown-linux-gnu/(更老版本的 LLVM 则直接在 lib/ 里),我们通常需要再配置下动态库搜索路径。

echo /usr/local/lib/x86_64-unknown-linux-gnu >> /etc/ld.so.conf
ldconfig

这样安装的 clang 就能正常运行了。

$ clang --version
clang version 20.1.0
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin

$ ldd /usr/local/bin/clang
        linux-vdso.so.1 =>  (0x00007ffc6d57c000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fca001ae000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fc9fffa6000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fc9ffda2000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fc9ffaa0000)
        libz.so.1 => /lib64/libz.so.1 (0x00007fc9ff88a000)
        libc++.so.1 => /usr/local/lib/x86_64-unknown-linux-gnu/libc++.so.1 (0x00007fc9ff583000)
        libc++abi.so.1 => /usr/local/lib/x86_64-unknown-linux-gnu/libc++abi.so.1 (0x00007fc9ff33b000)
        libunwind.so.1 => /usr/local/lib/x86_64-unknown-linux-gnu/libunwind.so.1 (0x00007fc9ff12e000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc9fef18000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fc9feb54000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fca0ad52000)
        libatomic.so.1 => /lib64/libatomic.so.1 (0x00007fc9fe94c000)

参考资料: