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 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..96f3aeac2 --- /dev/null +++ b/cmake/CxxQt.cmake @@ -0,0 +1,118 @@ +# 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 "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/") + 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}") + + # 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} + 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") + # 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}") + endforeach() + +endfunction() + + +function(cxxqt_import_qml_module target) + 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) + 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!") + 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 target + # 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 + IMPORTED_OBJECTS "${QML_MODULE_PLUGIN_DIR}/plugin_init.o") + target_link_libraries(${target} INTERFACE ${QML_MODULE_SOURCE_CRATE}) + + 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 ae7c28af1..0cfd0cba4 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, @@ -234,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, @@ -255,13 +256,35 @@ fn generate_cxxqt_cpp_files( generated_file_paths } +/// 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(PathBuf::from) +} + +fn out_dir() -> PathBuf { + env::var("OUT_DIR").unwrap().into() +} + +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 { - match env::var("CXXQT_EXPORT_DIR") { - Ok(export_dir) => format!("{export_dir}/{}", env::var("CARGO_PKG_NAME").unwrap()), - Err(_) => 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 { + env::var("CARGO_PKG_NAME").unwrap() +} + +fn static_lib_name() -> String { + format!("{}-cxxqt-generated", crate_name()) } fn panic_duplicate_file_and_qml_module( @@ -314,6 +337,7 @@ pub struct CxxQtBuilder { qml_modules: Vec, cc_builder: cc::Build, extra_defines: HashSet, + initializers: Vec, } impl CxxQtBuilder { @@ -329,6 +353,7 @@ impl CxxQtBuilder { qml_modules: vec![], cc_builder: cc::Build::new(), extra_defines: HashSet::new(), + initializers: Vec::new(), } } @@ -453,19 +478,25 @@ 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).unwrap_or_else(|_| { + panic!( + "Could not write header: {h_path}", + h_path = h_path.to_string_lossy() + ) + }); } // 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); @@ -496,19 +527,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(), @@ -518,7 +537,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 @@ -536,110 +555,129 @@ 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 cc_builder_whole_archive = 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 cc_builder_whole_archive] { - // 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); - } + for include_path in include_paths { + builder.include(include_path); + } + } - builder.includes(qtbuild.include_paths()); - builder.include(&header_root); - builder.include(&generated_header_dir); + fn moc_qobject_headers(&mut self, qtbuild: &mut qt_build_utils::QtBuild) { + for QObjectHeaderOpts { + path, + moc_arguments, + } in &self.qobject_headers + { + let moc_products = qtbuild.moc(path, moc_arguments.clone()); + self.cc_builder.file(moc_products.cpp); } + } - // Generate files - for files in generate_cxxqt_cpp_files(&self.rust_sources, &generated_header_dir) { + 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()); } } + } - // Run moc on C++ headers with Q_OBJECT macro - for QObjectHeaderOpts { - path, - moc_arguments, - } in self.qobject_headers - { - let moc_products = qtbuild.moc(&path, moc_arguments); - self.cc_builder.file(moc_products.cpp); + 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 { + 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!( + "CXX-Qt internal error: Expected only one object file out of cc::Build! Got {}", + obj_files.len() + ); } + } - let mut cc_builder_whole_archive_files_added = false; - - let lib_name = "cxx-qt-generated"; - - // 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 { + fn build_qml_modules( + &mut self, + init_builder: &cc::Build, + qtbuild: &mut qt_build_utils::QtBuild, + generated_header_dir: impl AsRef, + ) { + 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) { @@ -661,20 +699,27 @@ impl CxxQtBuilder { &qml_module.uri, qml_module.version_major, qml_module.version_minor, - lib_name, + // 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); - cc_builder_whole_archive.file(qml_module_registration_files.plugin_init); - cc_builder_whole_archive.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 { - cc_builder_whole_archive.file(qmlcachegen_file); + self.cc_builder.file(qmlcachegen_file); } + // 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); - cc_builder_whole_archive_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( @@ -685,28 +730,49 @@ impl CxxQtBuilder { ) { println!("cargo:rerun-if-changed={}", path.display()); } - } - for qrc_file in self.qrc_files { - cc_builder_whole_archive.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()); + // 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"); } - - cc_builder_whole_archive_files_added = true; + // 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((&plugin_dir_name, "plugin_init.o")), + ); } + } + 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 @@ -715,26 +781,104 @@ 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() - ); - 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; + self.initializers + .push(include_str!("std_types_qt5.cpp").to_owned()); } + } - if cc_builder_whole_archive_files_added { - cc_builder_whole_archive.compile("qt-static-initializers"); + 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) { + // 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 + self.build_qml_modules(&init_builder, &mut qtbuild, &generated_header_dir); + + self.build_qrc_files(&init_builder, &mut qtbuild); + + self.setup_qt5_compatibility(&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 { - self.cc_builder.compile(lib_name); + self.cc_builder.compile(&static_lib_name()); } } } 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"); diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index b8562fe13..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`. @@ -217,7 +223,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) @@ -689,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"); } @@ -828,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(), @@ -842,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 @@ -865,16 +875,39 @@ 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"), + ); - let mut qml_plugin_cpp = File::create(&qml_plugin_cpp_path).unwrap(); - write!( - qml_plugin_cpp, - r#" + if !qml_files.is_empty() && self.qmlcachegen_executable.is_some() { + generate_usage( + "int", + &format!("qInitResources_qmlcache_{qml_uri_underscores}"), + ); + } + let declarations = declarations.join("\n"); + let usages = usages.join("\n"); + + std::fs::write( + &qml_plugin_cpp_path, + format!( + 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,16 +917,17 @@ 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} }} }}; // 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 { @@ -903,21 +937,24 @@ 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 { 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/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/.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 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/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); diff --git a/examples/qml_minimal/CMakeLists.txt b/examples/qml_minimal/CMakeLists.txt index 464bebedc..646d80898 100644 --- a/examples/qml_minimal/CMakeLists.txt +++ b/examples/qml_minimal/CMakeLists.txt @@ -33,60 +33,22 @@ 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. - "$" - Qt::Core - Qt::Gui - Qt::Qml - Qt::QuickControls2 -) +# 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 + URI "com.kdab.cxx_qt.demo" + SOURCE_CRATE ${CRATE}) # ANCHOR_END: book_cmake_use_corrosion # ANCHOR: book_cmake_executable @@ -94,7 +56,7 @@ 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) # If we are using a statically linked Qt then we need to import any qml plugins qt_import_qml_plugins(${APP_NAME}) @@ -114,10 +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 - ${APP_NAME}_lib - Qt::QuickTest - ) + target_link_libraries(${APP_TEST_NAME} PRIVATE ${CRATE}_qml Qt::QuickTest) qt_import_qml_plugins(${APP_TEST_NAME}) set(TEST_CMD 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 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 )