简介:Telegram是界面效果出色、采用Win8风格设计的开源IM,要在Windows下完整编译往往面临不少依赖坑点。这份doc笔记以VS2013环境为主线,记录了从OpenSSL、LZMA、zlib、libexif到QT5.3.1的组件准备过程,并针对openssl头文件缺失或路径写法错误、QtMultimedia/QSoundEffect找不到、Qt5Widgets.lib缺失、libeay32MT.lib无法打开等典型编译错误,逐一说明修改位置和操作方法;附录还给出QT5.3.1的编译参考流程,虽未能全量编出,但主要QT库均已产出,可配合Telegram的qt_5_3_1_patch合并使用。资源共1个doc文件,压缩包约29KB,正文按问题关键词分段罗列,查找排错十分方便。该文档已有569人学习浏览,适合需要在Windows下自行编译Telegram、或对QT静态编译感兴趣的开发者参考。
1. Telegram 编译为什么总卡在 Qt 5.3.1 上
很多人以为 Telegram 编译最棘手的是 C++ 代码里那些模板和宏,真正把整个环境拖垮的,往往是 Qt 版本与编译器组合。这里说的 Telegram,不是哪天你从官网下载的那个二进制客户端,而是指从源码编译一套可运行的桌面客户端;标题里那个“QT5.3.1 编译”才是决定成败的核心变量。Qt 5.3.1 是 2014 年发布的版本,对应 Telegram Desktop 早期那一代源码,界面用 QML 写,依赖库还没有彻底切开。如果你手欠直接拿系统里的 Qt 5.15 或 6.x 去编译那版源码,信号槽、QQuickView 和 qml 模块的接口早就变得面目全非。我按“先编译 Qt → 再编译 Telegram → 排错 → 验证打包”的顺序讲,能抄作业的命令和踩过的坑都会给出来,适合需要在 Ubuntu/Debian 上自己编译 Telegram 旧版本、或者被规定必须锁定 Qt 5.3.1 的一线从业者。
2. 编译前的准备:Qt 5.3.1 依赖与工具链选型
2.1 为什么是 Qt 5.3.1 而不是最新版
旧版 Telegram Desktop 在 Linux 侧长期使用 Qt 5.3.x 构建。源码里大量 QML 文件用到了 Qt Quick 1.x 的过时接口,例如qmlRegisterType和QQuickView::setSource这类调用。在 Qt 5.3 之后的几次大版本升级里,QML 模块做了至少两轮重构,到了 Qt 5.12,旧式导入语句会直接报 module is not installed。如果你拿新 Qt 来编译这版 Telegram,报错会非常玄学:一半时间卡在 qml 文件的导入失败,另一半时间链接时找不到QQuickView符号。
当然,锁定 Qt 5.3.1 不一定是因为源码代码老。不少企业内部要求所有业务模块保持一个公共 ABI,团队里其他通信组件全是在 Qt 5.3.1 上编译的,你一个人把 Telegram 升级到 Qt 5.15,编译是爽了,但后面和其他模块协同就会出现 C++ ABI mismatch。很多第三方库的下载与编译文档也是围绕老版本写的,比如 QScintilla 某个稳定 tag 明确写着“Support Qt 5.3”,拿到 Qt 5.9 的头文件检测下直接不认账。
所以我的选型建议是:在源码目录里看.pro或 CMakeLists 中QT += ...的模块,判断它要求的 Qt 主版本;如果手头没有必须升级的理由,优先用源码同期的 Qt。不是新 Qt 不好,而是编译过程的可控比性能提升更值钱。
2.2 从源码编译 Qt 5.3.1:configure 参数与最小命令
编译 Qt 5.3.1 对系统版本有要求。Ubuntu 16.04 自带的 GCC 5 能比较顺利编完;换成 Ubuntu 20.04 的 GCC 9,configure 阶段就可能在-qt-pcre的处理上崩掉。所以先装编译器,再确认依赖库。我一般用下面的最小命令:
# 编译 Qt 5.3.1 之前的系统依赖 sudo apt-get update sudo apt-get install -y build-essential perl python2 \ libxcb1-dev libxcb-xinerama0-dev libxcb-cursor0-dev \ libgl1-mesa-dev libssl-dev libfontconfig1-dev \ libxkbcommon-dev libx11-xcb-dev # 假设已经下载了 qt-everywhere-opensource-src-5.3.1.tar.gz tar xf qt-everywhere-opensource-src-5.3.1.tar.gz cd qt-everywhere-opensource-src-5.3.1 ./configure -prefix /opt/qt531 \ -opensource -confirm-license \ -release -shared \ -fontconfig -libpng -libjpeg \ -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg \ -opengl desktop make -j$(nproc) make install第一个apt命令解决的是 xcb、字体、OpenGL 等依赖链。-prefix /opt/qt531决定 Qt 安装路径,不建议直接覆盖系统/usr,否则后续切版本很难收场。-release -shared指发布版本且动态链接,这样调试时能单独替换库文件。-fontconfig让 Qt 使用系统字体配置,解决中文乱码和字体渲染不一致。-qt-zlib -qt-pcre -qt-libpng -qt-libjpeg使用 Qt 自带的 zlib、pcre、png、jpeg,避免和系统库版本打架。
特别提醒,configure 时写-opensource -confirm-license是为了跳过交互确认,防止构建脚本跑在无人值守的 CI 上直接卡住。make -j$(nproc)在内存小于 4GB 的机器上容易引爆 OOM,改成make -j2会慢但稳定。整个 Qt 5.3.1 编译耗时大约 40 到 90 分钟,取决于 CPU 核数。
2.3 Qt 5.3.1 安装后的环境检查
安装完成后不要急着去编 Telegram,先写一个环境脚本,避免之后每次敲目录。
cat > /opt/qt531/env.sh << 'EOF' export QT_DIR=/opt/qt531 export PATH=$QT_DIR/bin:$PATH export LD_LIBRARY_PATH=$QT_DIR/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH=$QT_DIR/lib/pkgconfig:$PKG_CONFIG_PATH EOF source /opt/qt531/env.sh qmake -v which qmake这段脚本把 qmake、动态库路径和 pkg-config 路径都指向 Qt 5.3.1。qmake -v如果输出QMake version 3.0 Using Qt version 5.3.1,说明路径生效;which qmake则是确认当前 shell 拿到的不是/usr/bin/qmake。很多后续编译错误都源于这里:qmake 指向系统里另一个 Qt 版本,.pro文件全部被解析错,报错五花八门。
接着确认平台插件有没有被编译出来:
ls $QT_DIR/plugins/platforms/ # 正常能看到 libqxcb.so如果没有libqxcb.so,说明 configure 阶段 xcb 开发库并不完整。这是一个容易被漏掉的坑:configure 可能成功,但某些特性被标记为“可选”,等到程序运行时才暴露could not load the qxcb plugin。解决方法是回到源码目录删除config.cache,安装缺失的libxcb*开发包,重新 configure 后再 make install。
还有个小习惯:我会把 configure 的完整输出存到build.log,后面真的出问题,不必重跑几个小时的 make 就能定位是哪一步缺库。Qt 的 configure 和编译错误往往在日志里才有上下文,终端滚动后很难找到第一个报错。
到这里 Qt 5.3.1 已经可用了。下一章开始处理 Telegram 源码本身的工程配置。
3. 用 Qt 5.3.1 编译 Telegram Desktop:完整步骤
3.1 拉取源码前先确认子模块版本
Telegram Desktop 源码依赖的第三方库很多,包括 liblzma、OpenSSL、yaml 等。常见做法是从官方仓库拉取源码,并同步初始化子模块。但编译旧版本时不能直接 checkout master,因为当前 master 的最低 Qt 要求早就超过 5.15,不会认 Qt 5.3.1。应当先列出远程标签,挑一个与 Qt 5.3.1 同时期的版本。
git clone --recursive <telegram源码仓库> cd tdesktop git tag --list | tail -50 git checkout v0.9.5 # 示例标签,替换成你手头能编过的版本 git submodule update --init --recursive这里没有写死一个 tag,因为不同分支需要的 Qt 版本不一样。git checkout之后必须再执行git submodule update,把子模块切换到该 tag 对应的提交。如果中途有子模块拉取失败,可以清理对应目录重新 update,但不要在新旧 tag 之间反复横跳,那会让子模块的.git状态混乱。我一般会在编译前用git submodule status确认所有子模块是否处于同一个版本线。
3.2 配置 qmake 环境并生成 Makefile
确认 Qt 5.3.1 环境变量生效后,设置QTDIR,然后进入源码目录。旧版 Telegram 的构建入口是Telegram目录下的Telegram.pro,不是顶层 CMakeLists。
export QTDIR=/opt/qt531 export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH=$QTDIR/lib/pkgconfig:${PKG_CONFIG_PATH:-} cd tdesktop/Telegram qmake -r Telegram.pro CONFIG+=release make -j$(nproc)qmake -r表示递归处理当前.pro文件包含的子工程。旧版 Telegram 把 liblzma、zlib、第三方 yaml 这些库全部编进主工程,所以make会先构建第三方库,再编译主程序,整个过程比较长。CONFIG+=release会去掉调试符号,生成文件小一些,如果你还需要调试可以换为CONFIG+=debug。
这里必须注意编译顺序:如果qmake之后你手动修改了/opt/qt531下的 Qt 库或者升级了系统某个依赖,不要直接再 make,先执行make distclean再重新 qmake。否则已生成的 Makefile 里缓存了大量绝对路径,新旧路径混在一起,链接阶段会莫名其妙报“找不到 Qt5Core.so”。
3.3 调整路径参数处理非标准 Qt 安装位置
如果系统里同时装了多个 Qt,qmake 可能挑到系统默认的 Qt 5.15。这时可以在 qmake 命令里显式追加 include 路径和库路径:
qmake -r -spec linux-g++ Telegram.pro \ CONFIG+=release \ QMAKE_CFLAGS+=-I/opt/qt531/include \ QMAKE_CXXFLAGS+=-I/opt/qt531/include \ QMAKE_LIBDIR+=/opt/qt531/lib-spec linux-g++强制使用 Qt 5.3.1 自带的 mkspec 配置,而不是编译器自动探测。QMAKE_CFLAGS和QMAKE_CXXFLAGS是追加参数,不是覆盖,所以前面环境变量里的路径不会丢失。QMAKE_LIBDIR让链接器优先搜/opt/qt531/lib。
这些参数都是在遇到“Could not find -lQt5Core”“Qt version mismatch”之类报错时用的。如果报错来自系统 Qt 的头文件,说明PATH里 qmake 的顺序没调好,需要type qmake看看它来自哪里。
3.4 识别中间层编译产物
当make执行到一半时,你会看到它依次生成.o文件和几个静态库。Telegram 的主程序可执行文件在 Telegram 目录下叫Telegram;如果设置了CONFIG+=debug则可能是Telegram_debug。编译结束后先不要急着双击运行,用ls -lh Telegram确认二进制存在。
此时如果发现某些源文件根本没有参与编译,例如history_widget.cpp没有生成对应.o,多半是.pro里SOURCES的条件分支没有匹配。旧版工程里很多源文件按linux、win32、macos分块,qmake 运行在 Linux 下只编译linux { }块内文件。解决方法是检查Telegram.pro中对应块是否有拼写错误。
4. 编译过程中最容易翻车的 5 个问题与排查方法
4.1 qmake 一执行就崩:编译器太新
现象:在 Ubuntu 20.04 下执行qmake -v正常,但进入Telegram.pro后 qmake 直接段错误,或者报g++: internal compiler error: Killed (program cc1plus)。
原因:Qt 5.3.1 的 mkspec 对 GCC 9/10 做了错误假设,GCC 默认以-std=gnu++17编译,Qt 5.3 的部分私有头文件无法通过编译阶段。有些机器的“qmake 崩溃”其实是 qmake 调用了g++ -dumpspecs时被新版 GCC 的改动击穿。
解决:安装一个老版本工具链,例如apt install gcc-4.9 g++-4.9,然后修改/opt/qt531/mkspecs/linux-g++/qmake.conf里的QMAKE_CC和QMAKE_CXX指向gcc-4.9/g++-4.9。如果不想装老 GCC,可以在 qmake 命令行追加QMAKE_CXXFLAGS+=-std=c++11,但只能解决部分编译问题,qmake 崩溃仍可能发生。
4.2 子模块版本漂移导致接口对不上
现象:链接阶段报undefined reference to 'LZMA_STREAM_END',源码里明明引用了 lzma.h。
原因:拉取源码时没有锁定子模块,用了git submodule update --init但后来子模块终点被更新,导致third_party/lzma和主工程期望的 API 版本不一致。我遇到过子模块版本领先主工程三个月,编译时所有 lzma 函数全部链接失败。
解决:切回主工程 tag 后,执行git submodule status看当前子模块提交是否有+号前缀;如果有,运行git submodule sync和git submodule update --init --recursive。多个 git 仓库不干净时,直接删除出问题的子模块目录重新 clone 往往更快。
4.3 qml 编译错误:qrc 资源路径与模块版本错配
现象:编译到qrc_qml.cpp时,RCC 报 file not found,或者运行时报module "QtQuick" version 1.0 is not installed。
原因:qrc 资源文件里记录的 qml 路径和实际目录不一致,或者 qml 子模块版本的运行时高于 Qt 5.3.1。很多团队把 qml 编译错误一律归咎于语法,其实大部分是版本错配或路径错配。
解决:用rcc -list resources.qrc查看资源清单,对照实际文件路径修正 qrc。如果确认是版本问题,把 qml 子模块回退到主工程 tag 对应的提交,不要单独升级某个 qml 控件到最新版。
4.4 链接时符号冲突:链到了系统 Qt
现象:编译到最后,动态库链接阶段报undefined reference to 'QQuickView::rootContext()',或者Qt5Core.so version Qt_5.15 not defined in file libQt5Core.so.5 with link time reference。
原因:QMAKE_LIBDIR没生效,链接器优先使用了/usr/lib/x86_64-linux-gnu/libQt5Core.so.5。系统 Qt 5.15 的符号版本比 5.3.1 新,运行时不匹配。
解决:在LD_LIBRARY_PATH中加入/opt/qt531/lib不是万能的,链接阶段还要 LDFLAGS。在 qmake 命令行追加LIBS+=-L/opt/qt531/lib,并确认QMAKE_LIBDIR位于系统库路径之前。更稳的做法是在Telegram.pro开头写QMAKE_LIBDIR = /opt/qt531/lib,强制覆盖。
4.5 make 进程被 OOM-Killed
现象:make -j8编译到某个巨型 cpp 时,终端打印 Killed,之后dmesg里出现 Out of memory。
原因:旧版 Telegram 的history.cpp等文件一个翻译单元就占用 1GB 以上内存,多线程同时编译直接打满 4GB 虚拟机。不是代码问题,是资源限额。
解决:降低并行度到make -j2;如果还不行,加ulimit -v unlimited并检查 swap 分区。另外可以只编译目标文件:make -j2 Telegram,qmake 的 Makefile 会自动跳过已经生成的.o,只处理改动部分。这个方法对内存小的机器特别有用。
5. 编译完成后的运行验证与资源打包
5.1 ldd 检查与 runtime 路径
编译出来的 Telegram 二进制在运行时需要找到 Qt 5.3.1 的.so。如果直接用./Telegram启动,大概率报error while loading shared libraries。先看:
ldd Telegram | grep -i qt如果输出中有libQt5Core.so.5 => not found,说明LD_LIBRARY_PATH没包含/opt/qt531/lib。用前面写的 env.sh 设置后再次ldd,应看到所有 qt 库都指向/opt/qt531/lib。
有一个容易被忽略的库:libicu。Qt 5.3.1 在部分 Linux 发行版上依赖 ICU,Telegram 的多语言字符串处理也要用到。如果 ldd 显示libicui18n.so未找到,安装libicu-dev并重跑ldd。这个库缺失时程序能启动但界面上的中文会变成问号。
5.2 qt.conf 固定插件目录
如果以后要把 Telegram 挪到别的机器,不能只拷一个Telegram可执行文件。Qt 程序启动时会按可执行文件所在目录去搜插件和翻译,正确做法是在可执行文件旁边放一个qt.conf:
[Paths] Plugins = plugins Translations = translations把/opt/qt531/plugins里的platforms、imageformats目录拷贝到plugins/,把/opt/qt531/translations/qt_*.qm拷到translations/。这样即使目标机器没有安装 Qt 5.3.1,只要把libQt5Core.so.5等动态库一并拷贝并设置LD_LIBRARY_PATH,程序就能跑。qt.conf的作用就是明确告诉 Qt 插件路径,否则它会回退到编译时的绝对路径。
5.3 验证登录、注册与 API 申请失败 error
编译和运行只是第一步,Telegram 客户端真正可用还要过登录验证。我习惯先跑一次注册流程来验证网络和编码。如果第一次启动连不上服务器,先检查系统时间和时区:Qt 5.3.1 的 TLS 握手对时间偏移非常敏感,证书验证失败时客户端只会显示 connecting,后台日志里却会打 Certificate verify failed。
时间正确后,再看注册逻辑。你会发现 Telegram 注册收不到验证码这种情况和编译本身无关,多半是运行环境无法连通 Telegram 服务器,或者手机号被限流。另一个常见问题是 Telegram API 申请失败 error:如果你是在内部环境自建 API 凭据,需要检查 api_id 和 api_hash 是否写入配置文件;编译出来的客户端默认读取 Telegram 目录下的ApiId.h,如果这个头文件被 qmake 跳过,运行时就会拿到默认测试包的错误提示。
我一般会在启动后打开strace -f -e trace=network ./Telegram跟踪网络调用,确认它真的连到了 Telegram 数据中心的 IP 而不是卡在本地 socket。这个方式不依赖任何第三方工具,适合排查“连接上但无响应”的场景。
5.4 把可执行文件与资源打包分享
如果要给一台没有编译环境的机器部署,常见做法是用linuxdeployqt自动收集依赖,成品是一个 AppDir。但对 Qt 5.3.1 这种老版本,linuxdeployqt的检测逻辑可能不兼容,我建议手动按 5.2 的方式组织目录:
mkdir -p tg-portable/{plugins,translations,lib} cp Telegram tg-portable/ cp -a /opt/qt531/plugins/{platforms,imageformats} tg-portable/plugins/ cp -a /opt/qt531/translations/qt_*.qm tg-portable/translations/ cp /opt/qt531/lib/libQt5*.so.5* tg-portable/lib/ echo 'export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH' >> tg-portable/run.sh chmod +x tg-portable/run.sh这里把 Qt 的动态库全部集中到一个lib目录,run.sh启动时再调整LD_LIBRARY_PATH。注意不要把/usr/lib/x86_64-linux-gnu下的libssl.so也拷进去,Telegram 二进制链接的是系统 OpenSSL,版本跨度太大会导致 TLS 握手失败,最好由目标系统自带的 OpenSSL 提供。如果目标机器还没装任何 Qt 库,只保留实际用到的 platform 插件和 translation 可以减少体积;旧版 Telegram 没用到 SQL 插件,不需要把 sqldrivers 拷过去,判断依据是ldd Telegram里是否出现Qt5Sql。
6. 进阶技巧:把整个编译好的流程固化下来
手动编译一个带 Qt 5.3.1 的 Telegram,至少要执行三十多条命令。第一次我靠历史命令一条条敲,第二次开始就写成了脚本,不然下次升级系统很容易漏掉 configure 参数。下面的脚本是一个简化版,把前几章的步骤串起来:
#!/bin/bash set -euo pipefail QT_SRC=${QT_SRC:-$HOME/downloads/qt-everywhere-opensource-src-5.3.1.tar.gz} QT_PREFIX=${QT_PREFIX:-/opt/qt531} TELEGRAM_SRC=${TELEGRAM_SRC:-$HOME/tdesktop} [ -f "$QT_SRC" ] || { echo "no qt source"; exit 1; } tar xf "$QT_SRC" -C /tmp cd /tmp/qt-everywhere-opensource-src-5.3.1 ./configure -prefix "$QT_PREFIX" -opensource -confirm-license \ -release -shared -fontconfig -qt-zlib -qt-pcre -opengl desktop make -j"$(( $(nproc) / 2 ))" make install source "$QT_PREFIX/env.sh" cd "$TELEGRAM_SRC/Telegram" make distclean || true qmake -r Telegram.pro CONFIG+=release make -j"$(( $(nproc) / 2 ))"脚本里我特意把make并行度调成nproc / 2,这是我踩过 OOM 之后的习惯,为的是让小内存机器也一遍过。还有一点:每次重编前执行make distclean,清理掉 qmake 和 Makefile 的缓存,防止旧路径串入新编译。
如果想进一步复用编译产物,可以引入ccache。在脚本开头加一句export CCACHE_DIR=/var/cache/ccache-tg,再把QMAKE_CXX="ccache g++"传给 qmake,第二次构建能省下至少三分之一时间。我现在每次改 UI 或配置文件,已经不习惯全量编译,而是直接make -j2只重编改动过的文件;无论是在 Qt Creator 还是 Eclipse 里配置构建,我都会先关掉自动全量构建,明确只编译本次改动的代码。这种老工程最容易翻车的不是业务代码,而是工具链状态不稳定。先把重复的流程脚本化,再处理偶发问题,后面就顺多了。希望帮到你。
本文还有配套的精品资源,点击获取