diff --git a/crates/cxx-qt-build/Cargo.toml b/crates/cxx-qt-build/Cargo.toml index 7849a40c3..0ea8365a8 100644 --- a/crates/cxx-qt-build/Cargo.toml +++ b/crates/cxx-qt-build/Cargo.toml @@ -27,3 +27,4 @@ codespan-reporting = "0.11" default = ["qt_gui", "qt_qml"] qt_gui = ["cxx-qt-lib-headers/qt_gui"] qt_qml = ["cxx-qt-lib-headers/qt_qml"] +link_qt_external = [] diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 2e39b0788..6a9d68d21 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -400,7 +400,13 @@ impl CxxQtBuilder { let mut qtbuild = qt_build_utils::QtBuild::new(self.qt_modules.into_iter().collect()) .expect("Could not find Qt installation"); - qtbuild.cargo_link_libraries(); + // some external build systems will include object files found by cargo_link_libraries and + // are hard to remove. instead instruct this function to not include these object files. + if cfg!(feature = "link_qt_external") { + qtbuild.cargo_link_libraries(None); + } else { + qtbuild.cargo_link_libraries(Some(&mut self.cc_builder)); + } // Write cxx-qt-lib and cxx headers cxx_qt_lib_headers::write_headers(format!("{header_root}/cxx-qt-lib")); diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index e73e853ae..190f30810 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -16,7 +16,7 @@ fn main() { } let qtbuild = qt_build_utils::QtBuild::new(qt_modules).expect("Could not find Qt installation"); - qtbuild.cargo_link_libraries(); + qtbuild.cargo_link_libraries(None); // Required for tests qt_build_utils::setup_linker(); diff --git a/crates/qt-build-utils/Cargo.toml b/crates/qt-build-utils/Cargo.toml index fd7777086..6c6a830d8 100644 --- a/crates/qt-build-utils/Cargo.toml +++ b/crates/qt-build-utils/Cargo.toml @@ -13,5 +13,6 @@ description = "Build script helper for linking Qt libraries and using moc code g repository.workspace = true [dependencies] +cc = "1.0.74" versions = "4.1.0" thiserror = "1.0" diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index d7bb39004..52dc929a9 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -306,8 +306,78 @@ impl QtBuild { .to_string() } + fn cargo_link_qt_library( + &self, + name: &str, + prefix_path: &str, + lib_path: &str, + link_lib: &str, + prl_path: &str, + builder: &mut Option<&mut cc::Build>, + ) { + println!("cargo:rustc-link-lib={link_lib}"); + + match std::fs::read_to_string(prl_path) { + Ok(prl) => { + for line in prl.lines() { + if let Some(line) = line.strip_prefix("QMAKE_PRL_LIBS = ") { + parse_cflags::parse_libs_cflags( + name, + line.replace(r"$$[QT_INSTALL_LIBS]", lib_path) + .replace(r"$$[QT_INSTALL_PREFIX]", prefix_path) + .as_bytes(), + builder, + ); + } + } + } + Err(e) => { + println!( + "cargo:warning=Could not open {} file to read libraries to link: {}", + &prl_path, e + ); + } + } + } + + /// Some prl files include their architecture in their naming scheme. + /// Just try all known architectures and fallback to non when they all failed. + fn find_qt_module_prl( + &self, + lib_path: &str, + prefix: &str, + version_major: u32, + qt_module: &str, + ) -> String { + for arch in ["", "_arm64-v8a", "_armeabi-v7a", "_x86", "_x86_64"] { + let prl_path = format!( + "{}/{}Qt{}{}{}.prl", + lib_path, prefix, version_major, qt_module, arch + ); + match Path::new(&prl_path).try_exists() { + Ok(exists) => { + if exists { + return prl_path; + } + } + Err(e) => { + println!( + "cargo:warning=failed checking for existence of {}: {}", + prl_path, e + ); + } + } + } + + format!( + "{}/{}Qt{}{}.prl", + lib_path, prefix, version_major, qt_module + ) + } + /// Tell Cargo to link each Qt module. - pub fn cargo_link_libraries(&self) { + pub fn cargo_link_libraries(&self, mut builder: Option<&mut cc::Build>) { + let prefix_path = self.qmake_query("QT_INSTALL_PREFIX"); let lib_path = self.qmake_query("QT_INSTALL_LIBS"); println!("cargo:rustc-link-search={lib_path}"); @@ -343,35 +413,35 @@ impl QtBuild { } else { ( format!("Qt{}{qt_module}", self.version.major), - format!( - "{}/{}Qt{}{}.prl", - lib_path, prefix, self.version.major, qt_module - ), + self.find_qt_module_prl(&lib_path, prefix, self.version.major, qt_module), ) }; - println!("cargo:rustc-link-lib={link_lib}"); - - match std::fs::read_to_string(&prl_path) { - Ok(prl) => { - for line in prl.lines() { - if let Some(line) = line.strip_prefix("QMAKE_PRL_LIBS = ") { - parse_cflags::parse_libs_cflags( - &format!("Qt{}{qt_module}", self.version.major), - line.replace(r"$$[QT_INSTALL_LIBS]", &lib_path) - .replace(r"$$[QT_INSTALL_PREFIX]", &lib_path) - .as_bytes(), - ); - } - } - } - Err(e) => { - println!( - "cargo:warning=Could not open {} file to read libraries to link: {}", - &prl_path, e - ); - } - } + self.cargo_link_qt_library( + &format!("Qt{}{qt_module}", self.version.major), + &prefix_path, + &lib_path, + &link_lib, + &prl_path, + &mut builder, + ); + } + + let emscripten_targeted = match env::var("CARGO_CFG_TARGET_OS") { + Ok(val) => val == "emscripten", + Err(_) => false, + }; + if emscripten_targeted { + let platforms_path = format!("{}/platforms", self.qmake_query("QT_INSTALL_PLUGINS")); + println!("cargo:rustc-link-search={platforms_path}"); + self.cargo_link_qt_library( + "qwasm", + &prefix_path, + &lib_path, + "qwasm", + &format!("{platforms_path}/libqwasm.prl"), + &mut builder, + ); } } diff --git a/crates/qt-build-utils/src/parse_cflags.rs b/crates/qt-build-utils/src/parse_cflags.rs index 0346380af..00ff62596 100644 --- a/crates/qt-build-utils/src/parse_cflags.rs +++ b/crates/qt-build-utils/src/parse_cflags.rs @@ -8,7 +8,9 @@ //! It has been decoupled from the pkg-config crate because qt-build-utils reads Qt's .prl files instead, which //! does not require a pkg-config executable to be available. -use std::env; +use std::{collections::HashSet, env, sync::OnceLock}; + +static mut LINKED_OBJECT_FILES: OnceLock> = OnceLock::new(); /// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories) /// using target-specific logic. @@ -103,7 +105,11 @@ fn split_flags(link_args: &[u8]) -> Vec { words } -pub(crate) fn parse_libs_cflags(name: &str, link_args: &[u8]) { +pub(crate) fn parse_libs_cflags( + name: &str, + link_args: &[u8], + builder: &mut Option<&mut cc::Build>, +) { let mut is_msvc = false; let target = env::var("TARGET"); if let Ok(target) = &target { @@ -162,18 +168,38 @@ pub(crate) fn parse_libs_cflags(name: &str, link_args: &[u8]) { if path.is_file() { // Cargo doesn't have a means to directly specify a file path to link, // so split up the path into the parent directory and library name. - // TODO: pass file path directly when link-arg library type is stabilized - // https://github.com/rust-lang/rust/issues/99427 if let (Some(dir), Some(file_name), Ok(target)) = (path.parent(), path.file_name(), &target) { - match extract_lib_from_filename(target, &file_name.to_string_lossy()) { - Some(lib_basename) => { - println!("cargo:rustc-link-search={}", dir.display()); - println!("cargo:rustc-link-lib={lib_basename}"); + if file_name.to_string_lossy().ends_with(".o") { + if let Some(builder) = builder { + let path_string = path.to_string_lossy().to_string(); + unsafe { + // Linking will fail with duplicate symbol errors if the same .o file is linked twice. + // Many of Qt's .prl files repeat listing .o files that other .prl files also list. + let already_linked_object_files = + LINKED_OBJECT_FILES.get_or_init(HashSet::new); + if !already_linked_object_files.contains(&path_string) { + // Cargo doesn't have a means to directly specify an object to link, + // so use the cc crate to specify it instead. + // TODO: pass file path directly when link-arg library type is stabilized + // https://github.com/rust-lang/rust/issues/99427#issuecomment-1562092085 + // TODO: remove builder argument when it's not used anymore to link object files. + // also remove the dependency on cc when this is done + builder.object(path); + } + LINKED_OBJECT_FILES.get_mut().unwrap().insert(path_string); + } } - None => { - println!("cargo:warning=File path {} found in .prl file for {name}, but could not extract library base name to pass to linker command line", path.display()); + } else { + match extract_lib_from_filename(target, &file_name.to_string_lossy()) { + Some(lib_basename) => { + println!("cargo:rustc-link-search={}", dir.display()); + println!("cargo:rustc-link-lib={lib_basename}"); + } + None => { + println!("cargo:warning=File path {} found in .prl file for {name}, but could not extract library base name to pass to linker command line", path.display()); + } } } }