Skip to content

link .o files #605

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

Merged
merged 14 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ endif()
set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/${BUILD_DIR}/cargo/build")

# Add CMake tests for `cargo test/clippy/fmt/doc`.
add_test(NAME cargo_tests COMMAND cargo test --all-targets --target-dir
add_test(NAME cargo_tests COMMAND cargo test --features link_qt_object_files --all-targets --target-dir
${CARGO_TARGET_DIR})
add_test(NAME cargo_doc_tests COMMAND cargo test --doc --target-dir
add_test(NAME cargo_doc_tests COMMAND cargo test --features link_qt_object_files --doc --target-dir
${CARGO_TARGET_DIR})
add_test(NAME cargo_doc COMMAND cargo doc --workspace --target-dir ${CARGO_TARGET_DIR})
add_test(NAME cargo_clippy COMMAND cargo clippy --all-targets --target-dir
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ version_check = "0.9"
default = ["qt_gui", "qt_qml"]
qt_gui = ["cxx-qt-lib-headers/qt_gui"]
qt_qml = ["cxx-qt-lib-headers/qt_qml"]
link_qt_object_files = ["qt-build-utils/link_qt_object_files"]
2 changes: 1 addition & 1 deletion crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ 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();
qtbuild.cargo_link_libraries(&mut self.cc_builder);

// Write cxx-qt-gen, cxx-qt-lib and cxx headers
cxx_qt_gen::write_headers(format!("{header_root}/cxx-qt-common"));
Expand Down
1 change: 1 addition & 0 deletions crates/cxx-qt-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ qt_gui = ["cxx-qt-lib-headers/qt_gui"]
qt_qml = ["cxx-qt-lib-headers/qt_qml"]
time = ["dep:time"]
url = ["dep:url"]
link_qt_object_files = ["qt-build-utils/link_qt_object_files"]
4 changes: 3 additions & 1 deletion crates/cxx-qt-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn main() {
}

let qtbuild = qt_build_utils::QtBuild::new(qt_modules).expect("Could not find Qt installation");
qtbuild.cargo_link_libraries();

// Required for tests
qt_build_utils::setup_linker();

Expand Down Expand Up @@ -192,6 +192,8 @@ fn main() {
let mut builder =
cxx_build::bridges(rust_bridges.iter().map(|bridge| format!("src/{bridge}.rs")));

qtbuild.cargo_link_libraries(&mut builder);

let mut cpp_files = vec![
"core/qbytearray",
"core/qcoreapplication",
Expand Down
14 changes: 14 additions & 0 deletions crates/qt-build-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,19 @@ 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"

[features]
# When Cargo links an executable, whether a bin crate or test executable,
# and Qt 6 is linked statically, this feature must be enabled to link
# unarchived .o files with static symbols that Qt ships (for example
# to initialize Qt resources embedded within Qt libraries).
#
# CMake also links those .o files when linking Qt's targets, so this
# feature must be disabled for staticlib crates. Otherwise, linking
# will fail with duplicate symbol errors.
#
# When linking Qt dynamically, this makes no difference.
link_qt_object_files = []
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 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, builder: &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,
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"),
builder,
);
}
}

Expand Down
46 changes: 37 additions & 9 deletions crates/qt-build-utils/src/parse_cflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

use std::env;

#[cfg(feature = "link_qt_object_files")]
use std::{collections::HashSet, sync::OnceLock};

#[cfg(feature = "link_qt_object_files")]
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.
fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
Expand Down Expand Up @@ -103,7 +109,7 @@ 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 cc::Build) {
let mut is_msvc = false;
let target = env::var("TARGET");
if let Ok(target) = &target {
Expand Down Expand Up @@ -162,18 +168,40 @@ 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}");
let file_name = file_name.to_string_lossy();
if file_name.ends_with(".o") {
#[cfg(feature = "link_qt_object_files")]
{
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) {
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
3 changes: 2 additions & 1 deletion examples/cargo_without_cmake/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ cxx-qt-lib.workspace = true

[build-dependencies]
# Use `cxx-qt-build = "0.5"` here instead!
cxx-qt-build.workspace = true
# The link_qt_object_files feature is required for statically linking Qt 6.
cxx-qt-build = { workspace = true, features = [ "link_qt_object_files" ] }
# ANCHOR_END: book_cargo_toml_no_cmake
3 changes: 3 additions & 0 deletions examples/demo_threading/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ uuid = { version = "1.2", features = ["serde", "v4"] }

[build-dependencies]
cxx-qt-build.workspace = true

[features]
link_qt_object_files = [ "cxx-qt-build/link_qt_object_files" ]
3 changes: 3 additions & 0 deletions examples/qml_extension_plugin/plugin/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ serde_json.workspace = true

[build-dependencies]
cxx-qt-build.workspace = true

[features]
link_qt_object_files = [ "cxx-qt-build/link_qt_object_files" ]
3 changes: 3 additions & 0 deletions examples/qml_features/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ serde_json.workspace = true

[build-dependencies]
cxx-qt-build.workspace = true

[features]
link_qt_object_files = [ "cxx-qt-build/link_qt_object_files" ]
4 changes: 4 additions & 0 deletions examples/qml_minimal/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ cxx-qt-lib.workspace = true
[build-dependencies]
# Use `cxx-qt-build = "0.5"` here instead!
cxx-qt-build.workspace = true

[features]
# This feature must be enabled for `cargo test` when linking Qt 6 statically.
link_qt_object_files = [ "cxx-qt-build/link_qt_object_files" ]
# ANCHOR_END: book_build_dependencies

# ANCHOR_END: book_all