ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cubietruck上编译Qt 5.8.0:x64/x86/armhf三架构完整实战

Cubietruck上编译Qt 5.8.0:x64/x86/armhf三架构完整实战 编译Qt5.8.0这活儿说实话不是非干不可。手头这台Cubietruck是很多年前的板子了全志A20双核Cortex-A71GB内存当年在论坛里也算一代神板。系统装的是Armbian软件源里自带的Qt还是4.x想跑个稍微新点的Qt Quick程序都费劲。折腾了四个晚上总算是把Qt5.8.0的x64、x86、armhf三个版本都给编出来了装到板子上能跑开发机上也能用。整个过程踩了不少坑这篇文章就把编译思路、步骤和遇到的问题一次说清楚。先说一下为什么是Qt5.8.0。这个版本发布于2017年不是长时支持版但它是我印象里最后一个还比较“温和”的Qt5系列版本依赖要求没那么苛刻GCC 5和4.9都能编。后面的5.9开始对C14有更多依赖5.15更是把很多老板子卡死在门外。如果你手头也是Cubietruck、香橙派这类老ARM设备想在板上自己跑一套可用的Qt这个版本是一个非常实际的起点。这篇文章适合的人一是手里有Cubietruck或类似全志A20开发板的玩家二是需要在老旧的x86机器上装Qt的嵌入式开发者三是想搞懂“一套源码编译多个架构版本”这件事的朋友。1. 项目背景与整体设计思路拆解1.1 为什么要在Cubietruck上折腾Qt 5.8.0Cubietruck的硬件配置放到今天看依然有点意思全志A20双核Cortex-A7处理器主频1GHz板载1GB或2GB DDR3内存带千兆网口、SATA接口、HDMI输出还有板载WiFi和蓝牙。这块板子在2014年前后是NAS改造的热门选择我手上这台更是被我改装成了下载机加小型服务器常年挂在客厅角落里吃灰。问题出在软件上。Armbian的软件源里虽然能直接apt装Qt库但版本太老了很多用Qt5.2以下API写的程序都跑不起来。更要命的是编译嵌入式界面程序需要qmake、moc、uic这一套完整工具链系统里只有运行库没有对应的开发包。要么选择从源码编译一套完整的Qt要么继续忍受老旧的界面框架。对我来说前者只是花时间后者是花命所以果断选择编译。还有一点Qt 5.8.0虽然不像5.6那样是LTS但它的模块划分已经比较稳定QML引擎、Quick Controls 2、Qt WebSockets这些常用模块都处在成熟状态。更重要的是它在配置阶段允许你用非常细的开关裁剪模块这对老平台非常友好。你可以在configure时就关掉QWebEngine、关掉OpenGL甚至不要examples只保留自己需要的几个模块这样能省下成倍的时间。1.2 为什么一次编译x64、x86、armhf三个版本估计有人会问你要在Cubietruck上跑为什么还要编x64和x86两个版本说白了就是为了“开发调试在PC部署运行在板子”这条完整链路。x64版本是在我的宿主开发机上用的。Qt写的界面程序如果每次都要传到板子上跑一遍才能看效果效率实在太低。在PC上编译运行调试UI布局、测试逻辑、跑unit test这些工作全部在x64版上完成。等确认没问题了再用armhf版在板子上重新编译部署。x86版本则是给另外一台老旧的32位迷你主机准备的同时它也充当了一个“32位兼容性验证环境”。因为Qt是一个高度可移植的框架某些第三方库在32位环境下表现不一样提前用x86版本验证一下可以避免在目标平台翻车。还有一个更实际的原因。Qt的源码包是同一份配置三个不同的编译目录分别用不同的-prefix前缀安装逻辑上非常清晰。编译完x64再编译x86最后再到板子上编armhf三个版本互不干扰也不会污染同一个系统路径。这种“一份源码、三套配置、三个安装目录”的做法我后来做其他大型软件的移植时也一直在沿用真是特别好使。1.3 方案选型本机编译还是交叉编译确认要编译armhf版本后另一个问题摆到了面前是在Cubietruck板上直接编译还是在x64宿主机上做交叉编译交叉编译的优势是速度快毕竟宿主机CPU比板子强太多且磁盘空间充足。但Qt的交叉编译坑特别多标准工具链通常只提供编译器、链接器和Sysroot而Qt依赖的libxcb、libGL、libdbus等等都需要准备armhf版本的开发库稍不留神版本就对不上。就算Qt本体编好了运行时代码也容易因为依赖不一致而莫名其妙崩溃。我最后选择的是在Cubietruck板子上直接本机编译。理由很朴素板子内存虽然只有1GB但只要把swap空间扩够把编译并行度从-j4降到-j2它完全能扛住整个编译任务。而且本机编译出来的库天然适配当前的系统环境不需要操心肌构、交叉工具链这些事。代价是时间armhf版编译用了大概5个小时放在夜里挂机跑第二天早上起来就好了完全能接受。如果你非要走交叉编译我的建议是先把交叉工具链和Sysroot里的所有Qt依赖包都准备好然后使用-xplatform linux-arm-gnueabi-g这个参数并且要有心理准备去处理比本机编译多三倍的报错。这个问题后面我还会细说。2. 环境准备与依赖安装2.1 宿主机与开发板系统情况先说宿主机。我用的是一台跑Ubuntu 16.04 LTS的x86_64机器内核4.4系统自带GCC 5.4磁盘剩余空间大概50GB。这个配置编译Qt没有任何压力但有一点要提醒Ubuntu 18.04之后默认GCC版本太高7.0以上编译Qt5.8.0会遇到很多C11标准相关的问题不是不能解决但会多花很多精力。所以我建议尽量在GCC 5.x或者4.9的环境下操作。Cubietruck那边刷的是Armbian 5.35基于Ubuntu 16.04 armhf内核4.11。板子上预装了gcc 5.4和build-essential这点很关键。很多新手上来就在刷好的系统里直接跑configure结果提示找不到make、g其实就是基础开发包没装。先执行一遍sudo apt-get install build-essential不会有错。磁盘空间方面也要提前算清楚。Qt 5.8.0源码压缩包大概300MB解压后约2.8GB。每个架构编译时编译目录里会生成几千个目标文件和临时文件体积少说5GB多则10GB。x64、x86、armhf三套下来再加上三个安装目录怎么也要准备20GB以上余量。Cubietruck用的是16GB TF卡装上系统后剩下12GB多编译时我一度担心空间不够最后清理了一些缓存才算勉强够用。2.2 安装编译依赖在宿主机上我按下面这个列表安装了Qt编译的必要依赖sudo apt-get update sudo apt-get install build-essential perl python git sudo apt-get install libglib2.0-dev libfontconfig1-dev libfreetype6-dev sudo apt-get install libx11-dev libxext-dev libxfixes-dev libxrender-dev sudo apt-get install libxcb1-dev libx11-xcb-dev libxcb-glx0-dev sudo apt-get install libxcb-util0-dev libssl-dev libdbus-1-dev sudo apt-get install libexpat1-dev libudev-dev这些依赖不是拍脑袋列的。libxcb是Qt在X11下所有窗口操作的底层基础没有它Qt程序连窗口都创建不出来libxkbcommon负责键盘映射缺少它会导致按键事件失灵libglib2.0是底层事件循环辅助库虽然Qt默认不会强制依赖但某些平台插件会用到libssl则关系到Qt SSL模块是否能正常加载。这里有个容易忽略的点apt的包名在不同版本下会有差异。比如Ubuntu 20.04上libxcb-util0-dev的名字改成了libxcb-util-dev。如果你在configure阶段遇到“basic XLib functionality test failed”或者“The xcb library may be missing”的提示不要慌大概率就是某个xcb相关的开发包没有装全去软件源里搜一下libxcb再补装上就行。Cubietruck板子上的依赖和宿主机类似但少一些桌面相关的包。因为我最终是打算用linuxfb或offscreen平台插件跑界面不需要完整的X11环境所以只装了libglib2.0-dev libfontconfig1-dev libfreetype6-dev libdbus-1-dev libudev-dev这几个。2.3 预留足够的内存与Swap空间这个可能排在所有准备工作里最重要。Qt编译时的内存占用往往超出预期尤其是链接QtWebEngine这类大型模块时单个链接进程就可能吃掉2GB以上内存。我的Cubietruck只有1GB内存编译到一半直接报错virtual memory exhausted整个make进程挂掉。解决办法很简单先创建Swap文件。sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile再把它写进/etc/fstab永久生效。加了2GB swap之后Cubietruck的内存总可用量变成3GB编译就能继续跑下去了。有一说一SD卡的I/O性能是瓶颈swap用起来非常慢但总比编译中断强。如果你有条件最好用USB移动硬盘或者直接接一块SATA盘来放编译目录速度会快很多。3. Qt源码获取与配置要点3.1 下载Qt 5.8.0源码与校验安装依赖只是热身真正需要花心思的是configure这一步。先从官网下载源码包Qt每次发布都会同时放出Qt Everywhere源码包里面包含Qt Base、Qt Declarative、Qt Multimedia等全部模块。我下载的是qt-everywhere-opensource-src-5.8.0.tar.xz。wget https://download.qt.io/archive/qt/5.8/5.8.0/qt-everywhere-opensource-src-5.8.0.tar.xz sha256sum qt-everywhere-opensource-src-5.8.0.tar.xz下载完最好核对一下校验值官网的SHA256可以查。这一步看似纯粹心理安慰但我确实遇到过下载文件不完整导致解压报错的情况尤其是用国内镜像源下载时偶尔会碰到损坏文件。解压后进入源码根目录可以看到qtbase、qtdeclarative、qtquickcontrols等子目录所有的Qt模块都放在这里。3.2 configure参数详解Qt的安装不像普通软件一样敲./configure make make install就完事它给了非常多的编译开关用好这些开关能省下一整天时间。这是我实际使用的一套x64配置./configure -prefix /opt/Qt5.8.0-x64 \ -release -opensource -confirm-license \ -shared \ -nomake examples -nomake tests \ -no-opengl -no-qml-debug \ -qt-xcb -qt-zlib -qt-libpng -qt-libjpeg \ -skip qtwebengine逐个解释-prefix指定最终安装路径。三个版本分别用/opt/Qt5.8.0-x64、/opt/Qt5.8.0-x86、/opt/Qt5.8.0-armhf从路径上就能看出是哪个架构之后用哪个版本就切换哪个PATH。-release编译发布版不带调试符号体积更小运行速度更快。如果是在开发阶段想用gdb排查崩溃建议改成-debug-and-release。-opensource -confirm-license接受LGPL等开源协议不用交互式确认方便脚本化执行。-shared生成动态库。Qt的静态编译在5.8上问题比较多没有特殊需求就选shared。-nomake examples -nomake tests不编译官方自带的示例和测试程序。这一项能节省大量时间和磁盘空间示例代码少说也有1GB而且我们根本用不到。-no-opengl这一项在Cubietruck上尤其重要。全志A20虽然支持OpenGL ES 2.0但厂商的Mali 400 GPU驱动在Linux下优化一般Qt里的OpenGL模块一旦编不过或者运行崩溃很多程序直接白屏。关掉OpenGL反而更稳。-qt-xcb强制使用Qt自带的xcb库而不是系统里的版本。这样做的好处是编译环境不挑系统换一台机器也能编出一样的库。-skip qtwebengine直接裁掉QtWebEngine模块。这个模块编译极慢而且对编译器版本要求极苛刻GCC版本不对就编不过。即便编出来了在1GB内存的板子上也根本跑不动。3.3 x64、x86、armhf三套配置差异三套配置在参数上几乎一致只有prefix和一个平台参数不同。先看总览表目标架构prefix路径编译环境预计编译时间说明x86_64/opt/Qt5.8.0-x64宿主机Ubuntu 16.04约1.5小时用于开发调试i386/opt/Qt5.8.0-x86Docker i386容器约2小时兼容32位环境armhf/opt/Qt5.8.0-armhfCubietruck本机约5小时目标运行平台x64版本直接在上面那套配置里把prefix换成/opt/Qt5.8.0-x64就行不需要额外指定-platform因为默认就是linux-g。x86版本比较特殊。如果你想在64位宿主机上直接编译32位Qt需要安装gcc-multilib和g-multilib然后额外加-platform linux-g-32。但这样有个麻烦很多第三方依赖库比如libxcb必须安装32位版本多架构混装时apt经常闹脾气依赖关系剪不断理还乱。我折腾两小时后果断放弃改用Docker容器跑一个i386/ubuntu:16.04镜像在容器里安装32位依赖后默认编译的就是x86版本那叫一个干净省事。armhf版本在Cubietruck板上编译配置时不需要额外指定-platform因为板子上跑的g生成的就是armhf代码。需要注意的是Qt编译会尝试检测一些特性例如SSE指令集在ARM平台上会直接跳过这些都是正常情况。configure结束时只要显示Qt is now configured for building就算成功了。4. 编译安装全过程与核心参数4.1 x64版本编译流程配置完成后进入编译目录执行make。我是四核机器直接用了make -j4。这里多说一句如果你不指定-j参数make默认只会用单线程编译Qt这套庞大源码可能要跑四五个小时但-j开太大又容易把内存吃满导致OOM。常规做法是让并行度等于CPU核心数或者核心数减一。make -j4编译一分钟左右就会开始刷各种g输出中间偶尔跳几个warning基本不影响。我遇到过一个报错是perl版本太老导致的Qt的翻译文件处理脚本需要perl 5.10以上Ubuntu 16.04自带5.22没压力。另一个报错是缺少libxkbcommon-devconfigure时没提示make到一半才炸出来报错信息类似Package libxkbcommon was not found in the pkg-config search path。这个时候只能回头apt安装缺失的库然后重新执行makeQt的构建系统会接着上次的进度继续不用全部重来。make顺利结束后执行sudo make install安装过程很快几分钟内把编译产物复制到/opt/Qt5.8.0-x64。安装完成后验证一下export PATH/opt/Qt5.8.0-x64/bin:$PATH qmake --version能输出QMake version 3.1和Using Qt version 5.8.0就说明x64版本已经装好了。我用一个只有普通QPushButton的小程序测试跑起来完全不卡。4.2 x86版本编译流程x86版本是在Docker容器里编的。先启动一个i386系统容器docker run -it --rm -v /opt/qt-everywhere-opensource-src-5.8.0:/src i386/ubuntu:16.04 bash进入容器后同样安装依赖库和基础工具然后把宿主机上的Qt源码目录挂载进来。在容器内cd /src ./configure -prefix /opt/Qt5.8.0-x86 \ -release -opensource -confirm-license \ -shared \ -nomake examples -nomake tests \ -no-opengl -no-qml-debug \ -qt-xcb -qt-zlib -qt-libpng -qt-libjpeg \ -skip qtwebengine make -j4 make install容器里编译时-platform参数可以不写因为i386镜像里默认就是x86架构。我在容器外本来还担心Qt脚本会不会自动检测到64位宿主然后编出x64版实测证明完全不会configure检测的是容器自身的架构。整个过程大概两小时编出来的库在宿主机的32位程序里可以正常加载。4.3 armhf版本编译流程Cubietruck本机编译这是整个过程最磨人的环节。先通过rsync把源码从宿主机同步到板子rsync -avz --progress qt-everywhere-opensource-src-5.8.0/ cubietruck192.168.1.100:/home/cubietruck/qt-build/注意源码目录里如果已经有x64编译生成的临时文件必须先清理干净。最好的办法是传到板子后用git管理源码目录或者打包后再解压否则Makefile里残留的x64规则会让armhf编译乱套。我这里直接在编译前执行make distclean回到板子上配置cd /home/cubietruck/qt-build ./configure -prefix /opt/Qt5.8.0-armhf \ -release -opensource -confirm-license \ -shared \ -nomake examples -nomake tests \ -no-opengl -no-qml-debug \ -qt-zlib -qt-libpng -qt-libjpeg \ -skip qtwebengineCubietruck只有双核CPU并行度我保守地用了-j2make -j2这一步真的考验耐心。前两次都因为swap不够导致编译进程被内核kill掉。后来把swap扩到2GB才稳定。编译期间建议用htop盯一盯内存尤其是链接阶段如果看到进程内存占用接近Mem Swap总上限建议手动调低并行度或者干脆先睡一觉让它慢慢跑。经过大约5个多小时编译终于结束。执行make install安装到/opt/Qt5.8.0-armhf。装完后验证export PATH/opt/Qt5.8.0-armhf/bin:$PATH export LD_LIBRARY_PATH/opt/Qt5.8.0-armhf/lib:$LD_LIBRARY_PATH qmake --version如果输出Using Qt version 5.8.0板子上的armhf版就大功告成。4.4 演示程序编译与平台插件选择为了确认Qt在板子上不是“能编不能跑”我写了个最基础的窗口程序测试#include QApplication #include QPushButton int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton btn(Hello Cubietruck); btn.show(); return app.exec(); }在板子上用qmake编译qmake make运行前先确认QT_QPA_PLATFORM环境变量。因为板子没跑完整的X Server默认xcb插件可能用不了可以切换到linuxfb或offscreenexport QT_QPA_PLATFORMoffscreen ./hellox64宿主机上运行这个程序时用的平台插件是xcb窗口正常弹出。在Cubietruck上如果想接HDMI显示器直接看图形界面需要配置linuxfb或者eglfs这两个插件在编译参数里是默认自带直接-platform linuxfb ./hello就能看到窗口。我用这个方式验证了板子的HDMI输出图形界面能正常渲染。5. 编译过程中的坑与排查实录5.1 GCC版本太高编译失败Qt 5.8.0的源码放在了2017年后来的GCC版本在C标准细节上越来越严格很多“能编过”的代码在新编译器下会变成硬性报错。我在Ubuntu 18.04上用默认GCC 7编译时qtbase模块直接报错出现了模板推导失败之类的问题。如果你也遇到这类问题不要硬改源码最简单的方案是换成GCC 5或者GCC 4.9。Ubuntu可以用update-alternatives切换版本。sudo apt-get install gcc-5 g-5 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 50 sudo update-alternatives --install /usr/bin/g g /usr/bin/g-5 505.2 swap不够导致编译进程被杀这个坑几乎每个在1GB内存板子上编Qt的人都会遇到。现象是shell里突然出现Killed然后进程消失没有任何编译错误提示。看内核日志dmesg | tail -50会看到Out of memory: Kill process的记录。解决思路就一个把交换空间扩充到物理内存的两倍以上。Cubietruck上我建议至少2GB swap。如果板子上有USB存储把swap放在U盘或移动硬盘上会比放在TF卡快不少毕竟编译时swap的读写频率还是比较高的。5.3 xcb平台插件加载失败在宿主机上跑Qt程序时有时候会碰到could not load the Qt platform plugin xcb的报错。这个问题的根源是Qt运行库找不到xcb插件或者插件依赖的系统库缺失。排查方法很直接先用ldd看看插件的依赖ldd /opt/Qt5.8.0-x64/plugins/platforms/libqxcb.so如果看到某个库显示not found就要补装对应的32位或64位开发包。另外Qt程序会自动查找同目录下的plugins文件夹如果你把编译好的可执行文件拷到别的机器上记得把整个Qt安装目录一起带上或者通过export QT_PLUGIN_PATH/opt/Qt5.8.0-x64/plugins指定插件路径。5.4 源码编译思路的通用性这几个月我除了编Qt还在别的Linux环境上编过subversion、openssl等乱七八糟的软件。说真的源码编译这件事套路高度一致下载源码、安装依赖、configure、make、make install剩下的全是跟具体软件较劲。之前帮朋友在麒麟v10系统上编译subversion遇到的问题跟Qt也差不多无非是缺libapr、缺sqlite找到对应的开发包装上就好了。所以只要这次把Qt编通以后再碰任何源码包你心里都会有个底报错只是暂时的先把依赖补齐把参数调对总能编过去。6. 最后再分享几个实操经验如果让我重新走一遍Cubietruck上的Qt编译流程我会做几件不一样的事。第一一开始就把swap扩到3GB。别等到virtual memory exhausted的时候再去处理那一刻的心情真的很崩溃。第二给三个版本分别建一个单独的源码复制目录而不是企图用同一个目录反复make distclean。虽然distclean能清掉大部分临时文件但偶尔残留的配置会让新架构的编译产生非常隐蔽的错误不如从一开始就物理隔离。第三编译过程中单独开一个终端窗口定期用df -h看磁盘空间。Cubietruck的TF卡空间很容易被编译中间文件占满一旦写满make进程会报一些完全看不懂的IO错误清理起来比断点续传还麻烦。还有个小技巧编完一个版本后顺手把configure时的参数存成一个shell脚本放到源码根目录比如build-x64.sh、build-armhf.sh。这样过几个月再想重新编译时不用回忆当初到底用了哪些参数直接跑脚本就行。我在做完三套版本后就是这么干的后面升级Qt版本时省了不少力气。最后想说给老开发板编译新版Qt这件事听起来很折腾但它带来的成就感也是纯粹的从零到一。Cubietruck这种老平台早就没有官方二进制更新了唯一能让它继续跑现代软件的办法就是自己动手。编译过程中学到的东西——交叉架构、模块裁剪、链接器内存占用、平台插件机制——每一项都能用在之后的嵌入式开发项目里。希望这篇记录能给你省一点时间少熬一个夜。
返回列表