简介:这份资源面向需要在无外网或网络不稳定环境下部署编译工具链的运维与开发人员,提供了一套完整的 GCC 离线安装方案。压缩包共 12 个文件,以 11 个 rpm 包和 1 个 sh 安装脚本为主,整体约 24.09MB,其中 rpm 涵盖 gcc、gcc-c++、cpp、glibc-devel、glibc-headers、kernel-headers、libstdc++-devel 以及 ppl、mpfr、cloog-ppl 等依赖组件,脚本则负责自动解压、解析依赖并完成安装。资源针对 CentOS、RHEL 等基于 RPM 的发行版,省去逐一下载依赖的繁琐,适合远程服务器、内网机房或教学实验环境使用。目前已有 556 人学习下载,读者可借此掌握离线安装 GCC 的完整流程,理解 RPM 包结构与依赖处理思路,并直接复用脚本快速搭建可用的编译环境。
1. 内网机器上装 GCC:为什么“离线安装所有包”比想象中难
很多做交付的工程师都遇到过这种场景:客户现场是一台完全隔离的内网服务器,操作系统可能是 CentOS 7.9、Kylin V10 或者 Ubuntu 20.04,上面连yum源都指不到外网,但业务代码必须现场编译。你手里只有一台能上网的笔记本和一个 U 盘,任务是把 GCC 装上去,还得让gcc --version输出正确的版本号。标题里的“gcc 离线安装所有包以及脚本”,说的就是这件事——不是装一个gcc二进制那么简单,而是把 GCC 依赖的 GMP、MPFR、MPC、ISL 这些数学库,以及 binutils、glibc 头文件、C++ 标准库全部凑齐,再用一个可重复执行的 shell 脚本把整个流程固化下来。
热搜里“ubuntu 安装 gcc 失败”“centos7.9 安装 gcc”“kylin v10 编译 gcc 12”这些词,背后是同一类痛点:在线装靠apt或yum一行命令搞定,离线装却要自己解决依赖闭包。更麻烦的是“gcc 升级后为啥还是旧版本”——装完了,which gcc指向的还是/usr/bin/gcc,新版本躺在/usr/local/bin里没进 PATH。这篇笔记就按“先讲清楚要凑哪些包,再给可抄的脚本,最后说坑”的顺序走,适合需要在内网、信创环境或 CI 离线节点上落地编译工具链的从业者。
2. 离线安装 GCC 到底要凑齐哪些包:依赖闭包拆解
2.1 GCC 源码编译的四层依赖关系
GCC 不是自包含的,它编译自身时需要三类外部依赖。第一层是数学库:GMP(任意精度算术)、MPFR(浮点运算)、MPC(复数运算),这三个是编译 GCC 前端时链接的,缺一个configure就报Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+。第二层是基础设施:binutils 提供as、ld,glibc 提供系统调用封装和头文件,如果你要装的是 GCC 12 而系统自带 binutils 2.27,链接阶段可能因为新指令集不支持而失败。第三层是可选优化库:ISL 用于 Graphite 循环优化,cloog 在新版本里已被 ISL 取代,zlib、zstd 用于 LTO 压缩调试信息。
我一般把依赖分成“必须随 GCC 一起编”和“可以用系统自带的”两类。GMP、MPFR、MPC 建议随 GCC 一起编,因为系统自带的版本往往太老;binutils 和 glibc 如果系统版本不算太旧(CentOS 7.9 自带 binutils 2.27、glibc 2.17),可以先不动,只升 GCC。Kylin V10 这类信创系统 glibc 版本特殊,动 glibc 风险极高,血泪经验是能不碰就不碰。
2.2 离线包从哪来:三种获取路径的取舍
第一种是直接下 GCC 官方源码包gcc-12.3.0.tar.gz加gmp-6.2.1.tar.xz、mpfr-4.2.0.tar.xz、mpc-1.3.1.tar.gz,在能上网的机器上把 tarball 下好,拷进内网自己编。这种方式最干净,不依赖目标机的包管理器,缺点是编译时间长,GCC 全量编译在 4 核机器上要 40 分钟到 1 小时。
第二种是在同架构同版本的外网机器上用yumdownloader或apt-get download把 rpm/deb 包连同依赖一起拉下来,做成本地源。这种方式快,但要求外网机器和目标机操作系统版本、架构完全一致,差一个小版本就可能因为 glibc 符号版本对不上而翻车。
第三种是找现成的预编译工具链,比如某些发行版提供的devtoolset包。这种方式最省事,但版本受限于别人编好的范围,且在内网里不一定拿得到。我一般首选第一种,因为可控性最高,脚本能完整复现。
2.3 用 yumdownloader 在外网机拉全依赖的命令
如果走第二种路径,在外网 CentOS 机器上执行下面这套命令,把 GCC 及其依赖全部下载到本地目录:
# 安装下载工具,--destdir 指定存放目录,--resolve 递归拉依赖 yum install -y yum-utils mkdir -p /tmp/gcc-offline yumdownloader --resolve --destdir=/tmp/gcc-offline \ gcc gcc-c++ make binutils glibc-devel \ kernel-headers glibc-headers libmpc mpfr gmp # 把下载好的 rpm 打成 tar 包,方便拷进内网 tar czf gcc-offline-rpms.tar.gz -C /tmp/gcc-offline .--resolve是关键参数,它会自动把依赖树里所有 rpm 都拉下来,不加这个参数你只会拿到顶层包,内网rpm -ivh时立刻报缺依赖。--destdir后面跟绝对路径,目录要先建好。打完包后tar里的文件数就是完整依赖闭包,拷到内网后先rpm -ivh *.rpm批量装,遇到冲突用--nodeps要谨慎,那等于放弃依赖检查。
2.4 源码编译路径的 configure 参数怎么定
走源码路径时,configure参数决定装到哪、开哪些特性。下面是我在 CentOS 7.9 上编 GCC 12 常用的配置:
# 解压依赖到 GCC 源码目录,GCC 会自动识别 cd gcc-12.3.0 tar xf ../gmp-6.2.1.tar.xz && mv gmp-6.2.1 gmp tar xf ../mpfr-4.2.0.tar.xz && mv mpfr-4.2.0 mpfr tar xf ../mpc-1.3.1.tar.gz && mv mpc-1.3.1 mpc # 建独立构建目录,不要在源码目录里直接 configure mkdir build && cd build ../configure --prefix=/usr/local/gcc-12.3.0 \ --enable-languages=c,c++ \ --disable-multilib \ --with-system-zlib \ --enable-threads=posix--prefix指定安装根目录,我习惯带版本号,方便多版本共存和回滚。--enable-languages=c,c++只编 C 和 C++ 前端,不编 Fortran、Go 能省一半时间。--disable-multilib在 64 位系统上只生成 64 位库,除非你要编 32 位程序否则别开。--with-system-zlib用系统 zlib 而不是 GCC 自带的,能减少一个依赖。--enable-threads=posix让 libstdc++ 支持多线程,C++ 项目基本都要。
3. 把安装流程写成可重复执行的 shell 脚本
3.1 脚本整体结构:检测、解压、编译、链接四段式
一个能反复跑的离线安装脚本,核心是把“幂等”做进去——重复执行不会因为目录已存在而报错,也不会把已经装好的东西再装一遍。我一般把脚本分成四段:环境检测段判断系统版本和已有 GCC,解压段把 tarball 展开到工作目录,编译段执行 configure 和 make,链接段把新 GCC 软链到/usr/local/bin并更新 PATH。下面是一个可抄的骨架:
#!/bin/bash set -euo pipefail # 任一命令失败即退出,未定义变量报错 GCC_VER="12.3.0" PREFIX="/usr/local/gcc-${GCC_VER}" WORKDIR="/opt/build-gcc" TARBALL_DIR="/opt/gcc-src" # 离线包存放目录 # 第一段:环境检测 echo "[1/4] 检测环境..." if [ -x "${PREFIX}/bin/gcc" ]; then echo "GCC ${GCC_VER} 已安装,跳过编译" exit 0 fi mkdir -p "${WORKDIR}" "${PREFIX}" # 第二段:解压 echo "[2/4] 解压源码..." cd "${WORKDIR}" tar xf "${TARBALL_DIR}/gcc-${GCC_VER}.tar.gz" cd "gcc-${GCC_VER}" for dep in gmp-6.2.1 mpfr-4.2.0 mpc-1.3.1; do tar xf "${TARBALL_DIR}/${dep}.tar."* mv "${dep}" "${dep%%-*}" doneset -euo pipefail是脚本的后悔药,任何一步失败立刻停,不会带着错误继续往下跑。if [ -x "${PREFIX}/bin/gcc" ]做幂等判断,已经装过就直接退出。${dep%%-*}是 shell 参数扩展,把gmp-6.2.1截成gmp,因为 GCC 的 configure 只认gmp、mpfr、mpc这三个目录名。
3.2 编译段:make 参数与并行度控制
编译段是耗时最长的部分,参数设错要么慢要么内存爆。下面这段接在解压段后面:
# 第三段:编译 echo "[3/4] 开始编译,预计 30-60 分钟..." mkdir -p build && cd build ../configure --prefix="${PREFIX}" \ --enable-languages=c,c++ \ --disable-multilib \ --with-system-zlib \ --enable-threads=posix # 并行度取 CPU 核数,但内存小于 8G 时降到核数一半 JOBS=$(nproc) MEM_GB=$(free -g | awk '/^Mem:/{print $2}') if [ "${MEM_GB}" -lt 8 ]; then JOBS=$(( JOBS / 2 )) fi make -j"${JOBS}" make installnproc拿 CPU 核数,free -g拿内存 GB 数。GCC 编译单个文件峰值能吃到 1.5G 内存,8G 内存机器上-j8很容易触发 OOM Killer,把编译进程杀掉,现象是 make 突然报Killed然后退出。降到一半核数能稳住。make install会把二进制、头文件、库全部装到--prefix下,这一步不要用sudo除非 prefix 在系统目录。
3.3 链接段:让新 GCC 真正生效的三个动作
装完不生效是最高频的坑,热搜里“gcc 升级后为啥还是旧版本”说的就是这个。链接段要做三件事:
# 第四段:链接与生效 echo "[4/4] 配置环境变量..." # 动作一:软链到 /usr/local/bin,优先级高于 /usr/bin ln -sf "${PREFIX}/bin/gcc" /usr/local/bin/gcc ln -sf "${PREFIX}/bin/g++" /usr/local/bin/g++ ln -sf "${PREFIX}/bin/gcc" /usr/local/bin/cc # 动作二:写 profile,让新登录的 shell 自动带上 PATH cat > /etc/profile.d/gcc-${GCC_VER}.sh <<EOF export PATH=${PREFIX}/bin:\$PATH export LD_LIBRARY_PATH=${PREFIX}/lib64:\$LD_LIBRARY_PATH EOF chmod +x /etc/profile.d/gcc-${GCC_VER}.sh # 动作三:刷新动态链接库缓存 echo "${PREFIX}/lib64" > /etc/ld.so.conf.d/gcc-${GCC_VER}.conf ldconfig动作一用软链覆盖/usr/local/bin下的 gcc,因为/usr/local/bin在 PATH 里排在/usr/bin前面。动作二写/etc/profile.d/下的脚本,新开的 shell 会自动 source。动作三ldconfig刷新缓存,否则运行新 GCC 编出来的程序会报error while loading shared libraries: libstdc++.so.6: version GLIBCXX_3.4.30 not found,因为系统找不到新版的 libstdc++。三个动作缺一个,都会出现“装了但用不上”的玄学现象。
3.4 验证脚本是否真的装对了
装完必须验证,不能只看gcc --version输出。下面这套检查能覆盖 90% 的翻车场景:
# 检查版本和路径 which gcc && gcc --version | head -1 # 检查 C++ 标准库版本 strings /usr/local/gcc-12.3.0/lib64/libstdc++.so.6 | grep GLIBCXX_3.4.30 # 编译一个用到 C++17 特性的测试程序 cat > /tmp/test.cpp <<'EOF' #include <filesystem> #include <iostream> int main() { std::cout << std::filesystem::current_path() << std::endl; } EOF g++ -std=c++17 /tmp/test.cpp -o /tmp/test && /tmp/testwhich gcc确认路径指向新版本,strings ... | grep GLIBCXX确认 C++ 标准库版本够新,最后编译一个用std::filesystem的程序,能跑通说明头文件、库、链接器全部就位。如果第三步报undefined reference to std::filesystem,说明 libstdc++ 没更新或ldconfig没生效。
4. 离线安装 GCC 的避坑清单:五个真实翻车现场
4.1 坑一:configure 报缺 GMP/MPFR/MPC
现象:执行../configure后立刻退出,提示Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+。
原因:GCC 源码目录下没有gmp、mpfr、mpc三个子目录,或者目录名带了版本号(如gmp-6.2.1),configure 不认。
解决:把解压后的目录重命名为不带版本号的名字,mv gmp-6.2.1 gmp,三个都要改。确认ls gcc-12.3.0/能看到gmp、mpfr、mpc三个目录再 configure。
4.2 坑二:make 中途被 Killed
现象:make -j8跑到一半突然输出Killed,没有其他错误信息,编译中断。
原因:内存不足触发 OOM Killer。GCC 编译某些大文件(如insn-emit.c)时单进程内存占用超过 1.5G,并行度高时总内存需求是核数乘以 1.5G。
解决:降低并行度,make -j4或make -j2。用free -h看可用内存,按每核 1.5G 估算安全并行度。实在内存小就单线程make,慢但稳。
4.3 坑三:装完 gcc --version 还是旧版本
现象:make install成功,但gcc --version输出还是系统自带的 4.8.5。
原因:/usr/bin/gcc在 PATH 里的优先级高于/usr/local/bin/gcc,或者当前 shell 的 PATH 缓存没刷新。
解决:which -a gcc看所有 gcc 路径,确认/usr/local/bin/gcc存在。执行hash -r清 shell 命令缓存,source /etc/profile.d/gcc-12.3.0.sh刷新 PATH。如果还不行,检查/etc/profile里有没有在 source profile.d 之后又覆盖了 PATH。
4.4 坑四:编译出的程序运行时报 GLIBCXX 版本错误
现象:新 GCC 编出来的 C++ 程序在别的机器上跑,报version GLIBCXX_3.4.30 not found。
原因:程序链接了新版的 libstdc++.so.6,但目标机器的/usr/lib64/libstdc++.so.6还是旧版,且LD_LIBRARY_PATH没指向新库。
解决:在目标机器上把新 GCC 的lib64/libstdc++.so.6拷过去,或者编译时加-static-libstdc++静态链接标准库。生产环境我一般用静态链接,省得每台机器都配库路径。
4.5 坑五:Kylin V10 上编译 GCC 12 报 glibc 符号错误
现象:在 Kylin V10 上编 GCC 12,链接阶段报undefined reference to __libc_csu_init或类似符号错误。
原因:Kylin V10 的 glibc 版本和 GCC 12 默认假设的 glibc 接口不一致,某些符号在新 glibc 里被移除或改名。
解决:configure 时加--with-glibc-version=2.28(按目标机实际 glibc 版本填),或者用--disable-libsanitizer跳过对 glibc 版本敏感的 sanitizer 库。信创环境建议先查ldd --version确认 glibc 版本,再决定 GCC 版本,别硬上最新版。
5. 让脚本更耐用的两个进阶技巧
5.1 用日志文件定位编译失败的具体位置
GCC 编译输出几千行,失败时终端里翻不到关键信息。我习惯把 configure 和 make 的输出全部重定向到日志文件,失败时直接 grep:
# 把编译输出同时写到终端和日志 ../configure --prefix="${PREFIX}" ... 2>&1 | tee "${WORKDIR}/configure.log" make -j"${JOBS}" 2>&1 | tee "${WORKDIR}/make.log" # 失败后快速定位 grep -n -i "error" "${WORKDIR}/make.log" | head -202>&1把 stderr 合并到 stdout,tee同时输出到终端和文件。失败后grep -n -i "error"带行号找错误,比在终端里往上翻高效得多。热搜里“gcc 日志输出到文件”说的就是这个需求,tee是最省事的方案,不用改脚本逻辑。
5.2 多版本共存与快速切换
内网机器经常需要同时保留 GCC 9 和 GCC 12,不同项目用不同版本。我的做法是每个版本装到独立 prefix,用update-alternatives管理切换:
# 注册两个版本的 gcc 到 alternatives update-alternatives --install /usr/local/bin/gcc gcc /usr/local/gcc-9.5.0/bin/gcc 90 update-alternatives --install /usr/local/bin/gcc gcc /usr/local/gcc-12.3.0/bin/gcc 120 # 交互式切换 update-alternatives --config gcc数字 90 和 120 是优先级,数字大的默认选中。--config会列出所有版本让你选。这样切换不用改 PATH,也不会出现软链覆盖的混乱。我一般把这条也写进安装脚本末尾,装完自动注册,省得下次手动配。
这套方案我在 CentOS 7.9、Ubuntu 20.04、Kylin V10 上都跑过,源码编译路径最稳,yumdownloader 路径最快但版本受限。脚本里的幂等判断和日志重定向是两个最值得保留的习惯,前者让你敢重复执行,后者让你失败时不用猜。希望帮到你。
本文还有配套的精品资源,点击获取