Skip to content

Prl file parsing fixes #600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions crates/cxx-qt-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
8 changes: 7 additions & 1 deletion crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions crates/qt-build-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
124 changes: 97 additions & 27 deletions crates/qt-build-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");

Expand Down Expand Up @@ -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,
);
}
}

Expand Down
46 changes: 36 additions & 10 deletions crates/qt-build-utils/src/parse_cflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HashSet<String>> = 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.
Expand Down Expand Up @@ -103,7 +105,11 @@ fn split_flags(link_args: &[u8]) -> Vec<String> {
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 {
Expand Down Expand Up @@ -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());
}
}
}
}
Expand Down