Search code examples
linuxqtbazelbazel-rulesqt6

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


Currently, I am working on an experiment to support Qt6 via Bazel. My code can be found here.

On Linux with Bazel and GCC9 installed you can test my Qt6 Bazel rules via:

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

When trying to run Qt6HelloWorld this error gets reported:

/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

There seem to be a libQt6Network.so, libQt6Network.so.6 and libQt6Network.so.6.1.0 in Qt6.1.0. If I copy those files to bazel-out/k8-fastbuild/bin (where the Qt6HelloWorld binary resides) I still get this error.

Any ideas on how to fix this error? Any ideas on how I can extend my rules to copy over those required libs so Bazel is happy?


Solution

  • This is a late reply, but I just came across your rules trying to do something very similar.

    The main issue here seems to be that in your rules you reference libQt*.so, which are symlinks to the actual libraries. Bazel correctly creates a symlink to those in the runfiles directory (under _solib_k8) and puts the relative path from the main binary to the paths under _solib_k8 into the binary's RUNPATH. However, the DT_NEEDED entries in the binary refer to files ending in .so.6 (probably because that's the SONAME of the library). Those aren't found in any of the directories, giving you the error above. Here are some details:

    $ 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]
    [...]
    

    Examining the first .so in the first directory on the RUNPATH, we get this:

    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]
    [...]
    

    The obvious fix is to use libQt*.so.6 for the cc_import targets, but then we have to skip the interface_library argument, since apparently that one must end in .so. However, I'm not sure what benefit adds cc_import over cc_library anyways, at least on Linux. So I would advise using cc_library and simply glob something like libQt6Core.so*.

    Once that is fixed, we face the next issue: libicui18n.so.56 is required by at least one of the libQt*.so libraries, but we didn't provide that yet. Turns out it's required by all of them, so we can simply expand our glob. The result could then look like this:

    [
        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
    ]
    

    The last remaining issue is about the Qt platform plugin not being found. This can be fixed by providing the entire plugins directory via data and set QT_QPA_PLATFORM_PLUGIN_PATH. The plugins we can put into a filegroup in qt_6.1.0_linux_desktop_gcc_64.BUILD

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

    And then in the cc_binary put this as 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": [],
        }),
    )
    

    Since I have put all of that up locally now, I will create a PR for your GitHub repo.