踩坑!在 Ubuntu 16.04 ARM 平台运行 QML 程序
2024-12-11 22:20:59

最近拿到一个 Ubuntu 16.04 ARM 的嵌入式板子,要求能在上面跑 QML 程序。
因为通过软件仓库安装的 Qt 版本过低(5.5.1),所以要自行编译新版本的 Qt,折腾了一天踩了几个坑,记录一下。

编译 Qt

我这里选择了 Qt 5.7.1 版本,因为:

  1. 它是最后一个可以编译支持 XP 系统的版本,考虑到 Windows 平台可能有支持 XP 的需求。
  2. QtQuick.Controls2 是从 5.7 开始引入的。

安装依赖

在配置阶段就报错了,大概就是关于 Linux 显示的插件所依赖的包没有安装。

1
apt install "^libxcb.*" libx11-xcb-dev libglu1-mesa-dev libxrender-dev

方案来自 https://stackoverflow.com/a/39068500/22121934

编译错误

编译过程中仅有的一个错误

1
2
qeglfskmsegldevice.cpp:74:82: error: invalid conversion from ‘EGLDeviceEXT {aka void*}’ to ‘EGLNativeDisplayType {aka gbm_device*}’ [-fpermissive]
return static_cast<QEglFSKmsEglDeviceIntegration *>(m_integration)->eglDevice();

这个错误提示表明在将EGLDeviceEXT类型转换为EGLNativeDisplayType类型时发生了无效的转换。根据错误信息,可以看出EGLDeviceEXT实际上是void*类型,而EGLNativeDisplayTypegbm_device*类型。
查了半天在这里找到了修复补丁:https://code.qt.io/cgit/qt/qtbase.git/commit/?id=f577a01f5e8d9678d268917ca727a6e9a3e819a6

1
2
3
4
5
 EGLNativeDisplayType QEglFSKmsEglDevice::nativeDisplay() const
{
- return static_cast<QEglFSKmsEglDeviceIntegration *>(m_integration)->eglDevice();
+ return reinterpret_cast<EGLNativeDisplayType>(static_cast<QEglFSKmsEglDeviceIntegration *>(m_integration)->eglDevice());
}

配置动态库搜索路径

因为系统内已经安装了 Qt 5.5.1,我开始的想法是用第三方工具切换默认 Qt 版本,但我想错了,看我如何踩坑。。。
根据 Make qmake use qt5 by default 这里的讨论,我创建了/usr/share/qtchooser/qt571.conf文件,并写入以下内容:

1
2
3
/opt/Qt/5.7.1/bin
/opt/Qt/5.7.1/lib

注意文件末尾保持一个空行,否则最后的lib就会变为li
然后将 qtchooser 的default.conf软连接指向它即可。
输入qmake -v查看,显示的确是 5.7.1 版本,看上去是成功的。


尝试运行 QML 程序,提示 QtQuick 未安装:

1
2
QQmlApplicationEngine failed to load component
qrc:/main.qml:1 module "QtQuick" version 2.7 is not installed

但是明明在 Windows 上是可以运行的。此处又被坑了一个多小时,终于在 https://stackoverflow.com/a/30304009/22121934 这里看到了一个看似最不可能的答案,抱着试试的态度试了下,竟然不再报错了,程序能启动成功(但还有其他问题)。
原因是qtchooser工具主要用于切换不同版本的Qt编译器和工具链,如qmake、moc等,而不会直接修改Qt库的软链接。也就是说在编译程序时用的还是 Qt 5.5.1 的工具链。
其实想想也是,如果随便修改系统默认 Qt 库的软连接,那肯定会导致其他 Qt 应用都出问题。


所以正确用法是在适当的地方设置环境变量,一般有两种选择:

  1. 将新版本 Qt 的 lib 目录路径加入到LD_LIBRARY_PATH环境变量中。
  2. 编译链接时设置-Wl,-rpath参数,将搜索路径写入ELF文件中。

缺少字体

再次运行,报错:

1
2
QFontDatabase: Cannot find font directory /opt/Qt/5.7.1/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig.

看上去是和编译选项-system-freetype有关:

1
2
3
4
   -no-freetype ........ Do not compile in Freetype2 support.
-qt-freetype ........ Use the libfreetype bundled with Qt.
+ -system-freetype..... Use the libfreetype provided by the system (enabled if -fontconfig is active).
See http://www.freetype.org

这里有两种方式解决,一是重新编译 Qt,在编译前安装好libfontconfig库。二是修改环境变量QT_QPA_FONTDIR

1
export QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu

不过为了避免污染系统环境变量影响别的 Qt 程序,我选择了写在程序中:

1
2
3
#ifdef Q_OS_LINUX
qputenv("QT_QPA_FONTDIR", "/usr/share/fonts/truetype/dejavu");
#endif

软件渲染

再次运行,又报错:

1
2
QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled
Failed to create OpenGL context for format QSurfaceFormat(version 2.0, options QFlags(), depthBufferSize 24, redBufferSize -1, greenBufferSize -1, blueBufferSize -1, alphaBufferSize -1, stencilBufferSize 8, samples -1, swapBehavior 2, swapInterval 1, profile 0)

这个倒好说,因为设备没有 GPU 驱动,所以 OpenGL 没法用,不得不改为软件渲染。
软件渲染需要编译安装 Qt Quick 2D Render 模块:qtdeclarative-render2d-opensource-src-5.7.1.7z
为了使用软件渲染,也要设置环境变量:

1
qputenv("QMLSCENE_DEVICE", "softwarecontext");

相关阅读

qt-builder 图形化QT源码编译工具
What is the use of various Qt platform plugins?
Qt for Embedded Linux
QtQuick2DRenderer
QuickControls Versions