gcc 的非 root 用户离线编译安装比其他的软件的源码安装都要复杂:因为它有依赖,在服务器上无法通过联网下载,要提前下载依赖的压缩包,而且gcc的编译时间很长。

安装过程主要参考CentOS7 离线升级安装gcc到6.3.0Linux 非root安装GCC9.1.0

下载依赖

  • 在官网或者镜像网站下载 gcc-11.4.0.tar.gz,传到服务器上解压为~/tmp/gcc-11.4.0

  • 进入~/tmp/gcc-11.4.0子目录,需要解决下载依赖的问题。在可以联网的情况下,直接执行自带的下载依赖的脚本 ./contrib/download_prerequisites。如果服务器无法联网,则需要手动下载处理

  • 查看上述脚本,找到四个必要的依赖 gmp,mpfr,mpc,isl 以及对应的具体版本,例如 gcc-11.4.0 需要的四个依赖版本及其下载地址如下(不同版本的gcc对应的依赖版本也不一样)

    1
    2
    3
    4
    5
    6
    gmp='gmp-6.1.0.tar.bz2'
    mpfr='mpfr-3.1.6.tar.bz2'
    mpc='mpc-1.0.3.tar.gz'
    isl='isl-0.18.tar.bz2'

    base_url='http://gcc.gnu.org/pub/gcc/infrastructure/'

  • 用脚本中提供的链接下载对应的压缩包,上传到目录~/tmp/gcc-11.4.0中,无需解压

  • 把脚本中实际下载命令对应的行注释掉,然后执行 ./contrib/download_prerequisites,因为这个脚本在下载压缩包之外还干了别的事,不能整个绕过

源码编译与安装

  • 返回~/tmp/gcc-11.4.0的上一层目录,创建专门的编译目录~/tmp/gcc-11.4.0-build,进入这个编译目录

  • 在编译目录中依次执行如下命令进行编译(编译过程很长,最好开一个后台任务或者用screentmux之类的工具,避免编译被中断),这里没有选择将高版本gcc安装到~/.local,而是创建了一个单独的目录进行安装(必须使用完整的绝对路径,否则会报错。编译的目录占用空间比较大,约6G)

    1
    2
    ../gcc-11.4.0/configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --prefix=$HOME/opt/gcc-11.4.0
    make -j4

  • 然后安装到本地

    1
    make install

编译过程中可能遇到如下问题(编译过程中的报错信息可能长达几千行,必须找到第一个报错才有实际价值)

1
#error "Unable to find a suitable type for HOST_WIDE_INT"

这是因为某些环境变量被提前设置了,在编译之前撤销下面这些相关的环境变量定义即可

1
unset LIBRARY_PATH CPATH C_INCLUDE_PATH PKG_CONFIG_PATH CPLUS_INCLUDE_PATH INCLUDE

安装配置

安装完成并不代表高版本gcc已经可以使用了,事实上,如何让非标准路径下的高版本gcc标准路径下的低版本gcc共存才是最大的问题: 我们既希望新程序使用高版本gcc编译,使用对应的库,也希望依赖低版本gcc所对应库的程序仍然可以正常运行,这有点麻烦。

可能涉及的环境变量包括:

  • PATH
  • LIBRARY_PATH
  • LD_LIBRARY_PATH
  • C_INCLUDE_PATHCPLUS_INCLUDE_PATH

首先需要修改PATH以找到正确的gcc/g++

1
2
which gcc
which g++

可以将高版本gcc所对应路径添加到PATH的最前面,例如在~/.bashrc添加

1
export PATH="$HOME/opt/gcc-11.4.0/bin:$PATH"

在编译时,编译器通常会自动找到配套的标准库头文件和库文件,但是在运行时存在一个问题: 用新版本gcc编译产生的可执行文件找不到正确的libstdc++等动态库,运行时报错。

可以通过ldd命令来查看可执行程序实际找到的动态库路径

1
ldd ./a.out

可以通过设置环境变量LD_LIBRARY_PATH来指定动态库的搜索路径,例如在~/.bashrc添加

1
export LD_LIBRARY_PATH="$HOME/opt/gcc-11.4.0/lib64:$LD_LIBRARY_PATH"

但是上述操作仍然可能存在隐患,gcc的版本冲突可能产生在某些环节中产生问题,尤其是对于LD_LIBRARY_PATH的修改可能影响其它程序的运行。

为了找到正确的动态库,更保守的方法是在编译时将对应的路径以RPATH的方式提供给二进制程序,例如

1
g++ -Wl,-rpath=$HOME/opt/gcc-11.4.0/lib64 main.cpp

cmake配置

cmake也需要处理上面的两个问题:找不到正确的编译器,找不到正确的动态库(编译时,运行时)。

除了上面的做法,下面再介绍一些仅局限于cmake范围内,对系统影响更小的做法。

对于编译器的查找,可以通过环境变量CCCXX来指定,例如

1
2
export CC=$HOME/opt/gcc-11.4.0/gcc
export CXX=$HOME/opt/gcc-11.4.0/g++

也可以在cmake命令中加上CMAKE_C_COMPILERCMAKE_CXX_COMPILER选项来指定(优先级比CCCXX更高),例如

1
cmake -S . -B build -DCMAKE_C_COMPILER=$HOME/opt/gcc-11.4.0/gcc -DCMAKE_CXX_COMPILER=$HOME/opt/gcc-11.4.0/g++

或者直接写入CMakeLists.txt中

1
2
set(CMAKE_C_COMPILER "${GCC_PATH}/bin/gcc")
set(CMAKE_CXX_COMPILER "${GCC_PATH}/bin/g++")

对于动态库的查找,可以通过设置RPATH的方式,并不需要手动设置gcc的编译参数,cmake已经提供了RPATH相关变量 (但是拆分成了编译时和安装时两种情况),例如在CMakeLists.txt中添加

1
2
set(CMAKE_BUILD_RPATH "${GCC_PATH}/lib64")
set(CMAKE_INSTALL_RPATH "${GCC_PATH}/lib64")

这里的修改最好放在project命令之前。