终结!制作完美的交叉编译Qt库
2025-12-04 09:07:46

死磕交叉Qt库有段时间了,也写了几篇文章:

这几篇文章中的区别就是挂载sysroot的方式不同,但都有一个共同的缺陷:只能编译Qt基础的库,没有编译插件,基本无法分发到Linux中运行,在Linux上Qt总是依赖各种各样的库。

为什么在Windows上无法编译插件呢?因为有道无法逾越的鸿沟:检测程序。
在Windows上执行的是configure.bat,在Linux上执行的是configure,因为系统的差异你没法用configure.bat去检测Linux环境,这样就只能编译出Qt5CoreQt5Gui等基础库,一旦程序分发到Linux上运行,就是没法启动了,比如xcb库等问题,因为根本就没编译出来。

之前我都是在Linux下编译一套满血版的Qt,Windows下编译的Qt只是起到桩的作用(Windows 仅作编译,运行靠Linux库),只编译几个基本的Qt库也够了。
但这样存在隐患:因为两边的Qt库并非使用相同的配置,这可能导致类内存布局的不同,从而引发内存访问异常的错误。而且还要在Windows和Linux下分别编译。

接着我又继续摸索解决方案,比如试图在msys2中编译,但环境不同,也根本行不通。
探索了N条路线后想到一个缝合方案,根本不需要在Windows下编译了,而是将Linux下满血版本拷贝到Windows上使用!

这样的好处是:

  1. 只用编译一次Qt,确保了ABI一致,不用担心在Windows使用时和程序运行在Linux上的异常行为。
  2. 编译发生在Linux,所以是满血版的Qt,不会“缺斤少两”。

具体操作步骤:

  1. 在Linux上编译Qt,然后将includelibplugins这三个目录打包到Windows上(不要打包符号文件,而是直接打包链接的目标文件,tar命令用h参数),这就是所有的头文件和.so文件。
  2. 编译Windows版本的Qt,因为是跨平台开发,提取bintranslations目录

这样就拼接出了一个Qt库:

1
2
3
4
5
6
7
qt
├── bin // Windows平台的二进制工具
├── include // Linux下编译的头文件
├── lib // Linux下编译的.so库
├── mkspecs
├── plugins // Linux下编译的.so库
└── translations // 多语言文件

就这么简单!

但是编译程序时会报错,因为在Qt的CMake配置中会检查工具的完整性,在lib/cmake/Qt5Core/Qt5CoreConfigExtras.cmake文件中为qmakemocrcc加上.exe后缀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (NOT TARGET Qt5::qmake)
add_executable(Qt5::qmake IMPORTED)

set(imported_location "${_qt5Core_install_prefix}/bin/qmake.exe") # 添加.exe后缀
_qt5_Core_check_file_exists(${imported_location})

set_target_properties(Qt5::qmake PROPERTIES
IMPORTED_LOCATION ${imported_location}
)
endif()

if (NOT TARGET Qt5::moc)
add_executable(Qt5::moc IMPORTED)

set(imported_location "${_qt5Core_install_prefix}/bin/moc.exe") # 添加.exe后缀
_qt5_Core_check_file_exists(${imported_location})

set_target_properties(Qt5::moc PROPERTIES
IMPORTED_LOCATION ${imported_location}
)
# For CMake automoc feature
get_target_property(QT_MOC_EXECUTABLE Qt5::moc LOCATION)
endif()

if (NOT TARGET Qt5::rcc)
add_executable(Qt5::rcc IMPORTED)

set(imported_location "${_qt5Core_install_prefix}/bin/rcc.exe") # 添加.exe后缀
_qt5_Core_check_file_exists(${imported_location})

set_target_properties(Qt5::rcc PROPERTIES
IMPORTED_LOCATION ${imported_location}
)
endif()

还有一处在lib/cmake/Qt5Widgets/Qt5WidgetsConfigExtras.cmake文件中为uic加上.exe后缀。

1
2
3
4
5
6
7
8
9
10
if (NOT TARGET Qt5::uic)
add_executable(Qt5::uic IMPORTED)

set(imported_location "${_qt5Widgets_install_prefix}/bin/uic.exe") # 添加.exe后缀
_qt5_Widgets_check_file_exists(${imported_location})

set_target_properties(Qt5::uic PROPERTIES
IMPORTED_LOCATION ${imported_location}
)
endif()

还没完,在编译过程中Qt会依赖一些Linux下的库,由于我们没有制作sysroot,所以会链接失败。
我的做法是根据编译失败信息,将信息中提示的依赖库从Linux系统中拷贝出来,放到Qt的sysroot目录下,所以最终Qt的目录结构就是这样的:

1
2
3
4
5
6
7
8
9
10
11
qt
├── bin // Windows平台的二进制工具
├── include // Linux下编译的头文件
├── lib // Linux下编译的.so库
├── mkspecs
├── plugins // Linux下编译的.so库
├── translations // 多语言文件
└── sysroot
└── usr
├── include
└── lib

但是Qt并不会从sysroot下去搜索头文件和库,所以我们在lib/cmake/Qt5Core/Qt5CoreConfigExtras.cmake文件末尾加点配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
set(_cross_sysroot "${_qt5Core_install_prefix}/sysroot")
set(_arch_triple "aarch64-linux-gnu")

list(PREPEND CMAKE_PREFIX_PATH "${_cross_sysroot}/usr")

if(TARGET Qt5::Core)
foreach(_path
"${_cross_sysroot}/usr/include"
"${_cross_sysroot}/usr/include/${_arch_triple}"
)
if(EXISTS "${_path}")
target_include_directories(Qt5::Core INTERFACE "${_path}")
endif()
endforeach()

foreach(_path
"${_cross_sysroot}/lib"
"${_cross_sysroot}/lib/${_arch_triple}"
"${_cross_sysroot}/usr/lib"
"${_cross_sysroot}/usr/lib/${_arch_triple}"
)
if(EXISTS "${_path}")
target_link_options(Qt5::Core INTERFACE "-Wl,-rpath-link,${_path}")
endif()
endforeach()
endif()

unset(_cross_sysroot)
unset(_arch_triple)

最后,lib目录下的.prl.la文件中会有Linux的路径,不过这两种文件是qmake用的,由于qmake已经被淘汰了,所以我们可以直接删除这两类文件。

至此,我们就得到了一个可以在Windows上编译,运行在Linux,又不担心配置不同而导致ABI等问题的完美的交叉Qt库!