From aaad7418761420e948d19a55d125150bceb4c488 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Mon, 17 Jun 2024 11:45:12 +0100 Subject: [PATCH 01/20] WIP: qt-build-utils: add resources to volatile workaround --- crates/qt-build-utils/src/lib.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index b8562fe13..71c5dc1bc 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -865,8 +865,29 @@ prefer :/qt/qml/{qml_uri_dirs}/ let qml_plugin_cpp_path = PathBuf::from(format!("{out_dir}/{plugin_class_name}.cpp")); let qml_plugin_init_path = PathBuf::from(format!("{out_dir}/{plugin_class_name}_init.cpp")); { + let mut declarations = Vec::default(); + let mut usages = Vec::default(); + + let mut generate_usage = |return_type: &str, function_name: &str| { + declarations.push(format!("extern {return_type} {function_name}();")); + usages.push(format!("volatile auto {function_name}_usage = &{function_name};\nQ_UNUSED({function_name}_usage);")); + }; + // This function is generated by qmltyperegistrar - let register_types_function = format!("qml_register_types_{qml_uri_underscores}"); + generate_usage("void", &format!("qml_register_types_{qml_uri_underscores}")); + generate_usage( + "int", + &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"), + ); + + if !qml_files.is_empty() { + generate_usage( + "int", + &format!("qInitResources_qmlcache_{qml_uri_underscores}"), + ); + } + let declarations = declarations.join("\n"); + let usages = usages.join("\n"); let mut qml_plugin_cpp = File::create(&qml_plugin_cpp_path).unwrap(); write!( @@ -874,7 +895,9 @@ prefer :/qt/qml/{qml_uri_dirs}/ r#" #include -extern void {register_types_function}(); +// TODO: Add missing handling for GHS (Green Hills Software compiler) that is in +// https://code.qt.io/cgit/qt/qtbase.git/plain/src/corelib/global/qtsymbolmacros.h +{declarations} class {plugin_class_name} : public QQmlEngineExtensionPlugin {{ @@ -884,8 +907,7 @@ class {plugin_class_name} : public QQmlEngineExtensionPlugin public: {plugin_class_name}(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent) {{ - volatile auto registration = &{register_types_function}; - Q_UNUSED(registration); + {usages} }} }}; From 9a05eaddf4bf4a3c7f139d430ca1da9b10c3d6b6 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Mon, 17 Jun 2024 18:00:45 +0200 Subject: [PATCH 02/20] WIP: Add cxxqt_import_qml_module to CMake This allows us to get away without whole-archive in CMake by declaring an OBJECT library instead. --- CMakeLists.txt | 1 + cmake/CxxQt.cmake | 18 +++++++++++ crates/cxx-qt-build/src/lib.rs | 47 +++++++++++++++++++++++++---- examples/qml_minimal/CMakeLists.txt | 19 ++++++++---- 4 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 cmake/CxxQt.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 10449a26f..791cdd68d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(CompilerCaching) +include(CxxQt) # Enable extra Qt definitions for all projects add_compile_definitions( diff --git a/cmake/CxxQt.cmake b/cmake/CxxQt.cmake new file mode 100644 index 000000000..7ea30b4ca --- /dev/null +++ b/cmake/CxxQt.cmake @@ -0,0 +1,18 @@ + + + +function(cxxqt_import_qml_module target) + cmake_parse_arguments(QML_MODULE "" "URI;EXPORT_DIR;SOURCE_CRATE" "" ${ARGN}) + + # Note: This needs to match the URI conversion in cxx-qt-build + string(REPLACE "." "_" plugin_name ${QML_MODULE_URI}) + + # QML plugin - init targeth + set_source_files_properties( + "${QML_MODULE_EXPORT_DIR}/plugins/${plugin_name}_plugin_init.o" + PROPERTIES GENERATED ON) + add_library(${target} OBJECT IMPORTED) + set_property(TARGET ${target} PROPERTY IMPORTED_OBJECTS + "${QML_MODULE_EXPORT_DIR}/qml_minimal/plugins/${plugin_name}_plugin_init.o") + target_link_libraries(${target} INTERFACE ${QML_MODULE_SOURCE_CRATE}) +endfunction() diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index ae7c28af1..1764671fd 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -256,12 +256,21 @@ fn generate_cxxqt_cpp_files( } /// The include directory needs to be namespaced by crate name when exporting for a C++ build system, +/// The export directory, if one was specified through the environment. +fn export_dir() -> Option { + env::var("CXXQT_EXPORT_DIR") + .ok() + .map(|export_dir| format!("{export_dir}/{}", crate_name())) +} + +/// The export directory needs to be namespaced by crate name when exporting for a C++ build system, /// but for using cargo build without a C++ build system, OUT_DIR is already namespaced by crate name. fn header_root() -> String { - match env::var("CXXQT_EXPORT_DIR") { - Ok(export_dir) => format!("{export_dir}/{}", env::var("CARGO_PKG_NAME").unwrap()), - Err(_) => env::var("OUT_DIR").unwrap(), - } + export_dir().unwrap_or_else(|| env::var("OUT_DIR").unwrap()) +} + +fn crate_name() -> String { + env::var("CARGO_PKG_NAME").unwrap() } fn panic_duplicate_file_and_qml_module( @@ -571,7 +580,7 @@ impl CxxQtBuilder { // Use a separate cc::Build for the little amount of code that needs to be linked with +whole-archive // to avoid bloating the binary. let mut cc_builder_whole_archive = cc::Build::new(); - cc_builder_whole_archive.link_lib_modifier("+whole-archive"); + // cc_builder_whole_archive.link_lib_modifier("+whole-archive"); // Ensure we are not using rustc 1.69 if let Some(version) = version_check::Version::read() { @@ -668,7 +677,33 @@ impl CxxQtBuilder { self.cc_builder .file(qml_module_registration_files.qmltyperegistrar); self.cc_builder.file(qml_module_registration_files.plugin); - cc_builder_whole_archive.file(qml_module_registration_files.plugin_init); + + let mut new_builder = cc_builder_whole_archive.clone(); + new_builder.file(&qml_module_registration_files.plugin_init); + let obj_files = new_builder.compile_intermediates(); + if let Some(export_dir) = export_dir() { + for file in obj_files { + // Note: This needs to match the URI conversion in CxxQt.cmake!!! + let uri_lowercase = qml_module.uri.replace('.', "_"); + let path = PathBuf::from(&export_dir).join("plugins"); + std::fs::create_dir_all(&path).unwrap(); + let obj_path = path.join(format!("{uri_lowercase}_plugin_init.o")); + println!( + "Copying initializers obj file: {file_path} -> {obj_path}", + file_path = file.to_string_lossy(), + obj_path = obj_path.to_string_lossy() + ); + std::fs::copy(&file, obj_path).unwrap(); + } + } else { + for file in obj_files { + println!( + "cargo::rustc-link-arg-bins={file_path}", + file_path = file.to_string_lossy() + ) + } + } + cc_builder_whole_archive.file(qml_module_registration_files.rcc); for qmlcachegen_file in qml_module_registration_files.qmlcachegen { cc_builder_whole_archive.file(qmlcachegen_file); diff --git a/examples/qml_minimal/CMakeLists.txt b/examples/qml_minimal/CMakeLists.txt index 464bebedc..da13d5a49 100644 --- a/examples/qml_minimal/CMakeLists.txt +++ b/examples/qml_minimal/CMakeLists.txt @@ -81,12 +81,13 @@ target_include_directories(${APP_NAME}_lib INTERFACE "${CXXQT_EXPORT_DIR}/${CRAT target_link_libraries(${APP_NAME}_lib INTERFACE # WHOLE_ARCHIVE is needed for the generated QML plugin to register on startup, # otherwise the linker will discard the static variables that initialize it. - "$" - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 + ${CRATE} ) + +cxxqt_import_qml_module(${CRATE}qml + URI "com.kdab.cxx_qt.demo" + EXPORT_DIR ${CXXQT_EXPORT_DIR} + SOURCE_CRATE ${CRATE}) # ANCHOR_END: book_cmake_use_corrosion # ANCHOR: book_cmake_executable @@ -94,7 +95,13 @@ target_link_libraries(${APP_NAME}_lib INTERFACE add_executable(${APP_NAME} cpp/main.cpp) # Link to the Rust library -target_link_libraries(${APP_NAME} PRIVATE ${APP_NAME}_lib) +target_link_libraries(${APP_NAME} + PRIVATE + ${CRATE}qml + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2) # If we are using a statically linked Qt then we need to import any qml plugins qt_import_qml_plugins(${APP_NAME}) From a78e677a89f2b7c9006a9995221d9b495f90c101 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Tue, 18 Jun 2024 14:22:58 +0200 Subject: [PATCH 03/20] Add cxxqt_import_crate --- cmake/CxxQt.cmake | 83 +++++++++++++++++++++++++++-- crates/cxx-qt-build/src/lib.rs | 33 ++++++++---- crates/qt-build-utils/src/lib.rs | 5 +- examples/qml_minimal/CMakeLists.txt | 54 ++++--------------- 4 files changed, 113 insertions(+), 62 deletions(-) diff --git a/cmake/CxxQt.cmake b/cmake/CxxQt.cmake index 7ea30b4ca..f9ff01278 100644 --- a/cmake/CxxQt.cmake +++ b/cmake/CxxQt.cmake @@ -1,18 +1,91 @@ +# SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# SPDX-FileContributor: Leon Matthes +# +# SPDX-License-Identifier: MIT OR Apache-2.0 +find_package(Corrosion QUIET) +if(NOT Corrosion_FOUND) + include(FetchContent) + FetchContent_Declare( + Corrosion + GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git + GIT_TAG v0.5.0 + ) + + FetchContent_MakeAvailable(Corrosion) +endif() + +function(cxxqt_import_crate) + cmake_parse_arguments(IMPORT_CRATE "" "CXXQT_EXPORT_DIR;QMAKE" "" ${ARGN}) + + corrosion_import_crate(IMPORTED_CRATES __cxxqt_imported_crates ${IMPORT_CRATE_UNPARSED_ARGUMENTS}) + + message(STATUS "Found CXX-Qt crate(s): ${__cxxqt_imported_crates}") + + if (NOT DEFINED IMPORT_CRATE_CXXQT_EXPORT_DIR) + set(IMPORT_CRATE_CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt/") + endif() + message(VERBOSE "CXX-Qt EXPORT_DIR: ${IMPORT_CRATE_CXXQT_EXPORT_DIR}") + + if (NOT DEFINED IMPORT_CRATE_QMAKE) + get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) + if (NOT QMAKE STREQUAL "QMAKE-NOTFOUND") + set(IMPORT_CRATE_QMAKE "${QMAKE}") + else() + message(FATAL_ERROR "cxxqt_import_crate: QMAKE is not defined and could not be queried from the Qt::qmake target!\nPlease use the QMAKE argument to specify the path to the qmake executable or use find_package(Qt) before calling this function.") + endif() + endif() + + foreach(CRATE ${__cxxqt_imported_crates}) + + corrosion_set_env_vars(${CRATE} + "CXXQT_EXPORT_DIR=${IMPORT_CRATE_CXXQT_EXPORT_DIR}" + "QMAKE=${IMPORT_CRATE_QMAKE}" + $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}>) + + file(MAKE_DIRECTORY "${IMPORT_CRATE_CXXQT_EXPORT_DIR}/include/${CRATE}") + target_include_directories(${CRATE} INTERFACE "${IMPORT_CRATE_CXXQT_EXPORT_DIR}/include/${CRATE}") + + set_target_properties(${CRATE} + PROPERTIES + CXXQT_EXPORT_DIR "${IMPORT_CRATE_CXXQT_EXPORT_DIR}") + endforeach() + +endfunction() function(cxxqt_import_qml_module target) - cmake_parse_arguments(QML_MODULE "" "URI;EXPORT_DIR;SOURCE_CRATE" "" ${ARGN}) + cmake_parse_arguments(QML_MODULE "" "URI;SOURCE_CRATE" "" ${ARGN}) + + if (NOT DEFINED QML_MODULE_URI) + message(FATAL_ERROR "cxxqt_import_qml_module: URI must be specified!") + endif() + + if (NOT DEFINED QML_MODULE_SOURCE_CRATE) + message(FATAL_ERROR "cxxqt_import_qml_module: SOURCE_CRATE must be specified!") + endif() + + get_target_property(QML_MODULE_EXPORT_DIR ${QML_MODULE_SOURCE_CRATE} CXXQT_EXPORT_DIR) + + if (${QML_MODULE_EXPORT_DIR} STREQUAL "QML_MODULE_EXPORT_DIR-NOTFOUND") + message(FATAL_ERROR "cxxqt_import_qml_module: SOURCE_CRATE must be a valid target that has been imported with cxxqt_import_crate!") + endif() # Note: This needs to match the URI conversion in cxx-qt-build string(REPLACE "." "_" plugin_name ${QML_MODULE_URI}) + set(QML_MODULE_PLUGIN_DIR "${QML_MODULE_EXPORT_DIR}/plugins/${plugin_name}") + file(MAKE_DIRECTORY ${QML_MODULE_PLUGIN_DIR}) - # QML plugin - init targeth + # QML plugin - init target set_source_files_properties( - "${QML_MODULE_EXPORT_DIR}/plugins/${plugin_name}_plugin_init.o" + "${QML_MODULE_PLUGIN_DIR}/plugin_init.o" PROPERTIES GENERATED ON) add_library(${target} OBJECT IMPORTED) - set_property(TARGET ${target} PROPERTY IMPORTED_OBJECTS - "${QML_MODULE_EXPORT_DIR}/qml_minimal/plugins/${plugin_name}_plugin_init.o") + set_target_properties(${target} + PROPERTIES + IMPORTED_OBJECTS "${QML_MODULE_PLUGIN_DIR}/plugin_init.o") target_link_libraries(${target} INTERFACE ${QML_MODULE_SOURCE_CRATE}) + + message(VERBOSE "Expecting CXX-Qt QML plugin: ${QML_MODULE_URI} in directory: ${QML_MODULE_PLUGIN_DIR}") endfunction() diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 1764671fd..230686572 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -255,18 +255,25 @@ fn generate_cxxqt_cpp_files( generated_file_paths } -/// The include directory needs to be namespaced by crate name when exporting for a C++ build system, /// The export directory, if one was specified through the environment. +/// Note that this is not namspaced by crate. fn export_dir() -> Option { - env::var("CXXQT_EXPORT_DIR") - .ok() - .map(|export_dir| format!("{export_dir}/{}", crate_name())) + env::var("CXXQT_EXPORT_DIR").ok() +} + +fn plugin_dir(plugin_uri: &str) -> Option { + let plugin_uri = plugin_uri.replace('.', "_"); + export_dir().map(|export_dir| format!("{export_dir}/plugins/{plugin_uri}/")) } -/// The export directory needs to be namespaced by crate name when exporting for a C++ build system, +/// The include directory needs to be namespaced by crate name when exporting for a C++ build system, /// but for using cargo build without a C++ build system, OUT_DIR is already namespaced by crate name. fn header_root() -> String { - export_dir().unwrap_or_else(|| env::var("OUT_DIR").unwrap()) + format!( + "{export_dir}/include/{}", + crate_name(), + export_dir = export_dir().unwrap_or_else(|| env::var("OUT_DIR").unwrap()) + ) } fn crate_name() -> String { @@ -681,13 +688,12 @@ impl CxxQtBuilder { let mut new_builder = cc_builder_whole_archive.clone(); new_builder.file(&qml_module_registration_files.plugin_init); let obj_files = new_builder.compile_intermediates(); - if let Some(export_dir) = export_dir() { + if let Some(plugin_dir) = plugin_dir(&qml_module.uri) { for file in obj_files { // Note: This needs to match the URI conversion in CxxQt.cmake!!! - let uri_lowercase = qml_module.uri.replace('.', "_"); - let path = PathBuf::from(&export_dir).join("plugins"); + let path = PathBuf::from(&plugin_dir); std::fs::create_dir_all(&path).unwrap(); - let obj_path = path.join(format!("{uri_lowercase}_plugin_init.o")); + let obj_path = path.join("plugin_init.o"); println!( "Copying initializers obj file: {file_path} -> {obj_path}", file_path = file.to_string_lossy(), @@ -695,8 +701,13 @@ impl CxxQtBuilder { ); std::fs::copy(&file, obj_path).unwrap(); } - } else { + } else if env::var("CARGO_BIN_NAME").is_ok() { for file in obj_files { + // Only print this if we're building a bin target, as otherwise we get this + // error: + // + // error: invalid instruction `cargo::rustc-link-arg-bins` from build script of `...` + // The package ... does not have a bin target. println!( "cargo::rustc-link-arg-bins={file_path}", file_path = file.to_string_lossy() diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 71c5dc1bc..86d97016a 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -217,7 +217,10 @@ impl QtBuild { /// However, when building a Rust staticlib that gets linked to C++ code by a C++ build /// system, it is best to use the `QMAKE` environment variable to ensure that the Rust /// staticlib is linked to the same installation of Qt that the C++ build system has - /// detected. With CMake, you can get this from the `Qt::qmake` target's `IMPORTED_LOCATION` + /// detected. + /// With CMake, this will automatically be set up for you when using cxxqt_import_crate. + /// + /// Alternatively, you can get this from the `Qt::qmake` target's `IMPORTED_LOCATION` /// property, for example: /// ```cmake /// find_package(Qt6 COMPONENTS Core) diff --git a/examples/qml_minimal/CMakeLists.txt b/examples/qml_minimal/CMakeLists.txt index da13d5a49..92c5acd4f 100644 --- a/examples/qml_minimal/CMakeLists.txt +++ b/examples/qml_minimal/CMakeLists.txt @@ -33,60 +33,20 @@ endif() # ANCHOR: book_cmake_find_qmake # The path to the qmake executable path needs to be passed to the Rust # library's build script to ensure it uses the same installation of Qt as CMake. -get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) +# TODO: This has been removed, document it # ANCHOR_END: book_cmake_find_qmake # ANCHOR: book_cmake_find_corrosion -find_package(Corrosion QUIET) -if(NOT Corrosion_FOUND) - include(FetchContent) - FetchContent_Declare( - Corrosion - GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git - GIT_TAG v0.5.0 - ) - - FetchContent_MakeAvailable(Corrosion) -endif() +# TODO: Replace with fetch-content co cxx-qt-cmake # ANCHOR_END: book_cmake_find_corrosion # ANCHOR: book_cmake_use_corrosion set(CRATE qml_minimal) -# Corrosion creates a CMake target with the same name as the crate. -corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) - -# The Rust library's build script needs to be told where to output the -# generated headers so CMake can find them. To do this, tell Corrosion -# to set the CXXQT_EXPORT_DIR environment variable when calling `cargo build`. -# Also, set the QMAKE environment variable to ensure the Rust library uses -# the same installation of Qt as CMake. -set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt") -corrosion_set_env_vars(${CRATE} - "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}" - "QMAKE=${QMAKE}" - $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}> -) - -# Create an INTERFACE library target to link libraries to and add include paths. -# Linking this to both the application and the tests avoids having to setup -# the include paths and linked libraries for both of those. -add_library(${APP_NAME}_lib INTERFACE) - -# Include the headers generated by the Rust library's build script. Each -# crate gets its own subdirectory under CXXQT_EXPORT_DIR. This allows you -# to include headers generated by multiple crates without risk of one crate -# overwriting another's files. -target_include_directories(${APP_NAME}_lib INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}") - -target_link_libraries(${APP_NAME}_lib INTERFACE - # WHOLE_ARCHIVE is needed for the generated QML plugin to register on startup, - # otherwise the linker will discard the static variables that initialize it. - ${CRATE} -) +# Corrosion (through CXX-Qt) creates a CMake target with the same name as the crate. +cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) cxxqt_import_qml_module(${CRATE}qml URI "com.kdab.cxx_qt.demo" - EXPORT_DIR ${CXXQT_EXPORT_DIR} SOURCE_CRATE ${CRATE}) # ANCHOR_END: book_cmake_use_corrosion @@ -122,8 +82,12 @@ if(BUILD_TESTING) set(APP_TEST_NAME ${APP_NAME}_${TEST_NAME}_test) add_executable(${APP_TEST_NAME} tests/${TEST_NAME}/tst_${TEST_NAME}.cpp) target_link_libraries(${APP_TEST_NAME} PRIVATE - ${APP_NAME}_lib + ${CRATE}qml Qt::QuickTest + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2 ) qt_import_qml_plugins(${APP_TEST_NAME}) From 814003ca931cf96d39a10186bb310346fc278c52 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Tue, 18 Jun 2024 14:23:17 +0200 Subject: [PATCH 04/20] CMake: Use cxxqt_import_ functions everywhere --- examples/demo_threading/CMakeLists.txt | 43 ++++++------------- examples/qml_features/CMakeLists.txt | 54 +++++++++--------------- tests/basic_cxx_only/CMakeLists.txt | 20 +++------ tests/basic_cxx_qt/CMakeLists.txt | 23 +++------- tests/qt_types_standalone/CMakeLists.txt | 20 +++------ 5 files changed, 49 insertions(+), 111 deletions(-) diff --git a/examples/demo_threading/CMakeLists.txt b/examples/demo_threading/CMakeLists.txt index c8e712633..fe5f14d1b 100644 --- a/examples/demo_threading/CMakeLists.txt +++ b/examples/demo_threading/CMakeLists.txt @@ -27,38 +27,12 @@ endif() if(NOT Qt6_FOUND) find_package(Qt5 5.15 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner REQUIRED) endif() -get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) - -find_package(Corrosion QUIET) -if(NOT Corrosion_FOUND) - include(FetchContent) - FetchContent_Declare( - Corrosion - GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git - GIT_TAG v0.5.0 - ) - - FetchContent_MakeAvailable(Corrosion) -endif() set(CRATE cxx_qt_demo_threading) -corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) -set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt") -corrosion_set_env_vars(${CRATE} - "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}" - "QMAKE=${QMAKE}" - $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}> -) - -add_library(${APP_NAME}_lib INTERFACE) -target_include_directories(${APP_NAME}_lib INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}") -target_link_libraries(${APP_NAME}_lib INTERFACE - "$" - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 -) +cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) +cxxqt_import_qml_module(${CRATE}qml + URI "com.kdab.energy" + SOURCE_CRATE ${CRATE}) # Qt Graphical Effects imports changed in Qt 6 so provide proxies if(Qt5_FOUND) @@ -76,5 +50,12 @@ add_executable(${APP_NAME} images/images.qrc ${QML_COMPAT_RESOURCES} ) -target_link_libraries(${APP_NAME} PRIVATE ${APP_NAME}_lib) +target_link_libraries(${APP_NAME} + PRIVATE + ${CRATE}qml + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2) + qt_import_qml_plugins(${APP_NAME}) diff --git a/examples/qml_features/CMakeLists.txt b/examples/qml_features/CMakeLists.txt index ebdab49a4..38b03d1fd 100644 --- a/examples/qml_features/CMakeLists.txt +++ b/examples/qml_features/CMakeLists.txt @@ -27,45 +27,26 @@ endif() if(NOT Qt6_FOUND) find_package(Qt5 5.15 COMPONENTS Core Gui Qml Quick QuickControls2 QmlImportScanner QuickTest Test REQUIRED) endif() -get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) - -find_package(Corrosion QUIET) -if(NOT Corrosion_FOUND) - include(FetchContent) - FetchContent_Declare( - Corrosion - GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git - GIT_TAG v0.5.0 - ) - - FetchContent_MakeAvailable(Corrosion) -endif() set(CRATE qml_features) -corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) -set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt") -corrosion_set_env_vars(${CRATE} - "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}" - "QMAKE=${QMAKE}" - $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}> -) -add_library(${APP_NAME}_lib INTERFACE) -target_include_directories(${APP_NAME}_lib INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}") -target_link_libraries(${APP_NAME}_lib INTERFACE - "$" - Qt::Core - Qt::Gui - Qt::Qml - Qt::Quick - Qt::QuickControls2 -) +cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) +cxxqt_import_qml_module(${CRATE}qml + URI "com.kdab.cxx_qt.demo" + SOURCE_CRATE ${CRATE}) add_executable(${APP_NAME} cpp/main.cpp ) target_include_directories(${APP_NAME} PRIVATE cpp) -target_link_libraries(${APP_NAME} PRIVATE ${APP_NAME}_lib) +target_link_libraries(${APP_NAME} + PRIVATE + ${CRATE}qml + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2) + qt_import_qml_plugins(${APP_NAME}) if(BUILD_TESTING) @@ -76,8 +57,15 @@ if(BUILD_TESTING) set(APP_TEST_NAME ${APP_NAME}_test) add_executable(${APP_TEST_NAME} tests/main.cpp) target_include_directories(${APP_TEST_NAME} PRIVATE cpp) - target_link_libraries( - ${APP_TEST_NAME} PRIVATE ${APP_NAME}_lib Qt::Test Qt::QuickTest + target_link_libraries( ${APP_TEST_NAME} + PRIVATE + ${CRATE}qml + Qt::Test + Qt::QuickTest + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2 ) qt_import_qml_plugins(${APP_TEST_NAME}) diff --git a/tests/basic_cxx_only/CMakeLists.txt b/tests/basic_cxx_only/CMakeLists.txt index d89c680cb..b211a69ae 100644 --- a/tests/basic_cxx_only/CMakeLists.txt +++ b/tests/basic_cxx_only/CMakeLists.txt @@ -29,23 +29,9 @@ endif() if(NOT Qt6_FOUND) find_package(Qt5 5.15 COMPONENTS Core Gui Qml Test REQUIRED) endif() -get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) set(CRATE basic_cxx_only) -corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) -set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt") -corrosion_set_env_vars(${CRATE} - "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}" - "QMAKE=${QMAKE}" - $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}> -) -target_include_directories(${CRATE} INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}") -target_link_libraries(${CRATE} INTERFACE - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 -) +cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) add_executable(${APP_NAME} cpp/cxx_test.h @@ -55,4 +41,8 @@ target_include_directories(${APP_NAME} PRIVATE cpp) target_link_libraries(${APP_NAME} PRIVATE ${CRATE} Qt::Test + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2 ) diff --git a/tests/basic_cxx_qt/CMakeLists.txt b/tests/basic_cxx_qt/CMakeLists.txt index e144c7483..ecddc5299 100644 --- a/tests/basic_cxx_qt/CMakeLists.txt +++ b/tests/basic_cxx_qt/CMakeLists.txt @@ -29,26 +29,15 @@ endif() if(NOT Qt6_FOUND) find_package(Qt5 5.15 COMPONENTS Core Gui Qml Test REQUIRED) endif() -get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) set(CRATE basic_cxx_qt) -corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) -set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt") -corrosion_set_env_vars(${CRATE} - "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}" - "QMAKE=${QMAKE}" - $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}> -) -target_include_directories(${CRATE} INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}") -target_link_libraries(${CRATE} INTERFACE - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 -) +cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) add_executable(${APP_NAME} cpp/main.cpp) target_link_libraries(${APP_NAME} PRIVATE ${CRATE} - Qt::Test -) + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2 + Qt::Test) diff --git a/tests/qt_types_standalone/CMakeLists.txt b/tests/qt_types_standalone/CMakeLists.txt index db7b9bd70..5d82b2944 100644 --- a/tests/qt_types_standalone/CMakeLists.txt +++ b/tests/qt_types_standalone/CMakeLists.txt @@ -29,23 +29,9 @@ endif() if(NOT Qt6_FOUND) find_package(Qt5 5.15 COMPONENTS Core Gui Qml Test REQUIRED) endif() -get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) set(CRATE qt_types_standalone) -corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) -set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt") -corrosion_set_env_vars(${CRATE} - "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}" - "QMAKE=${QMAKE}" - $<$:RUSTC_WRAPPER=${CMAKE_RUSTC_WRAPPER}> -) -target_include_directories(${CRATE} INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}") -target_link_libraries(${CRATE} INTERFACE - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 -) +cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) add_executable(${APP_NAME} cpp/main.cpp @@ -94,4 +80,8 @@ target_include_directories(${APP_NAME} PRIVATE cpp) target_link_libraries(${APP_NAME} PRIVATE ${CRATE} Qt::Test + Qt::Core + Qt::Gui + Qt::Qml + Qt::QuickControls2 ) From 99f0bc0f95f37eca2e3e9d51fc58081faa60c7c5 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Tue, 18 Jun 2024 15:33:29 +0200 Subject: [PATCH 05/20] CMake: Add fake target to build required obj file Otherwise, Ninja will complain, as it needs **some** rule to build the target, even if that rule does nothing. --- cmake/CxxQt.cmake | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cmake/CxxQt.cmake b/cmake/CxxQt.cmake index f9ff01278..b5208e35b 100644 --- a/cmake/CxxQt.cmake +++ b/cmake/CxxQt.cmake @@ -67,6 +67,7 @@ function(cxxqt_import_qml_module target) endif() get_target_property(QML_MODULE_EXPORT_DIR ${QML_MODULE_SOURCE_CRATE} CXXQT_EXPORT_DIR) + get_target_property(QML_MODULE_CRATE_TYPE ${QML_MODULE_SOURCE_CRATE} TYPE) if (${QML_MODULE_EXPORT_DIR} STREQUAL "QML_MODULE_EXPORT_DIR-NOTFOUND") message(FATAL_ERROR "cxxqt_import_qml_module: SOURCE_CRATE must be a valid target that has been imported with cxxqt_import_crate!") @@ -78,9 +79,15 @@ function(cxxqt_import_qml_module target) file(MAKE_DIRECTORY ${QML_MODULE_PLUGIN_DIR}) # QML plugin - init target - set_source_files_properties( - "${QML_MODULE_PLUGIN_DIR}/plugin_init.o" - PROPERTIES GENERATED ON) + # When using the Ninja generator, we need to provide **some** way to generate the object file + # Unfortunately I'm not able to tell corrosion that this obj file is indeed a byproduct, so + # create a fake target for it. + # This target doesn't need to do anything, because the file should already exist after building the crate. + add_custom_target(${target}_mock_obj_output + COMMAND ${CMAKE_COMMAND} -E true + DEPENDS ${QML_MODULE_SOURCE_CRATE} + BYPRODUCTS "${QML_MODULE_PLUGIN_DIR}/plugin_init.o") + add_library(${target} OBJECT IMPORTED) set_target_properties(${target} PROPERTIES From 02699836c55fd3f7ea3cea310e1447fdea3f750b Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Tue, 18 Jun 2024 15:43:59 +0200 Subject: [PATCH 06/20] cxx-qt-build: Do not use -bins suffix of -link-arg This caused a build failure when not building a binary. Linking the object file in shouldn't produce issues. --- crates/cxx-qt-build/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 230686572..58e38a908 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -701,7 +701,7 @@ impl CxxQtBuilder { ); std::fs::copy(&file, obj_path).unwrap(); } - } else if env::var("CARGO_BIN_NAME").is_ok() { + } else { for file in obj_files { // Only print this if we're building a bin target, as otherwise we get this // error: @@ -709,7 +709,7 @@ impl CxxQtBuilder { // error: invalid instruction `cargo::rustc-link-arg-bins` from build script of `...` // The package ... does not have a bin target. println!( - "cargo::rustc-link-arg-bins={file_path}", + "cargo::rustc-link-arg={file_path}", file_path = file.to_string_lossy() ) } From 7d1edee4f970ecdc08bad8919671cd34bc14342d Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Tue, 18 Jun 2024 16:23:15 +0200 Subject: [PATCH 07/20] build-utils: Only reference cachegen if it exists --- crates/qt-build-utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 86d97016a..1ff39bd08 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -883,7 +883,7 @@ prefer :/qt/qml/{qml_uri_dirs}/ &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"), ); - if !qml_files.is_empty() { + if !qml_files.is_empty() && self.qmlcachegen_executable.is_some() { generate_usage( "int", &format!("qInitResources_qmlcache_{qml_uri_underscores}"), From 76f1eebd7104ad4a0278f95c2a53e353a2b6cf68 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Tue, 18 Jun 2024 16:59:50 +0200 Subject: [PATCH 08/20] Enable -x in scripts/check_cargo_build_rerun.sh And don't redirect the first build output to /dev/null This should allow us to see what exactly is failing in CI. --- scripts/check_cargo_build_rerun.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check_cargo_build_rerun.sh b/scripts/check_cargo_build_rerun.sh index 6e2d3a7da..582841694 100755 --- a/scripts/check_cargo_build_rerun.sh +++ b/scripts/check_cargo_build_rerun.sh @@ -5,7 +5,7 @@ # # SPDX-License-Identifier: MIT OR Apache-2.0 -set -e +set -ex # Ensure we are in the right directory SCRIPT=$(realpath "$0") @@ -35,7 +35,7 @@ function check_build_no_compiling() { } # Build once -cargo build -p qml-minimal-no-cmake &> /dev/null +cargo build -p qml-minimal-no-cmake # Build a second time check_build_no_compiling From 9aa28be730501ccb664369d4f2abf3ee9cd936b6 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Wed, 19 Jun 2024 15:28:36 +0200 Subject: [PATCH 09/20] cmake: Automatically add a Qt5 compatibility target --- cmake/CxxQt.cmake | 21 ++++++-- crates/cxx-qt-build/src/lib.rs | 78 +++++++++++++++++------------ examples/qml_minimal/CMakeLists.txt | 20 ++------ 3 files changed, 67 insertions(+), 52 deletions(-) diff --git a/cmake/CxxQt.cmake b/cmake/CxxQt.cmake index b5208e35b..873cf565f 100644 --- a/cmake/CxxQt.cmake +++ b/cmake/CxxQt.cmake @@ -21,7 +21,7 @@ function(cxxqt_import_crate) corrosion_import_crate(IMPORTED_CRATES __cxxqt_imported_crates ${IMPORT_CRATE_UNPARSED_ARGUMENTS}) - message(STATUS "Found CXX-Qt crate(s): ${__cxxqt_imported_crates}") + message(STATUS "CXX-Qt Found crate(s): ${__cxxqt_imported_crates}") if (NOT DEFINED IMPORT_CRATE_CXXQT_EXPORT_DIR) set(IMPORT_CRATE_CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt/") @@ -37,8 +37,13 @@ function(cxxqt_import_crate) endif() endif() - foreach(CRATE ${__cxxqt_imported_crates}) + if (Qt5_FOUND AND (NOT TARGET CXXQT_QT5_COMPATIBILITY)) + message(VERBOSE "CXX-QT detected Qt5 - Adding compatibility target: CXXQT_QT5_COMPATIBILITY") + add_library(CXXQT_QT5_COMPATIBILITY OBJECT ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../crates/cxx-qt-build/src/std_types_qt5.cpp) + target_link_libraries(CXXQT_QT5_COMPATIBILITY PRIVATE Qt5::Core) + endif() + foreach(CRATE ${__cxxqt_imported_crates}) corrosion_set_env_vars(${CRATE} "CXXQT_EXPORT_DIR=${IMPORT_CRATE_CXXQT_EXPORT_DIR}" "QMAKE=${IMPORT_CRATE_QMAKE}" @@ -50,6 +55,16 @@ function(cxxqt_import_crate) set_target_properties(${CRATE} PROPERTIES CXXQT_EXPORT_DIR "${IMPORT_CRATE_CXXQT_EXPORT_DIR}") + + if (Qt5_FOUND AND (QT_DEFAULT_MAJOR_VERSION EQUAL 5)) + message(VERBOSE "CXX-QT - Linking ${CRATE} to compatibility target: CXXQT_QT5_COMPATIBILITY") + + # Note that we need to link using TARGET_OBJECTS, so that the object files are included **transitively**, otherwise + # Only the linker flags from the compatibility target would be included, but not the actual object files. + # See also the "Linking Object Libraries" and "Linking Object Libraries via $" sections: + # https://cmake.org/cmake/help/latest/command/target_link_libraries.html + target_link_libraries(${CRATE} INTERFACE CXXQT_QT5_COMPATIBILITY $) + endif() endforeach() endfunction() @@ -94,5 +109,5 @@ function(cxxqt_import_qml_module target) IMPORTED_OBJECTS "${QML_MODULE_PLUGIN_DIR}/plugin_init.o") target_link_libraries(${target} INTERFACE ${QML_MODULE_SOURCE_CRATE}) - message(VERBOSE "Expecting CXX-Qt QML plugin: ${QML_MODULE_URI} in directory: ${QML_MODULE_PLUGIN_DIR}") + message(VERBOSE "CXX-Qt Expects QML plugin: ${QML_MODULE_URI} in directory: ${QML_MODULE_PLUGIN_DIR}") endfunction() diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 58e38a908..12de19e94 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -685,9 +685,9 @@ impl CxxQtBuilder { .file(qml_module_registration_files.qmltyperegistrar); self.cc_builder.file(qml_module_registration_files.plugin); - let mut new_builder = cc_builder_whole_archive.clone(); - new_builder.file(&qml_module_registration_files.plugin_init); - let obj_files = new_builder.compile_intermediates(); + let mut init_builder = cc_builder_whole_archive.clone(); + init_builder.file(&qml_module_registration_files.plugin_init); + let obj_files = init_builder.compile_intermediates(); if let Some(plugin_dir) = plugin_dir(&qml_module.uri) { for file in obj_files { // Note: This needs to match the URI conversion in CxxQt.cmake!!! @@ -699,7 +699,8 @@ impl CxxQtBuilder { file_path = file.to_string_lossy(), obj_path = obj_path.to_string_lossy() ); - std::fs::copy(&file, obj_path).unwrap(); + std::fs::copy(&file, obj_path) + .expect("Failed to move plugin_init object file to CXXQT_EXPORT_DIR!"); } } else { for file in obj_files { @@ -715,6 +716,46 @@ impl CxxQtBuilder { } } + // If we are using Qt 5 then write the std_types source + // This registers std numbers as a type for use in QML + // + // Note that we need this to be compiled into the whole_archive builder + // as they are stored in statics in the source. + // + // TODO: once +whole-archive and +bundle are allowed together in rlibs + // we should be able to move this into cxx-qt so that it's only built + // once rather than for every cxx-qt-build. When this happens also + // ensure that in a multi project that numbers work everywhere. + // + // Also then it should be possible to use CARGO_MANIFEST_DIR/src/std_types_qt5.cpp + // as path for cc::Build rather than copying the .cpp file + // + // https://github.com/rust-lang/rust/issues/108081 + // https://github.com/KDAB/cxx-qt/pull/598 + if qtbuild.version().major == 5 { + let mut std_types_builder = cc_builder_whole_archive.clone(); + + let std_types_contents = include_str!("std_types_qt5.cpp"); + let std_types_path = format!( + "{out_dir}/std_types_qt5.cpp", + out_dir = env::var("OUT_DIR").unwrap() + ); + let mut source = + File::create(&std_types_path).expect("Could not create std_types source"); + write!(source, "{std_types_contents}").expect("Could not write std_types source"); + std_types_builder.file(&std_types_path); + let obj_files = std_types_builder.compile_intermediates(); + + if export_dir().is_none() { + for file in obj_files { + println!( + "cargo:rustc-link-arg={file_path}", + file_path = file.to_string_lossy() + ) + } + } + } + cc_builder_whole_archive.file(qml_module_registration_files.rcc); for qmlcachegen_file in qml_module_registration_files.qmlcachegen { cc_builder_whole_archive.file(qmlcachegen_file); @@ -744,35 +785,6 @@ impl CxxQtBuilder { cc_builder_whole_archive_files_added = true; } - // If we are using Qt 5 then write the std_types source - // This registers std numbers as a type for use in QML - // - // Note that we need this to be compiled into the whole_archive builder - // as they are stored in statics in the source. - // - // TODO: once +whole-archive and +bundle are allowed together in rlibs - // we should be able to move this into cxx-qt so that it's only built - // once rather than for every cxx-qt-build. When this happens also - // ensure that in a multi project that numbers work everywhere. - // - // Also then it should be possible to use CARGO_MANIFEST_DIR/src/std_types_qt5.cpp - // as path for cc::Build rather than copying the .cpp file - // - // https://github.com/rust-lang/rust/issues/108081 - // https://github.com/KDAB/cxx-qt/pull/598 - if qtbuild.version().major == 5 { - let std_types_contents = include_str!("std_types_qt5.cpp"); - let std_types_path = format!( - "{out_dir}/std_types_qt5.cpp", - out_dir = env::var("OUT_DIR").unwrap() - ); - let mut source = - File::create(&std_types_path).expect("Could not create std_types source"); - write!(source, "{std_types_contents}").expect("Could not write std_types source"); - cc_builder_whole_archive.file(&std_types_path); - cc_builder_whole_archive_files_added = true; - } - if cc_builder_whole_archive_files_added { cc_builder_whole_archive.compile("qt-static-initializers"); } diff --git a/examples/qml_minimal/CMakeLists.txt b/examples/qml_minimal/CMakeLists.txt index 92c5acd4f..646d80898 100644 --- a/examples/qml_minimal/CMakeLists.txt +++ b/examples/qml_minimal/CMakeLists.txt @@ -44,8 +44,9 @@ endif() set(CRATE qml_minimal) # Corrosion (through CXX-Qt) creates a CMake target with the same name as the crate. cxxqt_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE}) +target_link_libraries(${CRATE} INTERFACE Qt::Core Qt::Gui Qt::Qml Qt::QuickControls2) -cxxqt_import_qml_module(${CRATE}qml +cxxqt_import_qml_module(${CRATE}_qml URI "com.kdab.cxx_qt.demo" SOURCE_CRATE ${CRATE}) # ANCHOR_END: book_cmake_use_corrosion @@ -55,13 +56,7 @@ cxxqt_import_qml_module(${CRATE}qml add_executable(${APP_NAME} cpp/main.cpp) # Link to the Rust library -target_link_libraries(${APP_NAME} - PRIVATE - ${CRATE}qml - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2) +target_link_libraries(${APP_NAME} PRIVATE ${CRATE}_qml) # If we are using a statically linked Qt then we need to import any qml plugins qt_import_qml_plugins(${APP_NAME}) @@ -81,14 +76,7 @@ if(BUILD_TESTING) function(add_qml_test TEST_NAME) set(APP_TEST_NAME ${APP_NAME}_${TEST_NAME}_test) add_executable(${APP_TEST_NAME} tests/${TEST_NAME}/tst_${TEST_NAME}.cpp) - target_link_libraries(${APP_TEST_NAME} PRIVATE - ${CRATE}qml - Qt::QuickTest - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 - ) + target_link_libraries(${APP_TEST_NAME} PRIVATE ${CRATE}_qml Qt::QuickTest) qt_import_qml_plugins(${APP_TEST_NAME}) set(TEST_CMD From b6d6ad3a6878ec4a98e09b32c7bfe3d928706ccc Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Wed, 19 Jun 2024 16:59:08 +0200 Subject: [PATCH 10/20] fix: Close the std_types_qt5 file before compiling --- crates/cxx-qt-build/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 12de19e94..dcc271e7a 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -743,6 +743,9 @@ impl CxxQtBuilder { let mut source = File::create(&std_types_path).expect("Could not create std_types source"); write!(source, "{std_types_contents}").expect("Could not write std_types source"); + source.sync_all().expect("Failed to write std_types source"); + drop(source); + std_types_builder.file(&std_types_path); let obj_files = std_types_builder.compile_intermediates(); From b118006a4a96a3f62ca83b56cdc584851032ccb6 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Wed, 19 Jun 2024 17:48:21 +0200 Subject: [PATCH 11/20] Remove loop error that would accidentally add unwanted files to the init builders --- crates/cxx-qt-build/src/lib.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index dcc271e7a..80115b127 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -586,7 +586,7 @@ impl CxxQtBuilder { // from within main when static linking, which would result in discarding those static variables. // Use a separate cc::Build for the little amount of code that needs to be linked with +whole-archive // to avoid bloating the binary. - let mut cc_builder_whole_archive = cc::Build::new(); + let mut plugin_builder = cc::Build::new(); // cc_builder_whole_archive.link_lib_modifier("+whole-archive"); // Ensure we are not using rustc 1.69 @@ -609,7 +609,7 @@ impl CxxQtBuilder { } } - for builder in [&mut self.cc_builder, &mut cc_builder_whole_archive] { + for builder in [&mut self.cc_builder, &mut plugin_builder] { // Note, ensure our settings stay in sync across cxx-qt, cxx-qt-build, and cxx-qt-lib builder.cpp(true); builder.std("c++17"); @@ -649,9 +649,10 @@ impl CxxQtBuilder { self.cc_builder.file(moc_products.cpp); } - let mut cc_builder_whole_archive_files_added = false; + let mut plugin_builder_files_added = false; let lib_name = "cxx-qt-generated"; + let init_builder = plugin_builder.clone(); // Bridges for QML modules are handled separately because // the metatypes_json generated by moc needs to be passed to qmltyperegistrar @@ -685,9 +686,9 @@ impl CxxQtBuilder { .file(qml_module_registration_files.qmltyperegistrar); self.cc_builder.file(qml_module_registration_files.plugin); - let mut init_builder = cc_builder_whole_archive.clone(); - init_builder.file(&qml_module_registration_files.plugin_init); - let obj_files = init_builder.compile_intermediates(); + let mut plugin_init_builder = init_builder.clone(); + plugin_init_builder.file(&qml_module_registration_files.plugin_init); + let obj_files = plugin_init_builder.compile_intermediates(); if let Some(plugin_dir) = plugin_dir(&qml_module.uri) { for file in obj_files { // Note: This needs to match the URI conversion in CxxQt.cmake!!! @@ -704,11 +705,6 @@ impl CxxQtBuilder { } } else { for file in obj_files { - // Only print this if we're building a bin target, as otherwise we get this - // error: - // - // error: invalid instruction `cargo::rustc-link-arg-bins` from build script of `...` - // The package ... does not have a bin target. println!( "cargo::rustc-link-arg={file_path}", file_path = file.to_string_lossy() @@ -733,7 +729,7 @@ impl CxxQtBuilder { // https://github.com/rust-lang/rust/issues/108081 // https://github.com/KDAB/cxx-qt/pull/598 if qtbuild.version().major == 5 { - let mut std_types_builder = cc_builder_whole_archive.clone(); + let mut std_types_builder = init_builder.clone(); let std_types_contents = include_str!("std_types_qt5.cpp"); let std_types_path = format!( @@ -759,12 +755,12 @@ impl CxxQtBuilder { } } - cc_builder_whole_archive.file(qml_module_registration_files.rcc); + plugin_builder.file(qml_module_registration_files.rcc); for qmlcachegen_file in qml_module_registration_files.qmlcachegen { - cc_builder_whole_archive.file(qmlcachegen_file); + plugin_builder.file(qmlcachegen_file); } self.cc_builder.define("QT_STATICPLUGIN", None); - cc_builder_whole_archive_files_added = true; + plugin_builder_files_added = true; // If any of the files inside the qml module change, then trigger a rerun for path in qml_module.qml_files.iter().chain( @@ -778,18 +774,18 @@ impl CxxQtBuilder { } for qrc_file in self.qrc_files { - cc_builder_whole_archive.file(qtbuild.qrc(&qrc_file)); + plugin_builder.file(qtbuild.qrc(&qrc_file)); // Also ensure that each of the files in the qrc can cause a change for qrc_inner_file in qtbuild.qrc_list(&qrc_file) { println!("cargo:rerun-if-changed={}", qrc_inner_file.display()); } - cc_builder_whole_archive_files_added = true; + plugin_builder_files_added = true; } - if cc_builder_whole_archive_files_added { - cc_builder_whole_archive.compile("qt-static-initializers"); + if plugin_builder_files_added { + plugin_builder.compile("cxx-qt-plugins"); } // Only compile if we have added files to the builder From f30e3bcac88ba11e40609264a574bfa1e9a97776 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Thu, 20 Jun 2024 10:34:48 +0200 Subject: [PATCH 12/20] cxx-qt-build: Refactor `build` method --- crates/cxx-qt-build/src/lib.rs | 492 ++++++++++++++++++--------------- 1 file changed, 272 insertions(+), 220 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 80115b127..7c4804f5d 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -25,6 +25,7 @@ use qml_modules::OwningQmlModule; pub use qml_modules::QmlModule; pub use qt_build_utils::MocArguments; +use qt_build_utils::SemVer; use quote::ToTokens; use std::{ collections::HashSet, @@ -257,23 +258,21 @@ fn generate_cxxqt_cpp_files( /// The export directory, if one was specified through the environment. /// Note that this is not namspaced by crate. -fn export_dir() -> Option { - env::var("CXXQT_EXPORT_DIR").ok() +fn export_dir() -> Option { + env::var("CXXQT_EXPORT_DIR").ok().map(PathBuf::from) } -fn plugin_dir(plugin_uri: &str) -> Option { - let plugin_uri = plugin_uri.replace('.', "_"); - export_dir().map(|export_dir| format!("{export_dir}/plugins/{plugin_uri}/")) +fn plugin_name_from_uri(plugin_uri: &str) -> String { + plugin_uri.replace('.', "_") } /// The include directory needs to be namespaced by crate name when exporting for a C++ build system, /// but for using cargo build without a C++ build system, OUT_DIR is already namespaced by crate name. -fn header_root() -> String { - format!( - "{export_dir}/include/{}", - crate_name(), - export_dir = export_dir().unwrap_or_else(|| env::var("OUT_DIR").unwrap()) - ) +fn header_root() -> PathBuf { + export_dir() + .unwrap_or_else(|| PathBuf::from(env::var("OUT_DIR").unwrap())) + .join("include") + .join(crate_name()) } fn crate_name() -> String { @@ -469,14 +468,16 @@ impl CxxQtBuilder { let directory = if dir_name.is_empty() { header_root.clone() } else { - format!("{header_root}/{dir_name}") + header_root.join(dir_name) }; std::fs::create_dir_all(directory.clone()) .expect("Could not create {directory} header directory"); - let h_path = format!("{directory}/{file_name}"); - let mut header = File::create(h_path).expect("Could not create header: {h_path}"); - write!(header, "{file_contents}").expect("Could not write header: {h_path}"); + let h_path = directory.join(file_name); + std::fs::write(&h_path, file_contents).expect(&format!( + "Could not write header: {h_path}", + h_path = h_path.to_string_lossy() + )); } // Add any of the defines @@ -512,19 +513,7 @@ impl CxxQtBuilder { } } - /// Generate and compile cxx-qt C++ code, as well as compile any additional files from - /// [CxxQtBuilder::qobject_header] and [CxxQtBuilder::cc_builder]. - pub fn build(mut self) { - // Ensure that the linker is setup correctly for Cargo builds - qt_build_utils::setup_linker(); - - let header_root = header_root(); - let generated_header_dir = format!("{header_root}/cxx-qt-gen"); - - let mut qtbuild = qt_build_utils::QtBuild::new(self.qt_modules.into_iter().collect()) - .expect("Could not find Qt installation"); - qtbuild.cargo_link_libraries(&mut self.cc_builder); - + fn define_qt_version_cfg_variables(version: &SemVer) { // Allow for Qt 5 or Qt 6 as valid values CxxQtBuilder::define_cfg_check_variable( "cxxqt_qt_version_major".to_owned(), @@ -534,7 +523,7 @@ impl CxxQtBuilder { // this allows us to have conditional Rust code CxxQtBuilder::define_cfg_variable( "cxxqt_qt_version_major".to_string(), - Some(qtbuild.version().major.to_string().as_str()), + Some(version.major.to_string().as_str()), ); // Seed all values from Qt 5.0 through to Qt 7.99 @@ -552,246 +541,309 @@ impl CxxQtBuilder { } } - for minor in 0..=qtbuild.version().minor { - let qt_version_at_least = format!( - "cxxqt_qt_version_at_least_{}_{}", - qtbuild.version().major, - minor - ); + for minor in 0..=version.minor { + let qt_version_at_least = + format!("cxxqt_qt_version_at_least_{}_{}", version.major, minor); CxxQtBuilder::define_cfg_variable(qt_version_at_least.to_string(), None); } // We don't support Qt < 5 - for major in 5..=qtbuild.version().major { + for major in 5..=version.major { let at_least_qt_major_version = format!("cxxqt_qt_version_at_least_{}", major); CxxQtBuilder::define_cfg_variable(at_least_qt_major_version, None); } + } + fn write_common_headers() { + let header_root = header_root(); // Write cxx-qt and cxx headers - cxx_qt::write_headers(format!("{header_root}/cxx-qt")); - std::fs::create_dir_all(format!("{header_root}/rust")) + cxx_qt::write_headers(header_root.join("cxx-qt")); + std::fs::create_dir_all(header_root.join("rust")) .expect("Could not create cxx header directory"); - let h_path = format!("{header_root}/rust/cxx.h"); + let h_path = header_root.join("rust").join("cxx.h"); // Wrap the File in a block scope so the file is closed before the compiler is run. // Otherwise MSVC fails to open cxx.h because the process for this build script already has it open. { - let mut header = File::create(h_path).expect("Could not create cxx.h"); - write!(header, "{}", cxx_gen::HEADER).expect("Could not write cxx.h"); + std::fs::write(h_path, cxx_gen::HEADER).expect("Failed to write cxx.h"); } + } - // Setup compiler - // Static QML plugin and Qt resource initialization need to be linked with +whole-archive - // because they use static variables which need to be initialized before main - // (regardless of whether main is in Rust or C++). Normally linkers only copy symbols referenced - // from within main when static linking, which would result in discarding those static variables. - // Use a separate cc::Build for the little amount of code that needs to be linked with +whole-archive - // to avoid bloating the binary. - let mut plugin_builder = cc::Build::new(); - // cc_builder_whole_archive.link_lib_modifier("+whole-archive"); - - // Ensure we are not using rustc 1.69 - if let Some(version) = version_check::Version::read() { - let (major, minor, _) = version.to_mmp(); - if major == 1 && minor == 69 { - // rustc 1.69 had a regression where +whole-archive wouldn't - // work without specifying -bundle. - // https://github.com/rust-lang/rust/pull/110917 - // - // However, we need to not have -bundle for qt-static-initializers to work - // with CMake builds, otherwise the statement below occurs where it's missing - // from the final binary. - // - // When building a staticlib -bundle means that the native static library - // is simply not included into the archive and some higher level build - // system will need to add it later during linking of the final binary. - // https://doc.rust-lang.org/rustc/command-line-arguments.html#option-l-link-lib - panic!("rustc 1.69.x is not supported with CXX-Qt due to a compiler bug.\nSee: https://github.com/rust-lang/rust/pull/110917\nPlease update your compiler using 'rustup update' or use an older compiler."); - } + fn setup_cc_builder<'a>( + builder: &mut cc::Build, + include_paths: &[impl AsRef], + defines: impl Iterator, + ) { + // Note, ensure our settings stay in sync across cxx-qt, cxx-qt-build, and cxx-qt-lib + builder.cpp(true); + builder.std("c++17"); + // MSVC + builder.flag_if_supported("/Zc:__cplusplus"); + builder.flag_if_supported("/permissive-"); + builder.flag_if_supported("/bigobj"); + // MinGW requires big-obj otherwise debug builds fail + builder.flag_if_supported("-Wa,-mbig-obj"); + + // Enable any extra defines + for define in defines { + builder.define(define, None); } - for builder in [&mut self.cc_builder, &mut plugin_builder] { - // Note, ensure our settings stay in sync across cxx-qt, cxx-qt-build, and cxx-qt-lib - builder.cpp(true); - builder.std("c++17"); - // MSVC - builder.flag_if_supported("/Zc:__cplusplus"); - builder.flag_if_supported("/permissive-"); - builder.flag_if_supported("/bigobj"); - // MinGW requires big-obj otherwise debug builds fail - builder.flag_if_supported("-Wa,-mbig-obj"); - - // Enable any extra defines - for extra_define in &self.extra_defines { - builder.define(extra_define, None); - } - - builder.includes(qtbuild.include_paths()); - builder.include(&header_root); - builder.include(&generated_header_dir); - } - - // Generate files - for files in generate_cxxqt_cpp_files(&self.rust_sources, &generated_header_dir) { - self.cc_builder.file(files.plain_cpp); - if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) { - self.cc_builder.file(&qobject); - self.qobject_headers.push(qobject_header.into()); - } + for include_path in include_paths { + builder.include(include_path); } + } - // Run moc on C++ headers with Q_OBJECT macro + fn moc_qobject_headers(&mut self, qtbuild: &mut qt_build_utils::QtBuild) { for QObjectHeaderOpts { path, moc_arguments, - } in self.qobject_headers + } in &self.qobject_headers { - let moc_products = qtbuild.moc(&path, moc_arguments); + let moc_products = qtbuild.moc(path, moc_arguments.clone()); self.cc_builder.file(moc_products.cpp); } + } - let mut plugin_builder_files_added = false; - - let lib_name = "cxx-qt-generated"; - let init_builder = plugin_builder.clone(); - - // Bridges for QML modules are handled separately because - // the metatypes_json generated by moc needs to be passed to qmltyperegistrar - for qml_module in self.qml_modules { - let mut qml_metatypes_json = Vec::new(); - - for files in generate_cxxqt_cpp_files(&qml_module.rust_files, &generated_header_dir) { - self.cc_builder.file(files.plain_cpp); - if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) - { - self.cc_builder.file(&qobject); - let moc_products = qtbuild.moc( - qobject_header, - MocArguments::default().uri(qml_module.uri.clone()), - ); - self.cc_builder.file(moc_products.cpp); - qml_metatypes_json.push(moc_products.metatypes_json); - } + fn generate_cpp_files_from_cxxqt_bridges(&mut self, header_dir: impl AsRef) { + for files in generate_cxxqt_cpp_files(&self.rust_sources, &header_dir) { + self.cc_builder.file(files.plain_cpp); + if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) { + self.cc_builder.file(&qobject); + self.qobject_headers.push(qobject_header.into()); } + } + } - let qml_module_registration_files = qtbuild.register_qml_module( - &qml_metatypes_json, - &qml_module.uri, - qml_module.version_major, - qml_module.version_minor, - lib_name, - &qml_module.qml_files, - &qml_module.qrc_files, - ); - self.cc_builder - .file(qml_module_registration_files.qmltyperegistrar); - self.cc_builder.file(qml_module_registration_files.plugin); - - let mut plugin_init_builder = init_builder.clone(); - plugin_init_builder.file(&qml_module_registration_files.plugin_init); - let obj_files = plugin_init_builder.compile_intermediates(); - if let Some(plugin_dir) = plugin_dir(&qml_module.uri) { - for file in obj_files { - // Note: This needs to match the URI conversion in CxxQt.cmake!!! - let path = PathBuf::from(&plugin_dir); - std::fs::create_dir_all(&path).unwrap(); - let obj_path = path.join("plugin_init.o"); - println!( - "Copying initializers obj file: {file_path} -> {obj_path}", - file_path = file.to_string_lossy(), - obj_path = obj_path.to_string_lossy() - ); - std::fs::copy(&file, obj_path) - .expect("Failed to move plugin_init object file to CXXQT_EXPORT_DIR!"); + fn build_object_file( + builder: &cc::Build, + file_path: impl AsRef, + export_path: Option<(&str, &str)>, + ) { + let mut obj_builder = builder.clone(); + obj_builder.file(file_path); + let obj_files = obj_builder.compile_intermediates(); + + if let [obj_file] = &obj_files[..] { + if let Some(export_dir) = export_dir() { + if let Some((out_directory, out_file_name)) = export_path { + let obj_dir = export_dir.join(out_directory); + std::fs::create_dir_all(&obj_dir).unwrap_or_else(|_| { + panic!( + "Could not create directory for object file: {}", + obj_dir.to_string_lossy() + ) + }); + let obj_path = obj_dir.join(out_file_name); + std::fs::copy(obj_file, &obj_path).unwrap_or_else(|_| { + panic!( + "Failed to move object file to {obj_path}!", + obj_path = obj_path.to_string_lossy() + ) + }); } } else { - for file in obj_files { - println!( - "cargo::rustc-link-arg={file_path}", - file_path = file.to_string_lossy() - ) - } + println!("cargo::rustc-link-arg={}", obj_file.to_string_lossy()) } + } else { + panic!( + "CXX-Qt internal error: Expected only one object file out of cc::Build! Got {}", + obj_files.len() + ); + } + } - // If we are using Qt 5 then write the std_types source - // This registers std numbers as a type for use in QML - // - // Note that we need this to be compiled into the whole_archive builder - // as they are stored in statics in the source. - // - // TODO: once +whole-archive and +bundle are allowed together in rlibs - // we should be able to move this into cxx-qt so that it's only built - // once rather than for every cxx-qt-build. When this happens also - // ensure that in a multi project that numbers work everywhere. - // - // Also then it should be possible to use CARGO_MANIFEST_DIR/src/std_types_qt5.cpp - // as path for cc::Build rather than copying the .cpp file - // - // https://github.com/rust-lang/rust/issues/108081 - // https://github.com/KDAB/cxx-qt/pull/598 - if qtbuild.version().major == 5 { - let mut std_types_builder = init_builder.clone(); - - let std_types_contents = include_str!("std_types_qt5.cpp"); - let std_types_path = format!( - "{out_dir}/std_types_qt5.cpp", - out_dir = env::var("OUT_DIR").unwrap() + fn build_qml_module( + &mut self, + qml_module: &OwningQmlModule, + init_builder: &cc::Build, + qtbuild: &mut qt_build_utils::QtBuild, + generated_header_dir: impl AsRef, + ) { + let mut qml_metatypes_json = Vec::new(); + + for files in generate_cxxqt_cpp_files(&qml_module.rust_files, &generated_header_dir) { + self.cc_builder.file(files.plain_cpp); + if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) { + self.cc_builder.file(&qobject); + let moc_products = qtbuild.moc( + qobject_header, + MocArguments::default().uri(qml_module.uri.clone()), ); - let mut source = - File::create(&std_types_path).expect("Could not create std_types source"); - write!(source, "{std_types_contents}").expect("Could not write std_types source"); - source.sync_all().expect("Failed to write std_types source"); - drop(source); - - std_types_builder.file(&std_types_path); - let obj_files = std_types_builder.compile_intermediates(); - - if export_dir().is_none() { - for file in obj_files { - println!( - "cargo:rustc-link-arg={file_path}", - file_path = file.to_string_lossy() - ) - } - } + self.cc_builder.file(moc_products.cpp); + qml_metatypes_json.push(moc_products.metatypes_json); } + } - plugin_builder.file(qml_module_registration_files.rcc); - for qmlcachegen_file in qml_module_registration_files.qmlcachegen { - plugin_builder.file(qmlcachegen_file); - } - self.cc_builder.define("QT_STATICPLUGIN", None); - plugin_builder_files_added = true; - - // If any of the files inside the qml module change, then trigger a rerun - for path in qml_module.qml_files.iter().chain( - qml_module - .rust_files - .iter() - .chain(qml_module.qrc_files.iter()), - ) { - println!("cargo:rerun-if-changed={}", path.display()); - } + let qml_module_registration_files = qtbuild.register_qml_module( + &qml_metatypes_json, + &qml_module.uri, + qml_module.version_major, + qml_module.version_minor, + // TODO: This will be passed to the `optional plugin ...` part of the qmldir + // We don't load any shared libraries, so the name shouldn't matter + // But make sure it still works + &plugin_name_from_uri(&qml_module.uri), + &qml_module.qml_files, + &qml_module.qrc_files, + ); + self.cc_builder + .file(qml_module_registration_files.qmltyperegistrar); + self.cc_builder.file(qml_module_registration_files.plugin); + + // In comparison to the other RCC files, we don't need to link this with whole-archive or + // anything like that. + // The plugin_init file already takes care of loading the resources associated with this + // RCC file. + self.cc_builder.file(qml_module_registration_files.rcc); + for qmlcachegen_file in qml_module_registration_files.qmlcachegen { + self.cc_builder.file(qmlcachegen_file); + } + // This is required, as described here: plugin_builder + self.cc_builder.define("QT_STATICPLUGIN", None); + + // If any of the files inside the qml module change, then trigger a rerun + for path in qml_module.qml_files.iter().chain( + qml_module + .rust_files + .iter() + .chain(qml_module.qrc_files.iter()), + ) { + println!("cargo:rerun-if-changed={}", path.display()); + } + + // Now all necessary symbols should be included in the cc_builder. + // However, the plugin needs to be initialized at runtime. + // This is done through the plugin_init file. + // It needs to be linked as an object file, to ensure that the linker doesn't throw away + // the static initializers in this file. + // For CMake builds, we export this file to then later include it as an object library in + // CMake. + // In cargo builds, add the object file as a direct argument to the linker. + Self::build_object_file( + init_builder, + &qml_module_registration_files.plugin_init, + Some(( + &format!("plugins/{}", plugin_name_from_uri(&qml_module.uri)), + "plugin_init.o", + )), + ); + } + + fn setup_qt5_compatibility(init_builder: &cc::Build, qtbuild: &qt_build_utils::QtBuild) { + // If we are using Qt 5 then write the std_types source + // This registers std numbers as a type for use in QML + // + // Note that we need this to be compiled into the whole_archive builder + // as they are stored in statics in the source. + // + // TODO: once +whole-archive and +bundle are allowed together in rlibs + // we should be able to move this into cxx-qt so that it's only built + // once rather than for every cxx-qt-build. When this happens also + // ensure that in a multi project that numbers work everywhere. + // + // Also then it should be possible to use CARGO_MANIFEST_DIR/src/std_types_qt5.cpp + // as path for cc::Build rather than copying the .cpp file + // + // https://github.com/rust-lang/rust/issues/108081 + // https://github.com/KDAB/cxx-qt/pull/598 + if qtbuild.version().major == 5 { + let std_types_contents = include_str!("std_types_qt5.cpp"); + let std_types_path = format!( + "{out_dir}/std_types_qt5.cpp", + out_dir = env::var("OUT_DIR").unwrap() + ); + std::fs::write(&std_types_path, std_types_contents) + .expect("Could not write std_types source"); + + Self::build_object_file(init_builder, std_types_path, None); + } + } + + /// Generate and compile cxx-qt C++ code, as well as compile any additional files from + /// [CxxQtBuilder::qobject_header] and [CxxQtBuilder::cc_builder]. + pub fn build(mut self) { + // Ensure that the linker is setup correctly for Cargo builds + qt_build_utils::setup_linker(); + + let header_root = header_root(); + let generated_header_dir = header_root.join("cxx-qt-gen/"); + + let mut qtbuild = qt_build_utils::QtBuild::new(self.qt_modules.drain().collect()) + .expect("Could not find Qt installation"); + qtbuild.cargo_link_libraries(&mut self.cc_builder); + Self::define_qt_version_cfg_variables(qtbuild.version()); + + Self::write_common_headers(); + + // Setup compilers + // Static QML plugin and Qt resource initializers need to be linked as their own separate + // object files because they use static variables which need to be initialized before main + // (regardless of whether main is in Rust or C++). Normally linkers only copy symbols referenced + // from within main when static linking, which would result in discarding those static variables. + // Use a separate cc::Build for the little amount of code that needs to be built & linked this way. + let mut init_builder = cc::Build::new(); + let mut include_paths = qtbuild.include_paths(); + include_paths.extend([header_root.clone(), generated_header_dir.clone()]); + + Self::setup_cc_builder( + &mut self.cc_builder, + &include_paths, + self.extra_defines.iter().map(String::as_str), + ); + Self::setup_cc_builder( + &mut init_builder, + &include_paths, + self.extra_defines.iter().map(String::as_str), + ); + // Note: From now on the init_builder is correctly configured. + // When building object files with this builder, we always need to copy it first. + // So remove `mut` to ensure that we can't accidentally change the configuration or add + // files. + let init_builder = init_builder; + + // Generate files + self.generate_cpp_files_from_cxxqt_bridges(&generated_header_dir); + + self.moc_qobject_headers(&mut qtbuild); + + // Bridges for QML modules are handled separately because + // the metatypes_json generated by moc needs to be passed to qmltyperegistrar + let qml_modules: Vec<_> = self.qml_modules.drain(..).collect(); + for qml_module in &qml_modules { + self.build_qml_module( + qml_module, + &init_builder, + &mut qtbuild, + &generated_header_dir, + ); } for qrc_file in self.qrc_files { - plugin_builder.file(qtbuild.qrc(&qrc_file)); + let obj_file_name = format!( + "{}.o", + qrc_file.file_name().unwrap_or_default().to_string_lossy() + ); + let obj_file_dir = format!("qrc/{}", crate_name()); + // TODO: Is it correct to use an obj file here, or should this just link normally? + Self::build_object_file( + &init_builder, + qtbuild.qrc(&qrc_file), + Some((&obj_file_dir, &*obj_file_name)), + ); // Also ensure that each of the files in the qrc can cause a change for qrc_inner_file in qtbuild.qrc_list(&qrc_file) { println!("cargo:rerun-if-changed={}", qrc_inner_file.display()); } - - plugin_builder_files_added = true; } - if plugin_builder_files_added { - plugin_builder.compile("cxx-qt-plugins"); - } + Self::setup_qt5_compatibility(&init_builder, &qtbuild); // Only compile if we have added files to the builder // otherwise we end up with no static library but ask cargo to link to it which causes an error if self.cc_builder.get_files().count() > 0 { - self.cc_builder.compile(lib_name); + self.cc_builder + .compile(&format!("{}-cxxqt-generated", crate_name())); } } } From 17e0e76efe0372e0c0bcbc63ffd102e5db7c5ffd Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Thu, 20 Jun 2024 13:05:30 +0200 Subject: [PATCH 13/20] Cxx-Qt-build: compile object file with -arg-bins --- crates/cxx-qt-build/src/lib.rs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 7c4804f5d..b2c7fc742 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -279,6 +279,10 @@ fn crate_name() -> String { env::var("CARGO_PKG_NAME").unwrap() } +fn is_binary() -> bool { + env::var("CARGO_BIN_NAME").is_ok() +} + fn panic_duplicate_file_and_qml_module( path: impl AsRef, uri: &str, @@ -474,10 +478,12 @@ impl CxxQtBuilder { .expect("Could not create {directory} header directory"); let h_path = directory.join(file_name); - std::fs::write(&h_path, file_contents).expect(&format!( - "Could not write header: {h_path}", - h_path = h_path.to_string_lossy() - )); + std::fs::write(&h_path, file_contents).unwrap_or_else(|_| { + panic!( + "Could not write header: {h_path}", + h_path = h_path.to_string_lossy() + ) + }); } // Add any of the defines @@ -641,8 +647,8 @@ impl CxxQtBuilder { ) }); } - } else { - println!("cargo::rustc-link-arg={}", obj_file.to_string_lossy()) + } else if is_binary() { + println!("cargo::rustc-link-arg-bins={}", obj_file.to_string_lossy()) } } else { panic!( @@ -687,14 +693,14 @@ impl CxxQtBuilder { &qml_module.qrc_files, ); self.cc_builder - .file(qml_module_registration_files.qmltyperegistrar); - self.cc_builder.file(qml_module_registration_files.plugin); - - // In comparison to the other RCC files, we don't need to link this with whole-archive or - // anything like that. - // The plugin_init file already takes care of loading the resources associated with this - // RCC file. - self.cc_builder.file(qml_module_registration_files.rcc); + .file(qml_module_registration_files.qmltyperegistrar) + .file(qml_module_registration_files.plugin) + // In comparison to the other RCC files, we don't need to link this with whole-archive or + // anything like that. + // The plugin_init file already takes care of loading the resources associated with this + // RCC file. + .file(qml_module_registration_files.rcc); + for qmlcachegen_file in qml_module_registration_files.qmlcachegen { self.cc_builder.file(qmlcachegen_file); } From 91689e52fbc94ed3e428704b769cbe74f661ce03 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Thu, 20 Jun 2024 13:56:49 +0200 Subject: [PATCH 14/20] cxx-qt-build: specify -l to the staticlib manually Otherwise the linker order doesn't work out and the linker cannot resolve the qt_static_plugin_*** functions --- crates/cxx-qt-build/src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index b2c7fc742..496cdec04 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -279,8 +279,8 @@ fn crate_name() -> String { env::var("CARGO_PKG_NAME").unwrap() } -fn is_binary() -> bool { - env::var("CARGO_BIN_NAME").is_ok() +fn static_lib_name() -> String { + format!("{}-cxxqt-generated", crate_name()) } fn panic_duplicate_file_and_qml_module( @@ -647,8 +647,13 @@ impl CxxQtBuilder { ) }); } - } else if is_binary() { - println!("cargo::rustc-link-arg-bins={}", obj_file.to_string_lossy()) + } else { + println!("cargo::rustc-link-arg={}", obj_file.to_string_lossy()); + // The linker argument order matters! + // We need to link the object file first, then link the static library. + // Otherwise, the linker will be unable to find the symbols in the static library file. + // See also: https://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc + println!("cargo::rustc-link-arg=-l{}", static_lib_name()); } } else { panic!( @@ -848,8 +853,7 @@ impl CxxQtBuilder { // Only compile if we have added files to the builder // otherwise we end up with no static library but ask cargo to link to it which causes an error if self.cc_builder.get_files().count() > 0 { - self.cc_builder - .compile(&format!("{}-cxxqt-generated", crate_name())); + self.cc_builder.compile(&static_lib_name()); } } } From a4939a11daf55f8a271608e5d3c033c2a2b24c56 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Thu, 20 Jun 2024 14:52:13 +0200 Subject: [PATCH 15/20] qt-build-utils: Refactor to use std::fs::write It's much more concise than using File::create followed by write! --- crates/qt-build-utils/src/lib.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 1ff39bd08..4bd42e4e8 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -892,10 +892,10 @@ prefer :/qt/qml/{qml_uri_dirs}/ let declarations = declarations.join("\n"); let usages = usages.join("\n"); - let mut qml_plugin_cpp = File::create(&qml_plugin_cpp_path).unwrap(); - write!( - qml_plugin_cpp, - r#" + std::fs::write( + &qml_plugin_cpp_path, + format!( + r#" #include // TODO: Add missing handling for GHS (Green Hills Software compiler) that is in @@ -916,9 +916,11 @@ public: // The moc-generated cpp file doesn't compile on its own; it needs to be #included here. #include "moc_{plugin_class_name}.cpp.cpp" -"# +"#, + ), ) - .unwrap(); + .expect("Failed to write plugin definition"); + self.moc( &qml_plugin_cpp_path, MocArguments { @@ -928,15 +930,16 @@ public: ); // Generate file to load static QQmlExtensionPlugin - let mut qml_plugin_init = File::create(&qml_plugin_init_path).unwrap(); - write!( - qml_plugin_init, - r#" + std::fs::write( + &qml_plugin_init_path, + format!( + r#" #include Q_IMPORT_PLUGIN({plugin_class_name}); "# + ), ) - .unwrap(); + .expect("Failed to write plugin initializer file"); } QmlModuleRegistrationFiles { From 5451dae94020f88307192d1cd82a361b8d3c4377 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Thu, 20 Jun 2024 15:40:25 +0200 Subject: [PATCH 16/20] cxx-qt-build: Allow custom initializers in opts This can be used to e.g. register custom types, import plugins, declare modules, etc. --- cmake/CxxQt.cmake | 14 ++ crates/cxx-qt-build/src/lib.rs | 213 ++++++++++-------- crates/cxx-qt-build/src/opts.rs | 9 + .../cxx-qt-lib-headers/include/core/init.cpp | 165 ++++++++++++++ .../cxx-qt-lib-headers/include/gui/init.cpp | 14 ++ crates/cxx-qt-lib-headers/src/lib.rs | 7 +- crates/cxx-qt-lib/src/core/qhash/qhash.cpp | 7 - crates/cxx-qt-lib/src/core/qlist/qlist.cpp | 60 ----- crates/cxx-qt-lib/src/core/qmap/qmap.cpp | 5 - crates/cxx-qt-lib/src/core/qset/qset.cpp | 27 --- .../cxx-qt-lib/src/core/qvector/qvector.cpp | 62 ----- 11 files changed, 324 insertions(+), 259 deletions(-) create mode 100644 crates/cxx-qt-lib-headers/include/core/init.cpp create mode 100644 crates/cxx-qt-lib-headers/include/gui/init.cpp diff --git a/cmake/CxxQt.cmake b/cmake/CxxQt.cmake index 873cf565f..dbfb17343 100644 --- a/cmake/CxxQt.cmake +++ b/cmake/CxxQt.cmake @@ -65,6 +65,20 @@ function(cxxqt_import_crate) # https://cmake.org/cmake/help/latest/command/target_link_libraries.html target_link_libraries(${CRATE} INTERFACE CXXQT_QT5_COMPATIBILITY $) endif() + + + add_custom_target(${CRATE}_mock_initializers + COMMAND ${CMAKE_COMMAND} -E true + DEPENDS ${CRATE} + BYPRODUCTS "${IMPORT_CRATE_CXXQT_EXPORT_DIR}/initializers.o") + + add_library(${CRATE}_initializers OBJECT IMPORTED) + set_target_properties(${CRATE}_initializers + PROPERTIES + IMPORTED_OBJECTS "${IMPORT_CRATE_CXXQT_EXPORT_DIR}/initializers.o") + target_link_libraries(${CRATE} INTERFACE ${CRATE}_initializers $) + + message(VERBOSE "CXX-Qt Expects QML plugin: ${QML_MODULE_URI} in directory: ${QML_MODULE_PLUGIN_DIR}") endforeach() endfunction() diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 496cdec04..063e0abc7 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -235,13 +235,13 @@ fn generate_cxxqt_cpp_files( rs_source: &[impl AsRef], header_dir: impl AsRef, ) -> Vec { - let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let mut generated_file_paths: Vec = Vec::with_capacity(rs_source.len()); for rs_path in rs_source { - let cpp_directory = format!("{}/cxx-qt-gen/src", env::var("OUT_DIR").unwrap()); - let path = format!("{manifest_dir}/{}", rs_path.as_ref().display()); - println!("cargo:rerun-if-changed={path}"); + let cpp_directory = out_dir().join("cxx-qt-gen/src"); + let path = manifest_dir.join(rs_path); + println!("cargo:rerun-if-changed={}", path.to_string_lossy()); let generated_code = match GeneratedCpp::new(&path) { Ok(v) => v, @@ -262,6 +262,10 @@ fn export_dir() -> Option { env::var("CXXQT_EXPORT_DIR").ok().map(PathBuf::from) } +fn out_dir() -> PathBuf { + env::var("OUT_DIR").unwrap().into() +} + fn plugin_name_from_uri(plugin_uri: &str) -> String { plugin_uri.replace('.', "_") } @@ -333,6 +337,7 @@ pub struct CxxQtBuilder { qml_modules: Vec, cc_builder: cc::Build, extra_defines: HashSet, + initializers: Vec, } impl CxxQtBuilder { @@ -348,6 +353,7 @@ impl CxxQtBuilder { qml_modules: vec![], cc_builder: cc::Build::new(), extra_defines: HashSet::new(), + initializers: Vec::new(), } } @@ -489,6 +495,8 @@ impl CxxQtBuilder { // Add any of the defines self.extra_defines.extend(opts.defines); + self.initializers.extend(opts.initializers); + // Add any of the Qt modules self.qt_modules.extend(opts.qt_modules); @@ -663,81 +671,83 @@ impl CxxQtBuilder { } } - fn build_qml_module( + fn build_qml_modules( &mut self, - qml_module: &OwningQmlModule, init_builder: &cc::Build, qtbuild: &mut qt_build_utils::QtBuild, generated_header_dir: impl AsRef, ) { - let mut qml_metatypes_json = Vec::new(); - - for files in generate_cxxqt_cpp_files(&qml_module.rust_files, &generated_header_dir) { - self.cc_builder.file(files.plain_cpp); - if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) { - self.cc_builder.file(&qobject); - let moc_products = qtbuild.moc( - qobject_header, - MocArguments::default().uri(qml_module.uri.clone()), - ); - self.cc_builder.file(moc_products.cpp); - qml_metatypes_json.push(moc_products.metatypes_json); + for qml_module in &self.qml_modules { + let mut qml_metatypes_json = Vec::new(); + + for files in generate_cxxqt_cpp_files(&qml_module.rust_files, &generated_header_dir) { + self.cc_builder.file(files.plain_cpp); + if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) + { + self.cc_builder.file(&qobject); + let moc_products = qtbuild.moc( + qobject_header, + MocArguments::default().uri(qml_module.uri.clone()), + ); + self.cc_builder.file(moc_products.cpp); + qml_metatypes_json.push(moc_products.metatypes_json); + } } - } - let qml_module_registration_files = qtbuild.register_qml_module( - &qml_metatypes_json, - &qml_module.uri, - qml_module.version_major, - qml_module.version_minor, - // TODO: This will be passed to the `optional plugin ...` part of the qmldir - // We don't load any shared libraries, so the name shouldn't matter - // But make sure it still works - &plugin_name_from_uri(&qml_module.uri), - &qml_module.qml_files, - &qml_module.qrc_files, - ); - self.cc_builder - .file(qml_module_registration_files.qmltyperegistrar) - .file(qml_module_registration_files.plugin) - // In comparison to the other RCC files, we don't need to link this with whole-archive or - // anything like that. - // The plugin_init file already takes care of loading the resources associated with this - // RCC file. - .file(qml_module_registration_files.rcc); - - for qmlcachegen_file in qml_module_registration_files.qmlcachegen { - self.cc_builder.file(qmlcachegen_file); - } - // This is required, as described here: plugin_builder - self.cc_builder.define("QT_STATICPLUGIN", None); + let qml_module_registration_files = qtbuild.register_qml_module( + &qml_metatypes_json, + &qml_module.uri, + qml_module.version_major, + qml_module.version_minor, + // TODO: This will be passed to the `optional plugin ...` part of the qmldir + // We don't load any shared libraries, so the name shouldn't matter + // But make sure it still works + &plugin_name_from_uri(&qml_module.uri), + &qml_module.qml_files, + &qml_module.qrc_files, + ); + self.cc_builder + .file(qml_module_registration_files.qmltyperegistrar) + .file(qml_module_registration_files.plugin) + // In comparison to the other RCC files, we don't need to link this with whole-archive or + // anything like that. + // The plugin_init file already takes care of loading the resources associated with this + // RCC file. + .file(qml_module_registration_files.rcc); + + for qmlcachegen_file in qml_module_registration_files.qmlcachegen { + self.cc_builder.file(qmlcachegen_file); + } + // This is required, as described here: plugin_builder + self.cc_builder.define("QT_STATICPLUGIN", None); + + // If any of the files inside the qml module change, then trigger a rerun + for path in qml_module.qml_files.iter().chain( + qml_module + .rust_files + .iter() + .chain(qml_module.qrc_files.iter()), + ) { + println!("cargo:rerun-if-changed={}", path.display()); + } - // If any of the files inside the qml module change, then trigger a rerun - for path in qml_module.qml_files.iter().chain( - qml_module - .rust_files - .iter() - .chain(qml_module.qrc_files.iter()), - ) { - println!("cargo:rerun-if-changed={}", path.display()); + // Now all necessary symbols should be included in the cc_builder. + // However, the plugin needs to be initialized at runtime. + // This is done through the plugin_init file. + // It needs to be linked as an object file, to ensure that the linker doesn't throw away + // the static initializers in this file. + // For CMake builds, we export this file to then later include it as an object library in + // CMake. + // In cargo builds, add the object file as a direct argument to the linker. + Self::build_object_file( + init_builder, + &qml_module_registration_files.plugin_init, + Some(( + &format!("plugins/{}", plugin_name_from_uri(&qml_module.uri)), + "plugin_init.o", + )), + ); } - - // Now all necessary symbols should be included in the cc_builder. - // However, the plugin needs to be initialized at runtime. - // This is done through the plugin_init file. - // It needs to be linked as an object file, to ensure that the linker doesn't throw away - // the static initializers in this file. - // For CMake builds, we export this file to then later include it as an object library in - // CMake. - // In cargo builds, add the object file as a direct argument to the linker. - Self::build_object_file( - init_builder, - &qml_module_registration_files.plugin_init, - Some(( - &format!("plugins/{}", plugin_name_from_uri(&qml_module.uri)), - "plugin_init.o", - )), - ); } fn setup_qt5_compatibility(init_builder: &cc::Build, qtbuild: &qt_build_utils::QtBuild) { @@ -770,6 +780,38 @@ impl CxxQtBuilder { } } + fn build_initializers(&mut self, init_builder: &cc::Build) { + let initializers_path = out_dir().join("cxxqt_initializers.cpp"); + std::fs::write(&initializers_path, self.initializers.join("\n")) + .expect("Could not write cxx_qt_initializers.cpp"); + Self::build_object_file( + init_builder, + initializers_path, + Some(("", "initializers.o")), + ); + } + + fn build_qrc_files(&mut self, init_builder: &cc::Build, qtbuild: &mut qt_build_utils::QtBuild) { + for qrc_file in &self.qrc_files { + let obj_file_dir = format!("qrc/{}", crate_name()); + let obj_file_name = format!( + "{}.o", + qrc_file.file_name().unwrap_or_default().to_string_lossy() + ); + // TODO: Is it correct to use an obj file here, or should this just link normally? + Self::build_object_file( + init_builder, + qtbuild.qrc(&qrc_file), + Some((&obj_file_dir, &*obj_file_name)), + ); + + // Also ensure that each of the files in the qrc can cause a change + for qrc_inner_file in qtbuild.qrc_list(&qrc_file) { + println!("cargo:rerun-if-changed={}", qrc_inner_file.display()); + } + } + } + /// Generate and compile cxx-qt C++ code, as well as compile any additional files from /// [CxxQtBuilder::qobject_header] and [CxxQtBuilder::cc_builder]. pub fn build(mut self) { @@ -819,37 +861,14 @@ impl CxxQtBuilder { // Bridges for QML modules are handled separately because // the metatypes_json generated by moc needs to be passed to qmltyperegistrar - let qml_modules: Vec<_> = self.qml_modules.drain(..).collect(); - for qml_module in &qml_modules { - self.build_qml_module( - qml_module, - &init_builder, - &mut qtbuild, - &generated_header_dir, - ); - } - - for qrc_file in self.qrc_files { - let obj_file_name = format!( - "{}.o", - qrc_file.file_name().unwrap_or_default().to_string_lossy() - ); - let obj_file_dir = format!("qrc/{}", crate_name()); - // TODO: Is it correct to use an obj file here, or should this just link normally? - Self::build_object_file( - &init_builder, - qtbuild.qrc(&qrc_file), - Some((&obj_file_dir, &*obj_file_name)), - ); + self.build_qml_modules(&init_builder, &mut qtbuild, &generated_header_dir); - // Also ensure that each of the files in the qrc can cause a change - for qrc_inner_file in qtbuild.qrc_list(&qrc_file) { - println!("cargo:rerun-if-changed={}", qrc_inner_file.display()); - } - } + self.build_qrc_files(&init_builder, &mut qtbuild); Self::setup_qt5_compatibility(&init_builder, &qtbuild); + self.build_initializers(&init_builder); + // Only compile if we have added files to the builder // otherwise we end up with no static library but ask cargo to link to it which causes an error if self.cc_builder.get_files().count() > 0 { diff --git a/crates/cxx-qt-build/src/opts.rs b/crates/cxx-qt-build/src/opts.rs index 4800dce47..1cbdb04d1 100644 --- a/crates/cxx-qt-build/src/opts.rs +++ b/crates/cxx-qt-build/src/opts.rs @@ -19,6 +19,8 @@ pub struct CxxQtBuildersOpts { pub(crate) headers: Vec<(String, String, String)>, /// Qt modules that are required pub(crate) qt_modules: HashSet, + /// Added initializer code required to be linked into a separate object file + pub(crate) initializers: Vec, } impl CxxQtBuildersOpts { @@ -46,6 +48,13 @@ impl CxxQtBuildersOpts { self.qt_modules.insert(module.to_owned()); self } + + /// Add initializer C++ code that must be compiled into an object file or linked with + /// whole-archive so that the linker doesn't optimize it out. + pub fn initializer(mut self, initializers: &str) -> Self { + self.initializers.push(initializers.to_owned()); + self + } } /// Options for qobject_headers diff --git a/crates/cxx-qt-lib-headers/include/core/init.cpp b/crates/cxx-qt-lib-headers/include/core/init.cpp new file mode 100644 index 000000000..670632b7d --- /dev/null +++ b/crates/cxx-qt-lib-headers/include/core/init.cpp @@ -0,0 +1,165 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#include +#include +#include +#include +#include + +static const int register_QHash_i32_QByteArray = + qRegisterMetaType<::QHash_i32_QByteArray>("QHash_i32_QByteArray"); +// Ensure that QHash (aka QVariantHash) is registered +// otherwise it cannot be used in QML +static const int register_QHash_QString_QVariant = + qRegisterMetaType<::QHash_QString_QVariant>("QHash_QString_QVariant"); + +static const int register_QList_bool = + qRegisterMetaType<::QList_bool>("QList_bool"); +static const int register_QList_f32 = + qRegisterMetaType<::QList_f32>("QList_f32"); +static const int register_QList_f64 = + qRegisterMetaType<::QList_f64>("QList_f64"); +static const int register_QList_i8 = qRegisterMetaType<::QList_i8>("QList_i8"); +static const int register_QList_i16 = + qRegisterMetaType<::QList_i16>("QList_i16"); +static const int register_QList_i32 = + qRegisterMetaType<::QList_i32>("QList_i32"); +static const int register_QList_i64 = + qRegisterMetaType<::QList_i64>("QList_i64"); +static const int register_QList_QByteArray = + qRegisterMetaType<::QList_QByteArray>("QList_QByteArray"); +static const int register_QList_QDate = + qRegisterMetaType<::QList_QDate>("QList_QDate"); +static const int register_QList_QDateTime = + qRegisterMetaType<::QList_QDateTime>("QList_QDateTime"); +static const int register_QList_QMargins = + qRegisterMetaType<::QList_QMargins>("QList_QMargins"); +static const int register_QList_QMarginsF = + qRegisterMetaType<::QList_QMarginsF>("QList_QMarginsF"); +static const int register_QList_QPersistentModelIndex = + qRegisterMetaType<::QList_QPersistentModelIndex>( + "QList_QPersistentModelIndex"); +static const int register_QList_QPoint = + qRegisterMetaType<::QList_QPoint>("QList_QPoint"); +static const int register_QList_QPointF = + qRegisterMetaType<::QList_QPointF>("QList_QPointF"); +static const int register_QList_QRect = + qRegisterMetaType<::QList_QRect>("QList_QRect"); +static const int register_QList_QRectF = + qRegisterMetaType<::QList_QRectF>("QList_QRectF"); +static const int register_QList_QSize = + qRegisterMetaType<::QList_QSize>("QList_QSize"); +static const int register_QList_QSizeF = + qRegisterMetaType<::QList_QSizeF>("QList_QSizeF"); +static const int register_QList_QString = + qRegisterMetaType<::QList_QString>("QList_QString"); +static const int register_QList_QTime = + qRegisterMetaType<::QList_QTime>("QList_QTime"); +static const int register_QList_QUrl = + qRegisterMetaType<::QList_QUrl>("QList_QUrl"); +// Ensure that QList (aka QVariantList) is registered +// otherwise it cannot be used in QML +static const int register_QList_QVariant = + qRegisterMetaType<::QList_QVariant>("QList_QVariant"); +static const int register_QList_u8 = qRegisterMetaType<::QList_u8>("QList_u8"); +static const int register_QList_u16 = + qRegisterMetaType<::QList_u16>("QList_u16"); +static const int register_QList_u32 = + qRegisterMetaType<::QList_u32>("QList_u32"); +static const int register_QList_u64 = + qRegisterMetaType<::QList_u64>("QList_u64"); + +// Ensure that QMap (aka QVariantMap) is registered +// otherwise it cannot be used in QML +static const int register_QMap_QString_QVariant = + qRegisterMetaType<::QMap_QString_QVariant>("QMap_QString_QVariant"); + +static const int register_QSet_bool = + qRegisterMetaType<::QSet_bool>("QSet_bool"); +static const int register_QSet_f32 = qRegisterMetaType<::QSet_f32>("QSet_f32"); +static const int register_QSet_f64 = qRegisterMetaType<::QSet_f64>("QSet_f64"); +static const int register_QSet_i8 = qRegisterMetaType<::QSet_i8>("QSet_i8"); +static const int register_QSet_i16 = qRegisterMetaType<::QSet_i16>("QSet_i16"); +static const int register_QSet_i32 = qRegisterMetaType<::QSet_i32>("QSet_i32"); +static const int register_QSet_i64 = qRegisterMetaType<::QSet_i64>("QSet_i64"); +static const int register_QSet_QByteArray = + qRegisterMetaType<::QSet_QByteArray>("QSet_QByteArray"); +static const int register_QSet_QDate = + qRegisterMetaType<::QSet_QDate>("QSet_QDate"); +static const int register_QSet_QDateTime = + qRegisterMetaType<::QSet_QDateTime>("QSet_QDateTime"); +static const int register_QSet_QPersistentModelIndex = + qRegisterMetaType<::QSet_QPersistentModelIndex>("QSet_QPersistentModelIndex"); +static const int register_QSet_QString = + qRegisterMetaType<::QSet_QString>("QSet_QString"); +static const int register_QSet_QTime = + qRegisterMetaType<::QSet_QTime>("QSet_QTime"); +static const int register_QSet_QUrl = + qRegisterMetaType<::QSet_QUrl>("QSet_QUrl"); +static const int register_QSet_u8 = qRegisterMetaType<::QSet_u8>("QSet_u8"); +static const int register_QSet_u16 = qRegisterMetaType<::QSet_u16>("QSet_u16"); +static const int register_QSet_u32 = qRegisterMetaType<::QSet_u32>("QSet_u32"); +static const int register_QSet_u64 = qRegisterMetaType<::QSet_u64>("QSet_u64"); + +static const int register_QVector_bool = + qRegisterMetaType<::QVector_bool>("QVector_bool"); +static const int register_QVector_f32 = + qRegisterMetaType<::QVector_f32>("QVector_f32"); +static const int register_QVector_f64 = + qRegisterMetaType<::QVector_f64>("QVector_f64"); +static const int register_QVector_i8 = + qRegisterMetaType<::QVector_i8>("QVector_i8"); +static const int register_QVector_i16 = + qRegisterMetaType<::QVector_i16>("QVector_i16"); +static const int register_QVector_i32 = + qRegisterMetaType<::QVector_i32>("QVector_i32"); +static const int register_QVector_i64 = + qRegisterMetaType<::QVector_i64>("QVector_i64"); +static const int register_QVector_QByteArray = + qRegisterMetaType<::QVector_QByteArray>("QVector_QByteArray"); +static const int register_QVector_QDate = + qRegisterMetaType<::QVector_QDate>("QVector_QDate"); +static const int register_QVector_QDateTime = + qRegisterMetaType<::QVector_QDateTime>("QVector_QDateTime"); +static const int register_QVector_QMargins = + qRegisterMetaType<::QVector_QMargins>("QVector_QMargins"); +static const int register_QVector_QMarginsF = + qRegisterMetaType<::QVector_QMarginsF>("QVector_QMarginsF"); +static const int register_QVector_QPersistentModelIndex = + qRegisterMetaType<::QVector_QPersistentModelIndex>( + "QVector_QPersistentModelIndex"); +static const int register_QVector_QPoint = + qRegisterMetaType<::QVector_QPoint>("QVector_QPoint"); +static const int register_QVector_QPointF = + qRegisterMetaType<::QVector_QPointF>("QVector_QPointF"); +static const int register_QVector_QRect = + qRegisterMetaType<::QVector_QRect>("QVector_QRect"); +static const int register_QVector_QRectF = + qRegisterMetaType<::QVector_QRectF>("QVector_QRectF"); +static const int register_QVector_QSize = + qRegisterMetaType<::QVector_QSize>("QVector_QSize"); +static const int register_QVector_QSizeF = + qRegisterMetaType<::QVector_QSizeF>("QVector_QSizeF"); +static const int register_QVector_QString = + qRegisterMetaType<::QVector_QString>("QVector_QString"); +static const int register_QVector_QTime = + qRegisterMetaType<::QVector_QTime>("QVector_QTime"); +static const int register_QVector_QUrl = + qRegisterMetaType<::QVector_QUrl>("QVector_QUrl"); +// Ensure that QVector (aka QVariantList) is registered +// otherwise it cannot be used in QML +static const int register_QVector_QVariant = + qRegisterMetaType<::QVector_QVariant>("QVector_QVariant"); +static const int register_QVector_u8 = + qRegisterMetaType<::QVector_u8>("QVector_u8"); +static const int register_QVector_u16 = + qRegisterMetaType<::QVector_u16>("QVector_u16"); +static const int register_QVector_u32 = + qRegisterMetaType<::QVector_u32>("QVector_u32"); +static const int register_QVector_u64 = + qRegisterMetaType<::QVector_u64>("QVector_u64"); diff --git a/crates/cxx-qt-lib-headers/include/gui/init.cpp b/crates/cxx-qt-lib-headers/include/gui/init.cpp new file mode 100644 index 000000000..538edf5c1 --- /dev/null +++ b/crates/cxx-qt-lib-headers/include/gui/init.cpp @@ -0,0 +1,14 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#include +#include + +static const int register_QList_QColor = + qRegisterMetaType<::QList_QColor>("QList_QColor"); +static const int register_QVector_QColor = + qRegisterMetaType<::QVector_QColor>("QVector_QColor"); diff --git a/crates/cxx-qt-lib-headers/src/lib.rs b/crates/cxx-qt-lib-headers/src/lib.rs index 1c47f6803..9640bf6d3 100644 --- a/crates/cxx-qt-lib-headers/src/lib.rs +++ b/crates/cxx-qt-lib-headers/src/lib.rs @@ -110,9 +110,14 @@ pub fn build_opts() -> cxx_qt_build::CxxQtBuildersOpts { opts = opts.header(file_contents, "cxx-qt-lib", file_name); } + opts = opts.initializer(include_str!("../include/core/init.cpp")); + #[cfg(feature = "qt_gui")] { - opts = opts.define("CXX_QT_GUI_FEATURE").qt_module("Gui"); + opts = opts + .define("CXX_QT_GUI_FEATURE") + .qt_module("Gui") + .initializer(include_str!("../include/gui/init.cpp")); } #[cfg(feature = "qt_qml")] diff --git a/crates/cxx-qt-lib/src/core/qhash/qhash.cpp b/crates/cxx-qt-lib/src/core/qhash/qhash.cpp index 089513908..20c3a20b1 100644 --- a/crates/cxx-qt-lib/src/core/qhash/qhash.cpp +++ b/crates/cxx-qt-lib/src/core/qhash/qhash.cpp @@ -28,10 +28,3 @@ CXX_QT_QHASH_ASSERTS(QString, QVariant, QString_QVariant); CXX_QT_QHASH_ASSERTS(::std::int32_t, QByteArray, i32_QByteArray); - -static const int register_QHash_i32_QByteArray = - qRegisterMetaType<::QHash_i32_QByteArray>("QHash_i32_QByteArray"); -// Ensure that QHash (aka QVariantHash) is registered -// otherwise it cannot be used in QML -static const int register_QHash_QString_QVariant = - qRegisterMetaType<::QHash_QString_QVariant>("QHash_QString_QVariant"); diff --git a/crates/cxx-qt-lib/src/core/qlist/qlist.cpp b/crates/cxx-qt-lib/src/core/qlist/qlist.cpp index 4644aff17..df797c6de 100644 --- a/crates/cxx-qt-lib/src/core/qlist/qlist.cpp +++ b/crates/cxx-qt-lib/src/core/qlist/qlist.cpp @@ -60,63 +60,3 @@ CXX_QT_QLIST_ASSERTS(::std::uint8_t, u8); CXX_QT_QLIST_ASSERTS(::std::uint16_t, u16); CXX_QT_QLIST_ASSERTS(::std::uint32_t, u32); CXX_QT_QLIST_ASSERTS(::std::uint64_t, u64); - -static const int register_QList_bool = - qRegisterMetaType<::QList_bool>("QList_bool"); -static const int register_QList_f32 = - qRegisterMetaType<::QList_f32>("QList_f32"); -static const int register_QList_f64 = - qRegisterMetaType<::QList_f64>("QList_f64"); -static const int register_QList_i8 = qRegisterMetaType<::QList_i8>("QList_i8"); -static const int register_QList_i16 = - qRegisterMetaType<::QList_i16>("QList_i16"); -static const int register_QList_i32 = - qRegisterMetaType<::QList_i32>("QList_i32"); -static const int register_QList_i64 = - qRegisterMetaType<::QList_i64>("QList_i64"); -static const int register_QList_QByteArray = - qRegisterMetaType<::QList_QByteArray>("QList_QByteArray"); -#ifdef CXX_QT_GUI_FEATURE -static const int register_QList_QColor = - qRegisterMetaType<::QList_QColor>("QList_QColor"); -#endif -static const int register_QList_QDate = - qRegisterMetaType<::QList_QDate>("QList_QDate"); -static const int register_QList_QDateTime = - qRegisterMetaType<::QList_QDateTime>("QList_QDateTime"); -static const int register_QList_QMargins = - qRegisterMetaType<::QList_QMargins>("QList_QMargins"); -static const int register_QList_QMarginsF = - qRegisterMetaType<::QList_QMarginsF>("QList_QMarginsF"); -static const int register_QList_QPersistentModelIndex = - qRegisterMetaType<::QList_QPersistentModelIndex>( - "QList_QPersistentModelIndex"); -static const int register_QList_QPoint = - qRegisterMetaType<::QList_QPoint>("QList_QPoint"); -static const int register_QList_QPointF = - qRegisterMetaType<::QList_QPointF>("QList_QPointF"); -static const int register_QList_QRect = - qRegisterMetaType<::QList_QRect>("QList_QRect"); -static const int register_QList_QRectF = - qRegisterMetaType<::QList_QRectF>("QList_QRectF"); -static const int register_QList_QSize = - qRegisterMetaType<::QList_QSize>("QList_QSize"); -static const int register_QList_QSizeF = - qRegisterMetaType<::QList_QSizeF>("QList_QSizeF"); -static const int register_QList_QString = - qRegisterMetaType<::QList_QString>("QList_QString"); -static const int register_QList_QTime = - qRegisterMetaType<::QList_QTime>("QList_QTime"); -static const int register_QList_QUrl = - qRegisterMetaType<::QList_QUrl>("QList_QUrl"); -// Ensure that QList (aka QVariantList) is registered -// otherwise it cannot be used in QML -static const int register_QList_QVariant = - qRegisterMetaType<::QList_QVariant>("QList_QVariant"); -static const int register_QList_u8 = qRegisterMetaType<::QList_u8>("QList_u8"); -static const int register_QList_u16 = - qRegisterMetaType<::QList_u16>("QList_u16"); -static const int register_QList_u32 = - qRegisterMetaType<::QList_u32>("QList_u32"); -static const int register_QList_u64 = - qRegisterMetaType<::QList_u64>("QList_u64"); diff --git a/crates/cxx-qt-lib/src/core/qmap/qmap.cpp b/crates/cxx-qt-lib/src/core/qmap/qmap.cpp index 6e059cf69..e4270b41a 100644 --- a/crates/cxx-qt-lib/src/core/qmap/qmap.cpp +++ b/crates/cxx-qt-lib/src/core/qmap/qmap.cpp @@ -27,8 +27,3 @@ static_assert(::std::is_copy_constructible::value); CXX_QT_QMAP_ASSERTS(QString, QVariant, QString_QVariant); - -// Ensure that QMap (aka QVariantMap) is registered -// otherwise it cannot be used in QML -static const int register_QMap_QString_QVariant = - qRegisterMetaType<::QMap_QString_QVariant>("QMap_QString_QVariant"); diff --git a/crates/cxx-qt-lib/src/core/qset/qset.cpp b/crates/cxx-qt-lib/src/core/qset/qset.cpp index 5e345d720..28b97fb6c 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset.cpp +++ b/crates/cxx-qt-lib/src/core/qset/qset.cpp @@ -39,30 +39,3 @@ CXX_QT_QSET_ASSERTS(::std::uint8_t, u8); CXX_QT_QSET_ASSERTS(::std::uint16_t, u16); CXX_QT_QSET_ASSERTS(::std::uint32_t, u32); CXX_QT_QSET_ASSERTS(::std::uint64_t, u64); - -static const int register_QSet_bool = - qRegisterMetaType<::QSet_bool>("QSet_bool"); -static const int register_QSet_f32 = qRegisterMetaType<::QSet_f32>("QSet_f32"); -static const int register_QSet_f64 = qRegisterMetaType<::QSet_f64>("QSet_f64"); -static const int register_QSet_i8 = qRegisterMetaType<::QSet_i8>("QSet_i8"); -static const int register_QSet_i16 = qRegisterMetaType<::QSet_i16>("QSet_i16"); -static const int register_QSet_i32 = qRegisterMetaType<::QSet_i32>("QSet_i32"); -static const int register_QSet_i64 = qRegisterMetaType<::QSet_i64>("QSet_i64"); -static const int register_QSet_QByteArray = - qRegisterMetaType<::QSet_QByteArray>("QSet_QByteArray"); -static const int register_QSet_QDate = - qRegisterMetaType<::QSet_QDate>("QSet_QDate"); -static const int register_QSet_QDateTime = - qRegisterMetaType<::QSet_QDateTime>("QSet_QDateTime"); -static const int register_QSet_QPersistentModelIndex = - qRegisterMetaType<::QSet_QPersistentModelIndex>("QSet_QPersistentModelIndex"); -static const int register_QSet_QString = - qRegisterMetaType<::QSet_QString>("QSet_QString"); -static const int register_QSet_QTime = - qRegisterMetaType<::QSet_QTime>("QSet_QTime"); -static const int register_QSet_QUrl = - qRegisterMetaType<::QSet_QUrl>("QSet_QUrl"); -static const int register_QSet_u8 = qRegisterMetaType<::QSet_u8>("QSet_u8"); -static const int register_QSet_u16 = qRegisterMetaType<::QSet_u16>("QSet_u16"); -static const int register_QSet_u32 = qRegisterMetaType<::QSet_u32>("QSet_u32"); -static const int register_QSet_u64 = qRegisterMetaType<::QSet_u64>("QSet_u64"); diff --git a/crates/cxx-qt-lib/src/core/qvector/qvector.cpp b/crates/cxx-qt-lib/src/core/qvector/qvector.cpp index 1e1332046..f1ae8617a 100644 --- a/crates/cxx-qt-lib/src/core/qvector/qvector.cpp +++ b/crates/cxx-qt-lib/src/core/qvector/qvector.cpp @@ -61,65 +61,3 @@ CXX_QT_QVECTOR_ASSERTS(::std::uint8_t, u8); CXX_QT_QVECTOR_ASSERTS(::std::uint16_t, u16); CXX_QT_QVECTOR_ASSERTS(::std::uint32_t, u32); CXX_QT_QVECTOR_ASSERTS(::std::uint64_t, u64); - -static const int register_QVector_bool = - qRegisterMetaType<::QVector_bool>("QVector_bool"); -static const int register_QVector_f32 = - qRegisterMetaType<::QVector_f32>("QVector_f32"); -static const int register_QVector_f64 = - qRegisterMetaType<::QVector_f64>("QVector_f64"); -static const int register_QVector_i8 = - qRegisterMetaType<::QVector_i8>("QVector_i8"); -static const int register_QVector_i16 = - qRegisterMetaType<::QVector_i16>("QVector_i16"); -static const int register_QVector_i32 = - qRegisterMetaType<::QVector_i32>("QVector_i32"); -static const int register_QVector_i64 = - qRegisterMetaType<::QVector_i64>("QVector_i64"); -static const int register_QVector_QByteArray = - qRegisterMetaType<::QVector_QByteArray>("QVector_QByteArray"); -#ifdef CXX_QT_GUI_FEATURE -static const int register_QVector_QColor = - qRegisterMetaType<::QVector_QColor>("QVector_QColor"); -#endif -static const int register_QVector_QDate = - qRegisterMetaType<::QVector_QDate>("QVector_QDate"); -static const int register_QVector_QDateTime = - qRegisterMetaType<::QVector_QDateTime>("QVector_QDateTime"); -static const int register_QVector_QMargins = - qRegisterMetaType<::QVector_QMargins>("QVector_QMargins"); -static const int register_QVector_QMarginsF = - qRegisterMetaType<::QVector_QMarginsF>("QVector_QMarginsF"); -static const int register_QVector_QPersistentModelIndex = - qRegisterMetaType<::QVector_QPersistentModelIndex>( - "QVector_QPersistentModelIndex"); -static const int register_QVector_QPoint = - qRegisterMetaType<::QVector_QPoint>("QVector_QPoint"); -static const int register_QVector_QPointF = - qRegisterMetaType<::QVector_QPointF>("QVector_QPointF"); -static const int register_QVector_QRect = - qRegisterMetaType<::QVector_QRect>("QVector_QRect"); -static const int register_QVector_QRectF = - qRegisterMetaType<::QVector_QRectF>("QVector_QRectF"); -static const int register_QVector_QSize = - qRegisterMetaType<::QVector_QSize>("QVector_QSize"); -static const int register_QVector_QSizeF = - qRegisterMetaType<::QVector_QSizeF>("QVector_QSizeF"); -static const int register_QVector_QString = - qRegisterMetaType<::QVector_QString>("QVector_QString"); -static const int register_QVector_QTime = - qRegisterMetaType<::QVector_QTime>("QVector_QTime"); -static const int register_QVector_QUrl = - qRegisterMetaType<::QVector_QUrl>("QVector_QUrl"); -// Ensure that QVector (aka QVariantList) is registered -// otherwise it cannot be used in QML -static const int register_QVector_QVariant = - qRegisterMetaType<::QVector_QVariant>("QVector_QVariant"); -static const int register_QVector_u8 = - qRegisterMetaType<::QVector_u8>("QVector_u8"); -static const int register_QVector_u16 = - qRegisterMetaType<::QVector_u16>("QVector_u16"); -static const int register_QVector_u32 = - qRegisterMetaType<::QVector_u32>("QVector_u32"); -static const int register_QVector_u64 = - qRegisterMetaType<::QVector_u64>("QVector_u64"); From 213902a4f67b5643ff2f13c51a0ea463b8515075 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Thu, 20 Jun 2024 16:42:36 +0200 Subject: [PATCH 17/20] Revert "fix: CI: Force use of Rust 1.77 (#957)" This reverts commit 40f5ad1582a856d9857a64a284abcf4bed8b8a7b. We are now building and linking object files directly, which works nicely with CMake and seems to work for Rust as well. --- .github/workflows/github-cxx-qt-tests.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/github-cxx-qt-tests.yml b/.github/workflows/github-cxx-qt-tests.yml index 24f9b9cd1..b4baead5c 100644 --- a/.github/workflows/github-cxx-qt-tests.yml +++ b/.github/workflows/github-cxx-qt-tests.yml @@ -193,13 +193,8 @@ jobs: # Ensure clippy and rustfmt is installed, they should come from github runner # # Note we still need rustfmt for the cxx-qt-gen tests - # - # TODO: Remove the `rustup default 1.77` selection. - # This is a workaround for Github Actions, which cannot currently compile CXX-Qt with Rust 1.78. - # - # See: https://github.com/KDAB/cxx-qt/issues/958 - name: "Install Rust toolchain" - run: rustup default 1.77 && rustup component add clippy rustfmt + run: rustup component add clippy rustfmt - name: "Rust tools cache" uses: actions/cache@v4 From 1757f0924840e092a4fc616a41477b4725e1ec87 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 21 Jun 2024 08:48:59 +0200 Subject: [PATCH 18/20] clippy: Allow missing_safety_doc in nested_qobjects This may be a clippy bug, as `cargo expand` tells me the generated functions do indeed have a `# Safety` section in their documentation. The `# Safety` sections also show up in the output of `cargo doc`. So it's unclear why clippy is complaining. --- examples/qml_features/rust/src/nested_qobjects.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs index 5e9e0ae69..dfa805165 100644 --- a/examples/qml_features/rust/src/nested_qobjects.rs +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -5,6 +5,10 @@ //! This example shows how a pointer from one Rust defined QObject to another Rust defined QObject can be used +// Currently, there seems to be a clippy bug, that says the `called` signals don't have `# Safety` +// docs, which they do have. So we disable the warning for now. +#![allow(clippy::missing_safety_doc)] + /// A CXX-Qt bridge which shows how a pointer from one Rust defined QObject to another Rust defined QObject can be used // ANCHOR: book_macro_code #[cxx_qt::bridge(cxx_file_stem = "nested_qobjects")] @@ -45,6 +49,10 @@ pub mod qobject { unsafe extern "RustQt" { /// Print the count of the given inner QObject + /// + /// # Safety + /// + /// As we deref a pointer in a public method this needs to be marked as unsafe #[qinvokable] unsafe fn print_count(self: Pin<&mut OuterObject>, inner: *mut InnerObject); From 193bb66a7aeb2428857fc14fec75ccf2363a489d Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 21 Jun 2024 09:40:31 +0200 Subject: [PATCH 19/20] cxx-qt-build: Include Qt5 support in initializers This way we don't need a separate object file and CMake target for this. --- cmake/CxxQt.cmake | 25 ++++++++----------------- crates/cxx-qt-build/src/lib.rs | 22 +++++++--------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/cmake/CxxQt.cmake b/cmake/CxxQt.cmake index dbfb17343..96f3aeac2 100644 --- a/cmake/CxxQt.cmake +++ b/cmake/CxxQt.cmake @@ -37,12 +37,6 @@ function(cxxqt_import_crate) endif() endif() - if (Qt5_FOUND AND (NOT TARGET CXXQT_QT5_COMPATIBILITY)) - message(VERBOSE "CXX-QT detected Qt5 - Adding compatibility target: CXXQT_QT5_COMPATIBILITY") - add_library(CXXQT_QT5_COMPATIBILITY OBJECT ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../crates/cxx-qt-build/src/std_types_qt5.cpp) - target_link_libraries(CXXQT_QT5_COMPATIBILITY PRIVATE Qt5::Core) - endif() - foreach(CRATE ${__cxxqt_imported_crates}) corrosion_set_env_vars(${CRATE} "CXXQT_EXPORT_DIR=${IMPORT_CRATE_CXXQT_EXPORT_DIR}" @@ -56,17 +50,10 @@ function(cxxqt_import_crate) PROPERTIES CXXQT_EXPORT_DIR "${IMPORT_CRATE_CXXQT_EXPORT_DIR}") - if (Qt5_FOUND AND (QT_DEFAULT_MAJOR_VERSION EQUAL 5)) - message(VERBOSE "CXX-QT - Linking ${CRATE} to compatibility target: CXXQT_QT5_COMPATIBILITY") - - # Note that we need to link using TARGET_OBJECTS, so that the object files are included **transitively**, otherwise - # Only the linker flags from the compatibility target would be included, but not the actual object files. - # See also the "Linking Object Libraries" and "Linking Object Libraries via $" sections: - # https://cmake.org/cmake/help/latest/command/target_link_libraries.html - target_link_libraries(${CRATE} INTERFACE CXXQT_QT5_COMPATIBILITY $) - endif() - - + # When using the Ninja generator, we need to provide **some** way to generate the object file + # Unfortunately I'm not able to tell corrosion that this obj file is indeed a byproduct, so + # create a fake target for it. + # This target doesn't need to do anything, because the file should already exist after building the crate. add_custom_target(${CRATE}_mock_initializers COMMAND ${CMAKE_COMMAND} -E true DEPENDS ${CRATE} @@ -76,6 +63,10 @@ function(cxxqt_import_crate) set_target_properties(${CRATE}_initializers PROPERTIES IMPORTED_OBJECTS "${IMPORT_CRATE_CXXQT_EXPORT_DIR}/initializers.o") + # Note that we need to link using TARGET_OBJECTS, so that the object files are included **transitively**, otherwise + # Only the linker flags from the object library would be included, but not the actual object files. + # See also the "Linking Object Libraries" and "Linking Object Libraries via $" sections: + # https://cmake.org/cmake/help/latest/command/target_link_libraries.html target_link_libraries(${CRATE} INTERFACE ${CRATE}_initializers $) message(VERBOSE "CXX-Qt Expects QML plugin: ${QML_MODULE_URI} in directory: ${QML_MODULE_PLUGIN_DIR}") diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 063e0abc7..45cd8bcc0 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -750,16 +750,15 @@ impl CxxQtBuilder { } } - fn setup_qt5_compatibility(init_builder: &cc::Build, qtbuild: &qt_build_utils::QtBuild) { + fn setup_qt5_compatibility(&mut self, qtbuild: &qt_build_utils::QtBuild) { // If we are using Qt 5 then write the std_types source // This registers std numbers as a type for use in QML // - // Note that we need this to be compiled into the whole_archive builder + // Note that we need this to be compiled into an object file // as they are stored in statics in the source. // - // TODO: once +whole-archive and +bundle are allowed together in rlibs - // we should be able to move this into cxx-qt so that it's only built - // once rather than for every cxx-qt-build. When this happens also + // TODO: Can we move this into cxx-qt so that it's only built + // once rather than for every cxx-qt-build? When we do this // ensure that in a multi project that numbers work everywhere. // // Also then it should be possible to use CARGO_MANIFEST_DIR/src/std_types_qt5.cpp @@ -768,15 +767,8 @@ impl CxxQtBuilder { // https://github.com/rust-lang/rust/issues/108081 // https://github.com/KDAB/cxx-qt/pull/598 if qtbuild.version().major == 5 { - let std_types_contents = include_str!("std_types_qt5.cpp"); - let std_types_path = format!( - "{out_dir}/std_types_qt5.cpp", - out_dir = env::var("OUT_DIR").unwrap() - ); - std::fs::write(&std_types_path, std_types_contents) - .expect("Could not write std_types source"); - - Self::build_object_file(init_builder, std_types_path, None); + self.initializers + .push(include_str!("std_types_qt5.cpp").to_owned()); } } @@ -865,7 +857,7 @@ impl CxxQtBuilder { self.build_qrc_files(&init_builder, &mut qtbuild); - Self::setup_qt5_compatibility(&init_builder, &qtbuild); + self.setup_qt5_compatibility(&qtbuild); self.build_initializers(&init_builder); From c9d2c5d462ae064be2a0eb0c825ee26a991d3952 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 21 Jun 2024 13:37:50 +0200 Subject: [PATCH 20/20] Export qmltypes and qmldir for each plugin --- crates/cxx-qt-build/src/lib.rs | 24 +++++++++++++++++++----- crates/qt-build-utils/src/lib.rs | 27 ++++++++++++++++++--------- examples/qml_features/.qmlls.ini | 3 +++ 3 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 examples/qml_features/.qmlls.ini diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 45cd8bcc0..0cfd0cba4 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -718,7 +718,7 @@ impl CxxQtBuilder { for qmlcachegen_file in qml_module_registration_files.qmlcachegen { self.cc_builder.file(qmlcachegen_file); } - // This is required, as described here: plugin_builder + // This is required, as described here: https://doc.qt.io/qt-6/plugins-howto.html#creating-static-plugins self.cc_builder.define("QT_STATICPLUGIN", None); // If any of the files inside the qml module change, then trigger a rerun @@ -731,6 +731,23 @@ impl CxxQtBuilder { println!("cargo:rerun-if-changed={}", path.display()); } + // Export the .qmltypes and qmldir files into a stable path, so that tools like + // qmllint/qmlls can find them. + let plugin_dir_name = format!("plugins/{}", plugin_name_from_uri(&qml_module.uri)); + if let Some(export_dir) = export_dir() { + let plugin_dir = export_dir.join(&plugin_dir_name); + std::fs::create_dir_all(&plugin_dir).expect("Could not create plugin directory"); + std::fs::copy( + qml_module_registration_files.qmltypes, + plugin_dir.join("plugin.qmltypes"), + ) + .expect("Could not copy plugin.qmltypes to export directory"); + std::fs::copy( + qml_module_registration_files.qmldir, + plugin_dir.join("qmldir"), + ) + .expect("Could not copy qmldir to export directory"); + } // Now all necessary symbols should be included in the cc_builder. // However, the plugin needs to be initialized at runtime. // This is done through the plugin_init file. @@ -742,10 +759,7 @@ impl CxxQtBuilder { Self::build_object_file( init_builder, &qml_module_registration_files.plugin_init, - Some(( - &format!("plugins/{}", plugin_name_from_uri(&qml_module.uri)), - "plugin_init.o", - )), + Some((&plugin_dir_name, "plugin_init.o")), ); } } diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 4bd42e4e8..1dc6b758a 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -174,6 +174,12 @@ pub struct QmlModuleRegistrationFiles { pub qmlcachegen: Vec, /// File generated by [qmltyperegistrar](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) CLI tool. pub qmltyperegistrar: PathBuf, + /// The .qmltypes file generated by [qmltyperegistrar](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) CLI tool. + /// Mostly used for IDE support (e.g. qmllint/qmlls). + pub qmltypes: PathBuf, + /// qmldir file path. + /// Mostly used for better qmllint/qmlls support. + pub qmldir: PathBuf, /// File with generated [QQmlEngineExtensionPlugin](https://doc.qt.io/qt-6/qqmlengineextensionplugin.html) that calls the function generated by qmltyperegistrar. pub plugin: PathBuf, /// File that automatically registers the QQmlExtensionPlugin at startup. Must be linked with `+whole-archive`. @@ -692,15 +698,16 @@ impl QtBuild { // Generate qmldir file let qmldir_file_path = format!("{qml_module_dir}/qmldir"); { - let mut qmldir = File::create(&qmldir_file_path).expect("Could not create qmldir file"); - write!( - qmldir, - "module {uri} + std::fs::write( + &qmldir_file_path, + format!( + "module {uri} optional plugin {plugin_name} classname {plugin_class_name} typeinfo plugin.qmltypes prefer :/qt/qml/{qml_uri_dirs}/ -" +", + ), ) .expect("Could not write qmldir file"); } @@ -831,13 +838,13 @@ prefer :/qt/qml/{qml_uri_dirs}/ } // Run qmltyperegistrar - let qmltyperegistrar_output_path = PathBuf::from(&format!( + let qmltyperegistrar_cpp_output = PathBuf::from(&format!( "{out_dir}/{qml_uri_underscores}_qmltyperegistration.cpp" )); { let mut args = vec![ "--generate-qmltypes".to_string(), - qmltypes_path, + qmltypes_path.clone(), "--major-version".to_string(), version_major.to_string(), "--minor-version".to_string(), @@ -845,7 +852,7 @@ prefer :/qt/qml/{qml_uri_dirs}/ "--import-name".to_string(), uri.to_string(), "-o".to_string(), - qmltyperegistrar_output_path.to_string_lossy().to_string(), + qmltyperegistrar_cpp_output.to_string_lossy().to_string(), ]; args.extend( metatypes_json @@ -945,7 +952,9 @@ Q_IMPORT_PLUGIN({plugin_class_name}); QmlModuleRegistrationFiles { rcc: self.qrc(&qrc_path), qmlcachegen: qmlcachegen_file_paths, - qmltyperegistrar: qmltyperegistrar_output_path, + qmltyperegistrar: qmltyperegistrar_cpp_output, + qmltypes: qmltypes_path.into(), + qmldir: qmldir_file_path.into(), plugin: qml_plugin_cpp_path, plugin_init: qml_plugin_init_path, } diff --git a/examples/qml_features/.qmlls.ini b/examples/qml_features/.qmlls.ini new file mode 100644 index 000000000..6183730a3 --- /dev/null +++ b/examples/qml_features/.qmlls.ini @@ -0,0 +1,3 @@ +[General] +buildDir=../../build +no-cmake-calls=true