1. 编译卡在 checking 的本质:不是慢,是卡住了
如果你在 Ubuntu 上从源码编译过软件,大概率见过这个场景:敲完./configure,终端刷出一行checking for gcc...,然后光标就停在那里,一分钟、五分钟、十分钟纹丝不动。你以为是机器性能不行,换了台配置更高的机器,结果还是一样。问题根本不在 CPU 算力,而在于配置脚本正在等待一个永远不会返回结果的网络请求。
这个现象在源码编译圈子里极其常见,尤其是编译那些依赖 autotools 构建系统的项目时。configure脚本在正式生成 Makefile 之前,会做大量环境探测工作:检查编译器是否存在、检查某个头文件能不能用、检查某个库的版本号够不够。这些检查绝大多数是本地操作,速度很快。但有一类检查会触发网络访问,一旦网络环境不通畅,脚本就会一直阻塞在超时等待上,表现出来就是"一直在 checking"。
我最早踩这个坑是在一台内网机器上编译一个图像处理库。那台机器没有外网访问权限,./configure跑了四十分钟还没结束,用strace跟了一下才发现,进程卡在一个connect()系统调用上,目标地址是某个外部主机。从那以后我就养成了一个习惯:编译任何源码之前,先看一眼 configure 脚本里有没有网络探测逻辑。
这篇文章适合所有在 Ubuntu 上从源码编译软件的人,不管你是刚接触 Linux 编译的新手,还是已经编译过几十个项目的老手。我会把"为什么卡"和"怎么解决"这两件事讲透,给出可以直接复制粘贴的操作步骤,也会分享一些我这些年积累的排查技巧和避坑经验。
2. 编译流程拆解:configure 阶段到底在干什么
2.1 从源码到可执行文件的三步走
在解决问题之前,有必要把源码编译的完整流程理清楚。Ubuntu 下从源码安装软件,标准流程是三步:
./configure:探测当前系统的环境,生成适配本机的 Makefilemake:根据 Makefile 里的规则,调用编译器把源码编译成目标文件,再链接成可执行文件make install:把编译好的文件复制到系统指定目录
很多人以为"编译慢"是第二步make的问题,但实际上,卡在 checking 的现象几乎全部发生在第一步./configure阶段。因为make阶段做的是纯粹的编译和链接工作,不涉及网络请求。而configure阶段要做环境探测,某些探测逻辑会尝试访问网络。
这里有个容易混淆的点:用户描述的"make 时一直在 checking",严格来说不准确。make命令本身不会输出checking字样,那些checking for xxx...的输出全部来自configure脚本。所以当你看到终端在刷checking,说明你还在 configure 阶段,或者你用的是一条把 configure 和 make 串起来的命令(比如./configure && make),实际卡住的是前半段。
2.2 configure 脚本的网络探测逻辑
configure脚本由 autoconf 工具生成,本质上是一个超长的 shell 脚本。它在探测环境时,会执行大量的测试用例。大部分测试是本地操作,比如:
- 检查
gcc能不能编译一个最简单的 C 程序 - 检查
stdio.h头文件是否存在 - 检查某个函数的签名是否匹配
但有一类测试会触发网络访问,常见的有这几种:
第一种是检查依赖库的在线版本。某些项目的 configure 脚本会尝试连接软件包仓库,确认依赖库的版本是否满足要求。这种设计在开发者本机可能没问题,但在内网环境或者网络受限的环境下就会卡住。
第二种是下载辅助脚本或数据文件。一些项目在 configure 阶段需要下载额外的构建工具或配置文件,如果下载源不可达,脚本就会一直等待超时。
第三种是 DNS 解析阻塞。即使 configure 脚本本身没有显式的网络请求,它调用的某些工具(比如某些版本的 pkg-config)可能会触发 DNS 查询。如果 DNS 服务器配置不当,每次查询都要等好几秒才超时,累积起来就非常可观。
2.3 为什么"等一等"解决不了问题
很多人遇到 checking 卡住,第一反应是"再等等,可能机器慢"。这个思路在大多数情况下是错的。本地编译检查再慢,也就是毫秒到秒级别的差异。如果你等了一分钟以上还没有任何新输出,那基本可以确定是网络阻塞,而不是计算慢。
网络阻塞的特点是:进程处于不可中断的等待状态,不会消耗 CPU,也不会产生新的输出。你可以用top命令观察,如果编译进程的 CPU 占用率接近 0%,同时终端没有任何新行输出,那几乎可以确定是卡在网络请求上了。
注意:有些 configure 脚本设置了很长的超时时间,比如 120 秒甚至 300 秒。这意味着你可能要等好几分钟才能看到它跳过这个检查。与其干等,不如主动干预。
3. 定位卡点的实操方法:别猜,用工具看
3.1 用 strace 精准定位阻塞位置
遇到 checking 卡住,最有效的排查工具是strace。它能跟踪进程的所有系统调用,让你清楚看到进程到底卡在哪个操作上。
操作步骤很简单。首先找到卡住的进程 PID:
ps aux | grep configure或者如果你是在另一个终端里操作,可以直接用:
pgrep -f configure拿到 PID 之后,用 strace 附加到该进程上:
sudo strace -p <PID> -f -e trace=network参数说明:-p指定进程号,-f跟踪子进程,-e trace=network只显示网络相关的系统调用。如果进程卡在网络请求上,你会看到类似这样的输出:
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("xxx.xxx.xxx.xxx")}, 16) = -1 EINPROGRESS (Operation now in progress)这就说明进程正在尝试连接某个外部主机的 80 端口,但连接没有成功返回。看到这个输出,问题就确认了:configure 脚本在等一个网络连接,而这个连接在当前环境下无法建立。
如果你没有安装 strace,可以先装一下:
sudo apt install strace3.2 用 ltrace 查看库函数调用
strace 看的是系统调用层面,如果你想看更上层的信息,比如进程在调用哪个库函数,可以用ltrace:
sudo ltrace -p <PID>ltrace 能显示进程调用的动态库函数,比如getaddrinfo、connect等。如果看到进程反复调用getaddrinfo并且每次都返回失败,那说明是 DNS 解析出了问题。
3.3 用 timeout 命令给 configure 加超时保护
与其等 configure 自己超时,不如主动给它加一个时间限制。timeout命令可以做到这一点:
timeout 60 ./configure --prefix=/usr/local这条命令的意思是:给 configure 最多 60 秒的执行时间,超过就强制终止。这样你就不用干等着了。如果 60 秒内 configure 没有完成,说明确实有阻塞,你可以根据前面 strace 的结果来针对性处理。
3.4 查看 configure 脚本中的网络相关代码
如果你想知道 configure 脚本里到底哪些地方会触发网络请求,可以直接搜索脚本内容:
grep -n "wget\|curl\|http\|ftp\|download" configure这条命令会列出 configure 脚本中所有涉及网络操作的代码行。你可以根据行号定位到具体的检查逻辑,判断这个检查是否必要。
实操心得:我一般会先用
grep快速扫一遍 configure 脚本,看看有没有明显的网络请求。如果有,再决定是跳过这个检查还是配置代理。这个方法比 strace 更快,适合在编译之前就做好预判。
4. 六种解决方案:从临时绕过到彻底修复
4.1 方案一:跳过网络检查(最直接)
如果 configure 脚本中的网络检查不是必须的,最直接的办法就是跳过它。具体怎么跳过,取决于脚本的实现方式。
方法 A:设置环境变量。很多 configure 脚本会检查某个环境变量来决定是否执行网络请求。比如有些脚本会检查http_proxy变量,如果设置了就尝试通过代理访问,如果没设置就跳过。你可以尝试:
./configure --prefix=/usr/local --disable-download或者:
NO_NETWORK=1 ./configure --prefix=/usr/local具体用哪个变量名,需要看 configure 脚本的说明。你可以用./configure --help查看所有可用选项,找到和网络相关的开关。
方法 B:直接修改 configure 脚本。如果脚本没有提供跳过选项,你可以手动注释掉网络检查相关的代码。用文本编辑器打开 configure,找到触发网络请求的那几行,在前面加#注释掉。修改之前建议先备份:
cp configure configure.bak然后用vim或nano编辑。找到类似这样的代码块:
# 原始代码 if test "$cross_compiling" = no; then $as_echo "$as_me: checking for online resources" >&6 wget -q http://example.com/resource -O resource.txt fi把它改成:
# 修改后 if test "$cross_compiling" = no; then $as_echo "$as_me: skipping online resource check" >&6 # wget -q http://example.com/resource -O resource.txt fi这样脚本就会跳过网络请求,直接继续后面的检查。
注意:修改 configure 脚本后,如果后续执行了
autoreconf或autoconf命令,修改会被覆盖。所以修改后直接运行./configure和make,不要再重新生成 configure 脚本。
4.2 方案二:配置本地缓存或离线资源
如果网络检查是必须的,但当前环境无法访问外网,可以考虑提前把需要的资源下载到本地,然后让 configure 脚本从本地读取。
步骤一:在能上网的机器上下载所需资源。根据 configure 脚本的提示,确定它需要下载什么文件。通常是一个压缩包或者一个数据文件。
步骤二:把文件放到指定位置。把下载好的文件复制到源码目录下,或者复制到 configure 脚本期望的路径。
步骤三:修改 configure 脚本指向本地文件。把脚本中的下载地址改成file://协议的本地路径:
# 原始代码 wget -q http://example.com/data.tar.gz -O data.tar.gz # 修改后 cp /path/to/local/data.tar.gz ./data.tar.gz这样脚本就不需要联网了。
4.3 方案三:设置合理的超时时间
如果网络检查无法跳过,但你知道它最终会超时并继续,那可以缩短超时时间,减少等待。
很多网络工具都支持超时参数。比如wget可以用--timeout指定超时秒数,curl可以用--connect-timeout和--max-time。如果 configure 脚本里用的是这些工具,你可以通过环境变量或者修改脚本的方式缩短超时。
以 wget 为例,可以在脚本中找到 wget 调用,加上超时参数:
# 原始代码 wget -q http://example.com/resource -O resource.txt # 修改后 wget -q --timeout=5 --tries=1 http://example.com/resource -O resource.txt这样即使网络不通,5 秒后就会放弃,不会一直卡着。
4.4 方案四:使用系统包管理器替代源码编译
如果你编译这个软件的目的只是使用它,而不是修改它的源码,那最省事的方案是用 Ubuntu 的包管理器直接安装:
sudo apt install <package-name>Ubuntu 的官方仓库里有大量常用软件,安装速度快,依赖自动处理,不需要手动编译。你可以先用apt search查一下有没有现成的包:
apt search <keyword>如果仓库里有,直接安装就行,完全绕过了编译环节。
实操心得:我见过很多人明明可以用
apt install一行命令搞定的事情,非要花几个小时从源码编译。除非你需要特定版本、特定编译选项,或者需要修改源码,否则优先用包管理器。
4.5 方案五:换用国内镜像源加速下载
如果 configure 阶段的网络请求是下载依赖包,而下载速度慢是因为源站在国外,那可以换成国内镜像源。
以常见的软件源为例,Ubuntu 本身的 apt 源可以换成国内镜像:
sudo sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list sudo apt update对于 configure 脚本中调用的下载工具,如果它支持指定镜像地址,也可以通过环境变量或配置文件来切换。比如 pip 可以配置国内源:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple这样后续的下载就会走国内镜像,速度快很多。
4.6 方案六:使用 ccache 加速重复编译
虽然 ccache 不能解决 checking 卡住的问题,但它能显著加速make阶段的编译速度。如果你需要反复编译同一个项目(比如调试代码时),ccache 可以把编译结果缓存起来,下次编译时直接复用。
安装 ccache:
sudo apt install ccache然后在 configure 时指定使用 ccache:
./configure CC="ccache gcc" CXX="ccache g++"或者在~/.bashrc里添加:
export CC="ccache gcc" export CXX="ccache g++"这样每次编译时,ccache 会先检查缓存,如果之前编译过相同的代码,就直接返回缓存结果,省去重复编译的时间。
5. 常见问题速查与排查技巧
5.1 常见问题对照表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| configure 卡在 checking for gcc | 编译器未安装或路径不对 | which gcc检查 | sudo apt install build-essential |
| configure 卡在 checking for xxx library | 依赖库缺失 | `dpkg -l | grep xxx` |
| configure 卡在 checking for online resources | 网络请求阻塞 | strace -p PID -e trace=network | 跳过检查或配置本地资源 |
| configure 卡在 checking whether to build xxx | 脚本逻辑死循环 | 查看 configure 脚本对应行 | 修改脚本或换版本 |
| make 阶段报错找不到头文件 | 开发包未安装 | 查看报错信息中的头文件名 | apt install libxxx-dev |
| make 阶段编译极慢 | 单线程编译 | nproc查看核心数 | make -j$(nproc) |
| make install 权限不足 | 未使用 sudo | 查看报错信息 | sudo make install |
5.2 排查思路:从外到内逐层定位
遇到编译问题,我习惯按这个顺序排查:
第一层:确认基础工具链是否完整。运行gcc --version、make --version、pkg-config --version,确保这些基础工具都装了。Ubuntu 上一条命令搞定:
sudo apt install build-essential pkg-config第二层:确认依赖库是否齐全。查看项目文档中的依赖列表,逐个确认是否安装。可以用dpkg -l检查已安装的包,用apt-cache search查找可用的包。
第三层:确认网络环境是否正常。如果前面两层都没问题,但 configure 还是卡住,那大概率是网络问题。用 strace 确认阻塞位置,然后按第 4 节的方案处理。
第四层:确认 configure 脚本本身是否有 bug。极少数情况下,configure 脚本本身存在逻辑错误,导致在某些环境下死循环。这种情况可以尝试换一个版本的源码,或者去项目的 issue 列表里搜索是否有类似问题。
5.3 独家避坑技巧
技巧一:编译前先读 README 和 INSTALL 文件。很多项目会在这些文件里说明编译依赖和特殊配置,提前读一遍能避免很多弯路。
技巧二:用script命令记录编译过程。编译出错时,完整的日志非常重要。用script命令可以把终端输出保存到文件:
script build.log ./configure && make exit这样所有输出都会记录在build.log里,方便事后排查。
技巧三:在 screen 或 tmux 里编译。编译大项目可能耗时很长,如果 SSH 连接断开,编译进程会被终止。用 screen 或 tmux 可以保持会话:
tmux new -s build ./configure && make # 按 Ctrl+B 然后按 D 脱离会话 # 下次用 tmux attach -t build 重新连接技巧四:用make -j并行编译。现代机器通常有多个 CPU 核心,用-j参数可以并行编译,大幅缩短时间:
make -j$(nproc)$(nproc)会自动获取 CPU 核心数。不过要注意,并行编译有时会因为依赖顺序问题导致报错,如果遇到奇怪的编译错误,可以试试单线程编译确认是否是并行导致的问题。
技巧五:保留 configure 的缓存文件。configure 脚本会生成一个config.cache文件,记录上次检查的结果。如果你需要多次运行 configure,保留这个文件可以跳过重复检查,加快速度。不过如果环境发生了变化,建议删掉缓存重新检查:
rm config.cache ./configure6. 一个完整的实操案例
6.1 场景描述
假设你在一台 Ubuntu 机器上编译一个名为libimageproc的图像处理库。这台机器可以访问内网,但无法直接访问外网。你执行了以下命令:
tar -xzf libimageproc-2.1.0.tar.gz cd libimageproc-2.1.0 ./configure --prefix=/usr/local终端输出停在:
checking for gcc... gcc checking whether the C compiler works... yes checking for online model data...然后就一直卡在这里,等了十分钟没有任何新输出。
6.2 排查过程
第一步:确认进程状态。
ps aux | grep configure输出显示 configure 进程存在,CPU 占用率 0.0%,状态为S(睡眠)。
第二步:用 strace 定位阻塞点。
sudo strace -p <PID> -e trace=network输出显示:
connect(3, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("203.0.113.10")}, 16) = -1 EINPROGRESS确认进程正在尝试连接一个外部 IP 的 443 端口,但连接无法建立。
第三步:查看 configure 脚本中的相关代码。
grep -n "online model data" configure找到对应的代码行:
$as_echo "$as_me: checking for online model data" >&6 wget -q https://example.com/models/model-data.tar.gz -O model-data.tar.gz确认是 wget 在下载模型数据文件。
6.3 解决方案实施
方案选择:由于模型数据文件是编译所必需的,不能简单跳过。我们采用"本地缓存 + 修改脚本"的方案。
步骤一:在能上网的机器上下载模型数据文件。
wget https://example.com/models/model-data.tar.gz步骤二:把文件传输到编译机器上。可以用 scp 或者 U 盘拷贝,放到源码目录下。
步骤三:修改 configure 脚本,跳过下载步骤。
cp configure configure.bak vim configure找到 wget 那一行,改成:
# 原始代码 wget -q https://example.com/models/model-data.tar.gz -O model-data.tar.gz # 修改后 if [ ! -f model-data.tar.gz ]; then echo "Error: model-data.tar.gz not found" >&2 exit 1 fi步骤四:重新运行 configure。
./configure --prefix=/usr/local这次 configure 顺利通过,不再卡住。
步骤五:编译并安装。
make -j$(nproc) sudo make install6.4 经验总结
这个案例的关键在于:先定位,再解决。不要一上来就盲目尝试各种方法,而是用 strace 确认阻塞位置,然后针对性地处理。另外,修改 configure 脚本之前一定要备份,万一改错了可以快速恢复。
提示:如果你经常需要在离线环境下编译各种软件,建议搭建一个内网的软件源镜像,把常用的依赖包和资源文件都缓存到内网。这样后续编译时就不需要每次都手动处理网络问题了。
7. 预防措施:让下次编译不再卡
7.1 编译前的环境检查清单
在开始编译之前,花两分钟做以下检查,可以避免大部分问题:
- 确认
build-essential已安装:dpkg -l build-essential - 确认
pkg-config已安装:pkg-config --version - 确认网络环境:
ping -c 1 8.8.8.8(如果无法访问外网,提前做好离线准备) - 确认磁盘空间充足:
df -h - 确认源码目录权限正确:
ls -la - 阅读项目文档中的编译说明
7.2 建立本地编译环境的最佳实践
如果你经常需要从源码编译软件,建议把编译环境标准化:
统一安装基础工具链:
sudo apt install build-essential pkg-config cmake autoconf automake libtool ccache配置 ccache 加速重复编译:
echo 'export CC="ccache gcc"' >> ~/.bashrc echo 'export CXX="ccache g++"' >> ~/.bashrc source ~/.bashrc建立本地依赖库缓存目录:
mkdir -p ~/build-cache把常用的依赖库源码包和资源文件放在这个目录里,需要时直接从本地取。
使用构建脚本自动化流程:
对于经常编译的项目,可以写一个简单的构建脚本:
#!/bin/bash set -e PREFIX=/usr/local JOBS=$(nproc) echo "Configuring..." ./configure --prefix=$PREFIX echo "Building with $JOBS jobs..." make -j$JOBS echo "Installing..." sudo make install echo "Done."把脚本保存为build.sh,每次编译时直接运行bash build.sh就行。
7.3 长期维护建议
源码编译不是一次性工作,后续升级版本时还要重复整个流程。为了减少重复劳动,可以考虑:
- 把编译好的软件打包成 deb 包,方便在多台机器上部署
- 用 Docker 容器封装编译环境,确保环境一致性
- 记录每次编译的配置参数和遇到的问题,形成自己的知识库
我个人在实际操作中的体会是:编译问题的排查成本远高于预防成本。花十分钟提前检查环境,比花一个小时排查卡住的原因要划算得多。尤其是网络相关的检查,在编译之前用grep扫一遍 configure 脚本,看看有没有网络请求,能省下大量等待时间。
最后再分享一个小技巧:如果你不确定某个 configure 脚本会不会卡住,可以先用timeout 30 ./configure --help试一下。--help参数通常不会触发完整的检查流程,但如果连--help都卡住,那说明脚本本身有问题,需要直接查看脚本内容。