KDAB / cxx-qt

Safe interop between Rust and Qt
https://kdab.github.io/cxx-qt/book/
978 stars 67 forks source link

Build error when adding Rust integration tests to a cargo_without_cmake project #770

Open VelorumS opened 6 months ago

VelorumS commented 6 months ago

It's more of a Cargo problem...

If you want to add Rust integration tests (a tests/ dir alongside the src/):

cargo_without_cmake
├── build.rs
├── Cargo.toml
├── qml
│   └── main.qml
├── src
│   ├── cxxqt_object.rs
│   └── main.rs
└── tests
    └── integrationtest.rs

then there is a linker error when running cargo test:

   Compiling qml-minimal-no-cmake v0.1.0 (/home/user/programs/cargo_without_cmake)
error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/usr/lib64/rustlib/x86_64-unknown-linux-gnu/bin:/home/user/.local/bin:/usr/condabin:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/cuda/bin:/opt/cuda/nsight_compute:/opt/cuda/nsight_systems/bin:/var/lib/flatpak/exports/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/var/lib/snapd/snap/bin" VSLANG="1033" "cc" "-m64" "/tmp/rustcZHPvoe/symbols.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.19anblkgpug4hgip.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.2nutpir1uyrhrpc3.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.2rxnk7n1ew8g7xk5.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.3lwzj3hj347wpu0n.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.3o3mxpc2rq5yjl13.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.3r89k4p8v6a429e9.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.41481foc2wnchejk.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.43nsfu0dm474mbua.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.48eg3kq8iwvj2s0s.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.4mng40gdrb7ye3xl.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.4nrn7p3waraf2wvr.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.4v81w8bb51jaw2qg.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.4xvf2py8m5u0pjmy.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.4y0bjrw1fguta9zj.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.4z5vd2etofng2n86.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.91mludg9sfo3ml.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.ow3w9lwhohuj1yx.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.x1qj6ctictl0fuz.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.xd0tgzci1u4dloi.rcgu.o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb.41n1h9rzcsy4bry6.rcgu.o" "-Wl,--as-needed" "-L" "/home/user/programs/cargo_without_cmake/target/debug/deps" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out" "-L" "/home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out" "-L" "/home/user/programs/cargo_without_cmake/target/debug/build/cxx-d07c81dd81c15556/out" "-L" "/home/user/programs/cargo_without_cmake/target/debug/build/link-cplusplus-86f2ece339eddd9e/out" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/usr/lib" "-L" "/home/user/programs/cargo_without_cmake/target/debug/build/cxx-qt-lib-7248bd24a098bd16/out" "-L" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bdynamic" "-lQt6Qml" "-lQt6Network" "-lQt6Gui" "-lQt6Core" "-lGLX" "-lOpenGL" "-Wl,-Bstatic" "-Wl,--whole-archive" "-lqt-static-initializers" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "-lcxx-qt-generated" "-Wl,--no-whole-archive" "-Wl,-Bdynamic" "-lstdc++" "-Wl,-Bstatic" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libtest-6723ffdf45e0c77a.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libgetopts-00f4ddaf78f28f5d.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libunicode_width-359f1da55006986c.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_std-1c504f7c9149e3f1.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libstd-535331ff8d183b16.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-83939951b5f9ca2b.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libobject-2792585c57f4c6e4.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-f2ce8644ec2a7967.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-7fc047e246b2adb6.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-d39e4457138093ea.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-e9b762c4847a0e11.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-b52c3d668c14ddd9.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-d0385c8db45ddbee.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-a31aa09f6e1b2333.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-8ae1469e4adf77e7.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libadler-8279ae5d66c90454.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-61bb80d5e366629a.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-d733e609406251ec.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-fb65a7e225f08116.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-07f355446a788533.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-7f68b09987b30acc.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libcore-929e9257d5cc7f1d.rlib" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-3562e7ef6ead1498.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/usr/lib64/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/user/programs/cargo_without_cmake/target/debug/deps/integrationtest-6deb4ec9578819bb" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-fuse-ld=gold"
  = note: /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:417: error: undefined reference to 'cxxbridge1$MyObject$number'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:421: error: undefined reference to 'cxxbridge1$MyObject$set_number'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:425: error: undefined reference to 'cxxbridge1$MyObject$string'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:430: error: undefined reference to 'cxxbridge1$MyObject$set_string'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:456: error: undefined reference to 'cxxbridge1$MyObject$increment_number'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:460: error: undefined reference to 'cxxbridge1$MyObject$say_hi'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:465: error: undefined reference to 'cxx_qt_my_object$cxxbridge1$create_rs_my_object_rust'
          /home/user/programs/cargo_without_cmake/target/debug/build/qml-minimal-no-cmake-0864b211f8cb53d0/out/cxx-qt-gen/src/qobject.cxx.cpp:487: error: undefined reference to 'cxxbridge1$box$MyObjectRust$drop'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

error: could not compile `qml-minimal-no-cmake` (test "integrationtest") due to previous error
warning: build failed, waiting for other jobs to finish...

Test itself (integrationtest.rs):

#[cfg(test)]
mod tests {
    #[test]
    fn integration_test() -> Result<(), Box<dyn std::error::Error>> {
        Ok(())
    }
}

The goal of the integration tests is to launch the qml-minimal-no-cmake binary, simulate inputs, examine outputs. No cxx-qt dependency needed.

So the test build is somehow incompatible with the build.rs.

Is there a way to make it just work by default without putting the tests in a separate crate?

ahayzen-kdab commented 6 months ago

Interesting I can reproduce this as well. Wonder how this is supposed to work as we need the build script for the crate to work.

Does it work if you move the tests into another crate and then depend on qml-minimal-no-cmake as a crate? As i've seen that suggested as a solution in some places :thinking:

klochowicz commented 3 months ago

I've run it as well; in my setup, I don't have any integration tests depending on the bridge crate, but I happen to have both a cxx and cxx_qt bridge crates that I export as a single static library (to be able to link against it in my CMake project).

I sort of got around my issue by hiding cxx_qt bridge crate behind a feature flag, but it would be nice if there was a better option :)

LeonMatthesKDAB commented 3 months ago

I can reproduce this as well. The issue is that the build.rs script compiles our generated C++ code and links it. This is the correct behavior to build the binary. Our main.rs file uses mod cxxqt_object; to ensure the bridge is built on the Rust side as well, which generates the functions you are missing.

As the integration test only builds the public interface, cargo test doesn't include the mod cxxqt_object;, as that's only in main.rs. You can fix this by adding a lib.rs file:

mod cxxqt_object;

Which should fix cargo test.

However, I've noticed that this somehow causes issues with qrc... When I then cargo run, I get:

Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `/home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/target/debug/qml-minimal-no-cmake`
QQmlApplicationEngine failed to load component
qrc:/qt/qml/com/kdab/cxx_qt/demo/qml/main.qml: No such file or directory

And the app doesn't load the window...

That issue disappears when I remove the lib.rs file again.

kristof-mattei commented 3 months ago

I can reproduce this as well. The issue is that the build.rs script compiles our generated C++ code and links it. This is the correct behavior to build the binary. Our main.rs file uses mod cxxqt_object; to ensure the bridge is built on the Rust side as well, which generates the functions you are missing.

As the integration test only builds the public interface, cargo test doesn't include the mod cxxqt_object;, as that's only in main.rs. You can fix this by adding a lib.rs file:

mod cxxqt_object;

Which should fix cargo test.

However, I've noticed that this somehow causes issues with qrc... When I then cargo run, I get:

Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `/home/kdab/Documents/projects/3371-Rust-RnD/cxx-qt/target/debug/qml-minimal-no-cmake`
QQmlApplicationEngine failed to load component
qrc:/qt/qml/com/kdab/cxx_qt/demo/qml/main.qml: No such file or directory

And the app doesn't load the window...

That issue disappears when I remove the lib.rs file again.

I wonder if this has something to do with a missing Q_INIT_RESOURCE(). If we run the code with lib.rs it seems that from the Rust side the main function links against the lib. It's not that it is actually a recompilation of the whole code.

kristof-mattei commented 3 months ago

I created a repo with a repro: https://github.com/kristof-mattei/cxx-qt-lib-test

As-is (with src/lib.rs) it gives the following error:

  = note: init.cpp:5: error: undefined reference to 'qInitResources_qml_module_resources_qrc()'
          collect2: error: ld returned 1 exit status

which I traced to cxx-qt-generated. I tried to link it in build.rs but that doesn't help.

If you comment out shared::init_resources(); in src/main.rs:9 the error changes to:

QQmlApplicationEngine failed to load component
qrc:/qt/qml/cxx_qt_lib_test/qml/main.qml: No such file or directory

Now, if you remove src/lib.rs it works, with our without the init_resources() call.

kristof-mattei commented 3 months ago

https://github.com/kristof-mattei/cxx-qt-lib-test

The build.rs generates a single function that imports all plugins defined. Works on cargo run and cargo test

LeonMatthesKDAB commented 2 months ago

@kristof-mattei Thank you for investigating this further and providing a workaround.

@ahayzen-kdab We should try to integrate this into our build system, so that this happens automatically. I have the suspicion something like this already happens in our build system for the main binary, but potentially not in the integration tests :thinking:

kristof-mattei commented 2 months ago

@LeonMatthesKDAB the Q_IMPORT_PLUGIN string occurs in your code base. That's where I got the inspiration from, as Q_INIT_RESOURCES generates the same lines.

But there is a difference between runtime not finding the qml, which can be fixed with the import plugin hack I wrote, or the build error you get under certain conditions.

I think certain code gets thrown away in the latter condition because we're not calling it. Which is why we need that call in the tests just to make sure the linker understands the components are needed.

But that feels like a weird workaround. It's not Rust-like to have to do that. But I'm unsure how to tell the linker to retain stuff.

ahayzen-kdab commented 2 months ago

Note that unused things being thrown away by the compiler we've hit before and was solved by putting those parts of the code into their own lib which are linked with WHOLE_ARCHIVE or +whole-archive.