Linux 上 Qt6 的 Bazel 规则:如何复制所需的库?

Bazel rules for Qt6 on Linux: How to copy required libs?

目前,我正在进行一项支持 Qt6 via Bazel. My code can be found here 的实验。

在安装了 Bazel 和 GCC9 的 Linux 上,您可以通过以下方式测试我的 Qt6 Bazel 规则:

git clone https://github.com/Vertexwahn/rules_qt6
cd rules_qt6
bazel run --config=gcc9 //:Qt6HelloWorld # run Qt6HelloWorld binary

尝试 运行 Qt6HelloWorld 时会报告此错误:

/home/user/.cache/bazel/_bazel_$USER/196a14423fc09522ef7bd657344d1cd0/execroot/Qt6Testbed/bazel- out/k8-fastbuild/bin/Qt6HelloWorld:
error while loading shared libraries: libQt6Network.so.6: cannot open shared object file: No such file or directory

Qt6.1.0中好像有libQt6Network.solibQt6Network.so.6libQt6Network.so.6.1.0。如果我将这些文件复制到 bazel-out/k8-fastbuild/binQt6HelloWorld 二进制文件所在的位置),我仍然会收到此错误。

关于如何修复此错误的任何想法? 关于如何扩展我的规则以复制那些所需的库以使 Bazel 满意的任何想法?

这是一个迟到的回复,但我刚刚看到你的规则试图做一些非常相似的事情。

这里的主要问题似乎是在您的规则中引用了 libQt*.so,它们是指向实际库的符号链接。 Bazel 正确地创建了一个指向 runfiles 目录(在 _solib_k8 下)的符号链接,并将从主二进制文件到 _solib_k8 下的路径的相对路径放入二进制文件的 RUNPATH 中。但是,二进制文件中的 DT_NEEDED 条目引用以 .so.6 结尾的文件(可能是因为那是库的 SONAME)。在任何目录中都找不到这些,给你上面的错误。以下是一些详细信息:

$ readelf -d [...]/execroot/Qt6Testbed/bazel-out/k8-fastbuild/bin/Qt6HelloWorld
 0x0000000000000001 (NEEDED)             Shared library: [libQt6Network.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libQt6Qml.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libQt6Core.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libQt6Gui.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libQt6Widgets.so.6]
[...]
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/_solib_k8/_U@qt_U6.1.0_Ulinux_Udesktop_Ugcc_U64_S_S_Cqt_Unetwork_Ulinux_Uimport___Ulib:$ORIGIN/_solib_k8/_U@qt_U6.1.0_Ulinux_Udesktop_Ugcc_U64_S_S_Cqt_Uqml_Ulinux_Uimport___Ulib:$ORIGIN/_solib_k8/_U@qt_U6.1.0_Ulinux_Udesktop_Ugcc_U64_S_S_Cqt_Ucore_Ulinux_Uimport___Ulib:$ORIGIN/_solib_k8/_U@qt_U6.1.0_Ulinux_Udesktop_Ugcc_U64_S_S_Cqt_Ugui_Ulinux_Uimport___Ulib:$ORIGIN/_solib_k8/_U@qt_U6.1.0_Ulinux_Udesktop_Ugcc_U64_S_S_Cqt_Uwidgets_Ulinux_Uimport___Uli]
[...]

检查 RUNPATH 上第一个目录中的第一个 .so,我们得到:

readelf -d [...]/execroot/Qt6Testbed/bazel-out/k8-fastbuild/bin/_solib_k8/_U@qt_U6.1.0_Ulinux_Udesktop_Ugcc_U64_S_S_Cqt_Unetwork_Ulinux_Uimport___Ulib/libQt6Network.so

Dynamic section at offset 0x206960 contains 36 entries:
  Tag        Type                         Name/Value
[...]
 0x000000000000000e (SONAME)             Library soname: [libQt6Network.so.6]
[...]

明显的解决方法是对 cc_import 目标使用 libQt*.so.6,但是我们必须跳过 interface_library 参数,因为显然那个参数必须以 [=22= 结尾].但是,我不确定 cc_importcc_library 有什么好处,至少在 Linux 上是这样。所以我建议使用 cc_library 和简单的 glob 类似 libQt6Core.so*.

一旦修复,我们将面临下一个问题:libicui18n.so.56 至少需要一个 libQt*.so 库,但我们尚未提供。事实证明它们都需要它,所以我们可以简单地扩展我们的 glob。结果可能如下所示:

[
    cc_library(
        name = "qt_%s_linux_import" % name,
        hdrs = [],
        srcs = glob([
            "lib/lib%s.so*" % library_name,
            "lib/libicu*.so*",
        ]),
        target_compatible_with = ["@platforms//os:linux"],
    )
    for name, include_folder, library_name, _ in QT_LIBRARIES
]

最后剩下的问题是找不到Qt平台插件。这可以通过 data 提供整个 plugins 目录并设置 QT_QPA_PLATFORM_PLUGIN_PATH 来解决。我们可以将插件放入 filegroup in qt_6.1.0_linux_desktop_gcc_64.BUILD

filegroup(
    name = "plugin_files",
    srcs = glob(["plugins/**/*"]),
    visibility = ["//visibility:public"],
)

然后在 cc_binary 中将其设为 data:

cc_binary(
    name = "Qt6HelloWorld",
    srcs = ["main.cpp"],
    deps = [
        ":qt_core",
        ":qt_qml",
        ":qt_widgets",
    ],
    env = select({
        "@platforms//os:linux": {
            "QT_QPA_PLATFORM_PLUGIN_PATH": "external/qt_6.1.0_linux_desktop_gcc_64/plugins",
        },
        "@platforms//os:windows": {
            # TODO
        },
    }),
    data = select({
        "@platforms//os:linux": ["@qt_6.1.0_linux_desktop_gcc_64//:plugin_files"],
        "@platforms//os:windows": [],
    }),
)

既然我现在已经把所有这些都放在本地了,我将为你的 GitHub 回购创建一个 PR。