KDAB / cxx-qt

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

CXX-Qt-build: Goal for 0.8 #1125

Open LeonMatthesKDAB opened 2 weeks ago

LeonMatthesKDAB commented 2 weeks ago

Problem Statement Currently, our build system suffers from some issues.

  1. Building a QML module uses the same cc_builder as the main build
  2. Regenerating the header files requires a full rebuild
    • Problematic for tooling like clangd that depends on up-to-date headers
  3. CMake has limited control over the build process.

Proposed solution CXX actually solves this by explicitly leaving the build configuration to the respective build system in all languages. For Cargo, CXX relies on the cc crate, for CMake it allows generating the files with corossion_add_cxxbridge. CMake then takes care of the actual building and linking.

We want to keep the advantage of our current build system approach, that deduplicates code between Cargo and CMake and allows implementing features like QML Modules for Qt5, but we also want to be prepared to give users more control when needed.

To achieve this, we aim to explicitly split the code generation and the build process itself.

The goal is that cxx_qt_build, like cxx_build just returns a pre-configured compiler, that the users are then free to do what they like with. However, as Qt extends C++, it also extends the compiler capabilities. To reflect that, we need to extend cc::Builder with our own QtBuilder (which we basically almost do).

The QtBuilder can then just be a direct wrapper of the cc::Builder, with additional features like moc, qrc, etc. We also plan to move this QtBuilder fully into qt_build_utils, so that it is reusable outside CXX-Qt, and should only be Qt-specific, not CXX-Qt specific.

This cleaner split would also open the door to a tool like cxxbridge, that can just generate the cpp and header files, which could then be driven by CMake, while reusing the existing CMake build system functions.

Example This is an example of what the new API might look like:

fn main() {
    // generate the bridge files and generate a builder that can compile them.
    let builder : qt_build_utils::QtBuild = cxx_qt_build::bridge("src/lib.rs");
    // Configure what and how to build and run the compiler.
    builder
         .qrc("../")
         .compile_qml_module(QmlModule {
            uri: "...",
            version_major: "...",
            // ...
         });
}

This makes the difference between generating the bridge files (cxx_qt_build::bridge) and configuring the build process itself (QtBuild) clearer and makes everything more reusable.

Open Questions We're not yet entirely clear about how this all plays together with initializers that are passed between crates and the export to CMake. Both of these things should ideally only happen once per crate, not per QtBuild instance, so it's unclear what exactly needs to be responsible for that.

Some ideas:

ahayzen-kdab commented 5 days ago

Idea we had in a discussion was that instead of trying to build an object file that contains all the other objects files. Instead each crate when it builds the object file with the initialisers, it calls public API in things in it's dependants object files.

This means that we do not need to merge the object files and the final object file is small as it is just referring to code that is already in the dependants preventing them from being optimised out.

So eg if crate A had a Qt module/resource object file, then crate B is built and depends on it, it would create it's own object file that has something that refers to the public API of the initialiser of the Qt module/resource in crate A.