
开篇先交代一下背景。去年手上拿到一块Cubietruck就是那款全志A20双核的开发板想在上面跑一套带界面的Qt应用做一个小型的数据展示终端。A20是Cortex-A7核心跑Debian系的armhf系统问题不大但真正折腾起来才发现我需要的还不只是armhf一个版本——开发机上要跑x64版本做日常调试还有一台老旧的32位工控机需要x86版本于是干脆把Qt 5.8.0在这三种架构下各编译了一遍前前后后花了将近一周时间。这篇东西记录的就是整个编译安装过程包括环境准备、configure参数取舍、交叉编译的坑、以及最后部署到Cubietruck上的实测情况。如果你手里也有ARM开发板或者需要在多个架构下编译同一个Qt版本这篇文章应该能帮你省掉不少弯路。1. 为什么是Cubietruck和Qt 5.8.0这个组合先说结论这种组合看起来有点老但在实际项目里非常常见。Cubietruck是2013年发布的一块全志A20开发板双核Cortex-A7、1GB内存支持SATA、千兆网口、HDMI输出。放到现在性能确实不够看但在工业控制、智能终端、数据采集这些场景里它的性价比和接口丰富程度依然能打。很多设备厂商到现在还在用这块板子或者用同款A20方案的板卡做产品原型。Qt 5.8.0是2017年初发布的一个LTS版本对嵌入式Linux的支持已经很成熟而且比后续的5.9、5.12更轻量编译时间更短运行占用的内存也更小。在Cubietruck这种只有1GB内存的板子上Qt 5.8.0的QWidget应用跑起来非常流畅而新版Qt配合QML动效反而容易卡顿。所以这个组合不是我随手挑的而是实际项目里验证过能稳定跑的搭配。那为什么要一次编译三个版本说起来有点无奈但也很现实x64版本主力开发环境是64位Ubuntu日常写代码、调逻辑、看界面效果都靠它。没有x64版本就没法快速迭代。x86版本客户现场有一台老旧的32位工控机x86架构、2GB内存被要求在上面跑同一套界面程序。Qt官方对x86的预编译包不太好找尤其是5.8.0这种中间版本干脆自己编。armhf版本这就是Cubietruck的目标版本了。板子内存小直接在板上编译Qt源码太痛苦一会儿细说所以采用交叉编译的方式在开发机上产出armhf二进制再拷贝到板子上部署。三个版本用同一套源码、同一个版本号保证了开发、测试、部署三个环节的行为一致排查问题的时候不会因为Qt版本差异引入变量。这种思路在正规的项目开发流程里非常重要。2. 编译前的准备工具链、依赖库、磁盘空间那些不起眼的坑编译Qt这种庞然大物准备工作做不好后面全是泪。我先说几个我在准备阶段踩到的坑每一个都浪费过时间。2.1 主机环境和磁盘空间的硬性要求我用的编译主机是Ubuntu 16.04 x64内存8GB磁盘剩了大概30GB。Qt 5.8.0完整编译一次x64版本大约需要8-10GB的磁盘空间源码加上构建产物armhf交叉编译因为要下载额外的sysroot空间占用更大。如果磁盘空间少于20GB建议先清理否则编到一半磁盘满了make直接报错之前几个小时的编译白费。另外编译时一定要用-j参数多线程编译但线程数别贪多。我的建议是-j$(nproc)或者稍微少一点。Qt的编译对内存的消耗不小8GB内存开8个线程编译x64版本内存占用会飙到6GB以上如果内存不够建议降到4个线程否则OOM杀进程的体验非常酸爽。2.2 基础依赖包的安装清单Qt编译需要大量系统库。我整理了一份最小依赖清单大家直接对着装sudo apt-get update sudo apt-get install -y build-essential perl python git \ libfontconfig1-dev libfreetype6-dev libx11-dev libxext-dev libxfixes-dev \ libxcb1-dev libx11-xcb-dev libxcb-glx0-dev libxcb-keysyms1-dev \ libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync-dev \ libxcb-xfixes0-dev libxcb-render-util0-dev libxcb-xinerama0-dev \ libxcb-xkb-dev libxkbcommon-dev libxkbcommon-x11-dev \ libssl-dev libglib2.0-dev libdbus-1-dev libasound2-dev libgstreamer1.0-dev \ libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dev libharfbuzz-dev这几个包关系到Qt的GUI模块、xcb插件、OpenGL支持和系统集成功能。缺了libxcb系列的包编译出来的Qt无法正常显示窗口缺了libfontconfig和libfreetype字体渲染会出各种奇怪问题。2.3 交叉编译工具链的选择armhf版本需要交叉编译工具链。针对全志A20Cortex-A7架构我推荐用gcc-arm-linux-gnueabihf工具链Ubuntu 16.04上直接安装即可sudo apt-get install -y gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf注意这里有个非常容易踩的坑工具链的版本和后面sysroot里的库版本必须匹配。如果工具链是gcc 5.x但sysroot里放的是gcc 8.x编译出来的库链接阶段会出现大量找不到符号或者ABI兼容错误。我刚开始就是随手找了一个新版本的linaro工具链结果配置阶段过了编译到qtbase模块时各种C标准库相关的错误浪费了整整一个下午排查。后来老老实实用Ubuntu官方仓库里的工具链跟系统的libc版本对齐问题迎刃而解。2.4 sysroot的构建策略交叉编译 Qt 需要一个 armhf 的根文件系统sysroot里面包含 armhf 版本的 libc、libstdc、X11 库等。这里有两种方式方式一用debootstrap直接构建一个最小化的armhf Ubuntu根文件系统。这种方式干净依赖关系可控推荐。方式二直接从Cubietruck板子上打包根文件系统。这种方式能保证跟板子的环境完全一致但容易携带很多无关文件而且无法保证开发机和板子的文件权限一致。我用的是方式一具体的debootstrap命令如下在root下执行sudo debootstrap --archarmhf xenial /opt/armhf-sysroot http://ports.ubuntu.com/ubuntu-ports/然后需要把系统库软链接处理一下因为交叉编译时链接器查找库的路径不完全是sysroot的根路径还需要给/usr/lib/arm-linux-gnueabihf创建符号链接sudo ln -s /opt/armhf-sysroot/usr/lib/arm-linux-gnueabihf /opt/armhf-sysroot/usr/lib/arm-linux-gnueabihf这一步看起来简单但实际交叉编译环境搭建里90%的问题都出在路径和软链接上。3. x64版本先行摸清Qt 5.8.0的编译脾气X64版本作为最常规的编译其实是最适合用来验证环境和摸清编译参数的。先说结论Qt 5.8.0的x64编译只要依赖装全了基本一次过唯一要注意的就是configure参数别多加没必要的模块。3.1 configure参数分析与取舍下载源码并解压后我用的configure命令是cd /path/to/qt-everywhere-opensource-src-5.8.0 ./configure \ -prefix /opt/Qt5.8.0-x64 \ -opensource -confirm-license \ -release \ -shared \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtwayland \ -skip qtconnectivity \ -skip qtserialbus \ -skip qtlocation \ -skip qtsensors \ -skip qtserialport \ -no-opengl解释一下几个关键参数的取舍-nomake examples和-nomake tests必须加。Qt自带的例程和测试代码非常庞大编译它们耗时且没有实际意义只产出库和开发头文件就够了。-skip qtwebengine这个必须跳过。QtWebEngine是捆绑Chromium内核的编译它需要下载数GB的第三方依赖内存和编译时间都不可接受。如果项目里不需要浏览器内核直接跳过。-skip qtwaylandCubietruck和工控机上都不跑Wayland省掉。-skip那些传感器、定位、总线之类的模块都是嵌入式场景里不常用的省了能加快编译速度。-no-opengl这里要特别说明。一开始我纠结要不要OpenGL支持最后决定第一轮先关掉因为后面的armhf交叉编译要支持OpenGL会很复杂而且Qt的QPainter软件渲染在A20上跑2D界面足够。如果后续确实需要GPU加速再单独编译GL模块也不迟。3.2 编译与安装命令配置通过后直接编译make -j$(nproc) sudo make installx64版本我总共编译了大约50分钟8核并行。编译完成后验证一下安装是否成功/opt/Qt5.8.0-x64/bin/qmake -v如果能正确输出版本信息说明x64编译成功。然后随便拿一个Qt自带的demo编译一下确认运行正常cd /opt/Qt5.8.0-x64/examples/widgets/analogclock /opt/Qt5.8.0-x64/bin/qmake make ./analogclock3.3 x64版本遗留的一个隐患libxcbx64编译过程中有个小插曲。Qt 5.8.0默认使用xcb作为Linux下的GUI后端但如果你系统里装了多个版本的libxcb或者libxcb的版本过低会出现Qt程序能编译但启动时报could not load the xcb plugin的问题。排查方法是export QT_DEBUG_PLUGINS1 ./analogclock然后看输出的日志里报哪个库加载失败。常见的缺库是libxcb-xinerama.so.0用ldd检查Qt的xcb插件依赖即可定位。这里给个建议如果是以deploy到其他机器为目的编译时就要特别注意-prefix指定的安装路径因为Qt在编译插件时会把这个路径硬编码到代码里部署时路径必须一致否则运行时会找不到插件。4. x86版本的编译32位环境下的依赖和链接问题x86版本的编译本质上跟x64类似但有个关键区别编译主机是64位的要产出32位的二进制必须保证所有的依赖库也都是32位的。4.1 开启32位编译支持在64位Ubuntu上编译32位程序需要先开启多架构支持sudo dpkg --add-architecture i386 sudo apt-get update然后安装32位版本的依赖库。比x64的依赖清单多了一个关键的点很多库需要同时装64位和32位两套。我刚开始偷懒只装了部分32位库结果configure阶段报缺少libxcb顺手装了一个libxcb1-dev:i386又报缺libxkbcommon这样反复实在折磨人。索性一次性把涉及到的库全装一遍sudo apt-get install -y libfontconfig1-dev:i386 libfreetype6-dev:i386 \ libx11-dev:i386 libxext-dev:i386 libxfixes-dev:i386 \ libxcb1-dev:i386 libx11-xcb-dev:i386 libxcb-glx0-dev:i386 \ libxcb-keysyms1-dev:i386 libxcb-image0-dev:i386 \ libxcb-shm0-dev:i386 libxcb-icccm4-dev:i386 \ libxcb-sync-dev:i386 libxcb-xfixes0-dev:i386 \ libxcb-render-util0-dev:i386 libxcb-xinerama0-dev:i386 \ libssl-dev:i386 libglib2.0-dev:i386 libdbus-1-dev:i386 \ libegl1-mesa-dev:i386 libgles2-mesa-dev:i386 libgl1-mesa-dev:i386 \ libxkbcommon-dev:i3864.2 需要注意的编译参数差异x86版本的configure参数和x64基本一致但有一个参数必须显式指定./configure \ -prefix /opt/Qt5.8.0-x86 \ -opensource -confirm-license \ -release -shared \ -platform linux-g-32 \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtwayland \ -skip qtconnectivity \ -no-opengl关键在-platform linux-g-32它会强制qmake使用32位的编译标志-m32。不加这个参数的话qmake默认用64位模式编出来的东西在真正的32位系统上无法运行。另外-no-opengl这个参数在x86版本上也必须保留。32位系统的OpenGL库路径和64位的不一样容易链接错既然目标机器不需要GPU加速这个模块直接关掉。4.3 编译时间和结果验证x86版本编译时间比x64还要长一些大概70分钟因为32位模式编译的时候优化效果差一点而且同样的源码要多编一遍。make install之后验证方法跟x64一样。还有一个细节在64位主机上验证32位的Qt程序需要确保系统里有32位的运行时库。我之前编译完x86版本后直接跑qmake -v报找不到共享库就是因为主机的32位ld-linux没装。用ldd看一下缺少什么然后安装对应的:i386运行时库即可。5. armhf交叉编译针对Cubietruck的实战配置重头戏来了。armhf版本的编译是整个过程中最复杂、也最容易出问题的一环。交叉编译Qt的难点不在于Qt本身的编译而在于依赖链路的完整性sysroot里缺任何一个库configure阶段就可能直接报错而configure的报错信息往往又不够直观。5.1 交叉编译环境变量配置在配置之前按惯例要把交叉编译的环境变量准备好。我写了一个环境变量脚本每次编译前source一下export CROSS_COMPILEarm-linux-gnueabihf- export CC${CROSS_COMPILE}gcc export CXX${CROSS_COMPILE}g export AR${CROSS_COMPILE}ar export LD${CROSS_COMPILE}ld export STRIP${CROSS_COMPILE}strip export SYSROOT/opt/armhf-sysroot export PKG_CONFIG_PATH/opt/armhf-sysroot/usr/lib/arm-linux-gnueabihf/pkgconfig export PKG_CONFIG_SYSROOT_DIR$SYSROOT这些变量不光影响后面的configure还会在make阶段被Qt的构建系统引用所以一定要确保指向正确。5.2 configure参数配置与坑点armhf版本的configure命令如下./configure \ -prefix /opt/Qt5.8.0-armhf \ -opensource -confirm-license \ -release -shared \ -xplatform linux-arm-gnueabihf-g \ -sysroot $SYSROOT \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtwayland \ -skip qtconnectivity \ -skip qtsensors \ -skip qtserialport \ -skip qtserialbus \ -no-opengl \ -no-feature-accessibility \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-harfbuzz \ -qt-pcre这里每个参数都有讲究-xplatform linux-arm-gnueabihf-g指定交叉编译的目标平台。qtbase里的mkspecs目录下已经预置了这个名称的配置qmake会用它来查找交叉编译器。-sysroot $SYSROOT告诉编译系统去哪个目录找armhf的库和头文件。-qt-zlib、-qt-libpng等强制使用Qt源码里自带的第三方库而不是去系统的sysroot里找。这个非常重要。交叉编译时sysroot里不一定有这些库的armhf版本即便有版本也未必兼容。直接用Qt内置的版本能省掉大量排查依赖的时间。-no-feature-accessibility关闭无障碍访问功能。这个功能在嵌入式场景里用不上关掉可以节省内存和编译时间。但注意如果你后面的应用依赖Qt的accessible功能就得留着。我实际编译过程中configure阶段最大的一个坑是找不到libicu。Qt 5.8.0在configure时默认尝试启用ICUInternational Components for Unicode支持如果sysroot里没有armhf的ICU库configure并不会直接报错而是会显示一行警告但后续编译某些模块的时候却因为缺少ICU头文件而失败。我的做法是在configure参数里显式加上-no-icu避免这个模块的干扰。-no-icu同理也不需要WebKit相关支持但既然-skip qtwebengine已包含就不影响。5.3 编译中遇到的第一号坑链接错误提示找不到libGLconfigure过程中如果提示找不到OpenGL库通常是因为我们用了-no-opengl但Qt的某些模块比如qtwayland、qt3d还是会尝试链接GL库。我的办法是既加上-no-opengl又加上-no-feature-opengl双重保险-no-opengl \ -no-feature-opengl \这个组合在5.8.0上是有效的编译完整跑完没有再报GL相关的错误。5.4 编译时间与产物确认armhf版本的源码编译在8核主机上大约需要90分钟。编译结束后检查产物file /opt/Qt5.8.0-armhf/bin/qmake输出应该是类似这样的内容/opt/Qt5.8.0-armhf/bin/qmake: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]..., not stripped看到ARM和not stripped基本可以确认交叉编译成功。6. 部署到Cubietruck移植Qt和应用程序的完整流程编译出armhf版本的Qt只是第一步真正落地还要把整个Qt运行环境部署到Cubietruck上。这里有几个必须注意的问题每个都可能导致运行时异常。6.1 整个Qt目录拷贝到板子最简单粗暴但有效的部署方式就是把整个/opt/Qt5.8.0-armhf目录拷贝到Cubietruck上对应的路径。我的板子和开发机在同一局域网直接用scp传scp -r /opt/Qt5.8.0-armhf root192.168.1.100:/opt/Cubietruck的存储如果是8GB的TF卡一个Qt占1GB多一点能接受。拷贝完在板子上验证一下动态库链接是否完整/opt/Qt5.8.0-armhf/bin/qmake -v如果报error while loading shared libraries用ldd检查缺哪个库然后把对应的armhf库文件拷到板子的/usr/lib/arm-linux-gnueabihf/目录下。6.2 设置板子上的Qt运行环境在Cubietruck上需要设置以下几个环境变量export LD_LIBRARY_PATH/opt/Qt5.8.0-armhf/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM_PLUGIN_PATH/opt/Qt5.8.0-armhf/plugins/platforms export QT_QPA_FONTDIR/usr/share/fonts/truetype export QT_QPA_PLATFORMlinuxfb最关键的是最后一行QT_QPA_PLATFORMlinuxfb。Cubietruck虽然有HDMI输出但如果不想跑完整的X Server/Wayland直接让Qt用linuxfb插件在framebuffer上画图是最轻量、最稳定的方案。linuxfb就是直接操作底层显示缓冲不依赖任何窗口系统。当然如果板子上装了Xorg并启动了图形界面也可以不设置这个变量让Qt默认用xcb插件。但实测下来Cubietruck的X Server跑起来要占几十MB内存而linuxfb模式下Qt叠加上我们的应用总共也就占100多MB内存对1GB内存的板子来说省一点是一点。6.3 在板子上编译一个测试程序部署完Qt后写一个最简单的测试程序来验证整个环境。我在板子上直接建了一个测试目录mkdir ~/test cd ~/test cat main.cpp EOF #include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello Cubietruck Qt 5.8.0); label.resize(320, 240); label.show(); return app.exec(); } EOF /opt/Qt5.8.0-armhf/bin/qmake -project /opt/Qt5.8.0-armhf/bin/qmake make ./test编译的时候有三个容易踩的坑板子的内存只有1GBmake默认单线程没问题但千万别加-j4会OOM。如果板子上的GCC版本跟开发机不一样编译时要确保用Qt安装目录里的qmake它会带上正确的编译参数。如果直接调系统qmake可能用了错的系统库。运行时如果黑屏或没有画面先把程序在命令行跑一下看有没有报错输出。常见问题是linuxfb插件没找到或者是/dev/fb0设备不可用。6.4 在Cubietruck上的性能实测我最终在板子上跑了一个稍微复杂一点的QWidget应用包含实时曲线绘制、数据表格刷新和几个下拉菜单。实测结果应用启动时间约2秒从点击运行到看到主窗口界面拖动和切换倒是流畅没用GPU纯靠CPU软渲染A20双核Cortex-A7跑到60%占用率曲线刷新频率设置为10Hz帧率在25-30fps之间够用整个应用Qt库占用内存约180MB板子还有充足余量。如果后续想提升性能可以考虑加-qt-freetype时选择打开字体缓存或者使用更轻量的QML Quick Control 2这些优化能明显降低软渲染时CPU的负担。7. 交叉编译和部署期间最头疼的几个坑这一节专门讲讲我在整个过程中遇到的、最值得记录的几个坑。这些如果没人说靠自己排查会相当费时间。7.1 configure的自动检测结果可信度有限Qt的configure阶段会执行大量自动检测但交叉编译时自动检测经常出现误判。最重要的一条经验务必阅读configure输出中的Summary部分逐行确认你需要的特性是否被正确启用或禁用。比如我第一遍交叉编译时summary里显示Xcb: no我以为是正常的后来才发现是因为sysroot里缺少xcb相关的头文件导致Qt直接放弃了xcb支持。结果编译出来的Qt能在linuxfb模式下跑但没法跑任何xcb程序。如果你需要在板子上的X Server里运行Qt程序必须确保configure输出里Xcb: yes。排查方法是回到sysroot安装对应的armhf xcb库sudo chroot /opt/armhf-sysroot apt-get install -y libxcb1-dev libx11-xcb-dev libxcb-glx0-dev ... exit然后重新跑configure确认Xcb: yes。7.2 在板子上本地编译armhf版本耗时惊人第一次我试图直接在Cubietruck上编译Qt结论是千万别这么干。A20双核处理器编Qt光是qtbase模块就需要超过8个小时加上其他模块一天一夜都未必能编完。而且编译过程中板子几乎失去响应串口终端敲个命令都要等几秒。交叉编译的效率大约是板子本地编译的5-6倍而且开发机还能多线程并行。7.3 qmake的路径硬编码问题Qt编译时很多路径会被硬编码到二进制文件里最典型的就是-prefix指定的安装路径。如果你编译时用-prefix /opt/Qt5.8.0-armhf之后部署时就必须把整个Qt安装目录放到这个路径下否则qmake生成的Makefile会指向错误的目录。这个前面提过但实际操作中还是很容易踩。我有一次图省事把Qt解压到了/home/user/qt-arm目录编译部署的时候直接拷到了板子的/opt/下结果一运行就报找不到/home/user/qt-arm/lib。没办法只能重新拷回去或者重新编译一遍。这个教训值得单独拿出来提醒一次。7.4 ldd在交叉编译场景下的失效交叉编译的二进制在开发机上直接跑ldd查看依赖会得到类似not a dynamic executable的错误。这是因为开发机的内核无法识别ARM的ELF文件。正确的查看方式是用交叉工具链的readelfarm-linux-gnueabihf-readelf -d /opt/Qt5.8.0-armhf/bin/qmake | grep NEEDED或者直接把二进制拷到板子上在板子上执行ldd。我习惯了前者毕竟在开发机上排查问题更快。7.5 字体渲染——经常被忽略的运行时问题Qt程序在Cubietruck上跑起来了但中文字体可能显示成方块或者乱码。原因是linuxfb模式下Qt使用的字体目录需要明确指定而且系统里必须有对应的字体文件。我在板子上安装了文泉驿微米黑apt-get install -y fonts-wqy-microhei然后设置环境变量QT_QPA_FONTDIR/usr/share/fonts/truetype/wqy重启程序后中文显示就正常了。这个问题在x64和x86版本上不太会遇到因为桌面系统通常自带字体配置但在嵌入式Linux上字体的坑几乎是必踩的。8. 三版本编译的最终小结和一些实用建议三个版本全部编译安装完毕最后总结一下整体耗时的分布版本编译时间-j8产物大小状态x64约50分钟约900MB成功x86约70分钟约850MB成功armhf约90分钟约1.1GB成功部署到Cubietruck后实际运行稳定说明这套编译方案是可行的。基于这次完整经历我从个人角度给出几条建议希望对后来人有用第一编译之前先把configure的说明文档./configure -h完整过一遍尤其是那些-qt-*和-no-feature-*参数。Qt的定制化选项非常灵活而configure却不会因为你漏了某个参数就报错不利于发现问题。第二在版本选择上Qt 5.8.0对老版本Linux比如Ubuntu 16.04的兼容性很好gcc 5.x编译没有问题。如果你手里的板子系统比较老用5.8.0比用新版Qt省心得多。第三交叉编译时强烈建议用debootstrap方式构建sysroot别直接在板子上打包。板子上的系统环境混杂很容易带入多余依赖而且文件权限可能有问题后面排查起来极困难。第四部署到板子之后用strip压缩一下Qt库文件的体积。我用arm-linux-gnueabihf-strip处理了lib目录下的所有共享库整体从1.1GB降到了约500MB对存储紧张的板卡来说非常实用。最后说说后续优化方向。如果对界面启动速度不满意可以考虑用Qt的预编译机制qmake的cache文件或者精简Qt只编译qtbase模块。qtbase已经包含QWidget、QPainter这些最核心的模块对纯QWidget应用来说完全够用还能再省一点空间。另一个方向是尝试打开EGLFS插件配合Mali400的GPU驱动做硬件加速A20的GPU是Mali400 MP2性能虽然一般但做界面渲染加速还是能提升不少体验的。我这次因为时间关系没有深入有兴趣的可以自己试一下到时候可以多交流。