Skip to content

Commit c62afff

Browse files
committed
cxx-qt-lib: use cxx_build instead of CMake for C++
This is a step towards obviating the need for CMake to build autogenerated code, which will simplify the build process by moving the invocation of Cargo to CMake build time rather than CMake configure time.
1 parent 1647cd4 commit c62afff

File tree

7 files changed

+78
-223
lines changed

7 files changed

+78
-223
lines changed

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,28 @@ endfunction()
149149
add_subdirectory(book)
150150
add_subdirectory(examples)
151151

152+
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Test REQUIRED)
153+
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Test REQUIRED)
154+
get_target_property(QMAKE Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION)
155+
set(CARGO_ENV "QMAKE=${QMAKE};CARGO_TARGET_DIR=${CMAKE_CURRENT_SOURCE_DIR}/target")
156+
152157
# Create helper method which adds relevent cargo tests for a given manifest
153158
function(add_test_cargo TEST_NAME_PREFIX MANIFEST_PATH ADD_DOCTESTS)
154159
# Add cargo as a test
155160
add_test(NAME ${TEST_NAME_PREFIX}_cargo_tests COMMAND cargo test --all-targets --manifest-path ${MANIFEST_PATH})
161+
set_property(TEST ${TEST_NAME_PREFIX}_cargo_tests PROPERTY ENVIRONMENT ${CARGO_ENV})
156162
# Check if we should enable doc tests
157163
if (${ADD_DOCTESTS} STREQUAL "DOCTESTS_ON")
158164
# Add cargo docs as a test
159165
add_test(NAME ${TEST_NAME_PREFIX}_cargo_doc_tests COMMAND cargo test --doc --manifest-path ${MANIFEST_PATH})
166+
set_property(TEST ${TEST_NAME_PREFIX}_cargo_doc_tests PROPERTY ENVIRONMENT ${CARGO_ENV})
160167
endif()
161168
# Add clippy as a test
162169
add_test(NAME ${TEST_NAME_PREFIX}_cargo_clippy COMMAND cargo clippy --all-targets --manifest-path ${MANIFEST_PATH} -- -D warnings)
170+
set_property(TEST ${TEST_NAME_PREFIX}_cargo_clippy PROPERTY ENVIRONMENT ${CARGO_ENV})
163171
# Add rustfmt as a test
164172
add_test(NAME ${TEST_NAME_PREFIX}_cargo_fmt COMMAND cargo fmt --manifest-path ${MANIFEST_PATH} -- --check)
173+
set_property(TEST ${TEST_NAME_PREFIX}_cargo_fmt PROPERTY ENVIRONMENT ${CARGO_ENV})
165174
endfunction()
166175

167176
# Add cargo tests for all our manifests

cmake/CxxQt.cmake

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,14 @@ function(cxx_qt_generate_cpp GEN_SOURCES)
4141

4242
file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/target/cxx-qt-gen")
4343

44+
get_target_property(QMAKE Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION)
45+
4446
# Run cargo during config to ensure the cpp source file list is created
4547
execute_process(
46-
COMMAND ${CARGO_CMD}
48+
COMMAND cmake -E env
49+
"CARGO_TARGET_DIR=${CMAKE_CURRENT_SOURCE_DIR}/target"
50+
"QMAKE=${QMAKE}"
51+
${CARGO_CMD}
4752
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
4853
)
4954

@@ -62,6 +67,7 @@ function(cxx_qt_include APP_NAME)
6267
target_include_directories(${APP_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
6368
# Include the target folder so that cxx-qt-gen and cxx-qt-lib can be included
6469
target_include_directories(${APP_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/target")
70+
target_include_directories(${APP_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/target/cxxbridge")
6571
# Our cxx_qt and cxx headers are in this folder and need to be included
6672
target_include_directories(${APP_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/target/cxx-qt-gen/statics")
6773
endfunction()
@@ -100,9 +106,15 @@ function(cxx_qt_link_rustlib APP_NAME)
100106
else()
101107
set(RUST_PART_LIB "${CMAKE_CURRENT_SOURCE_DIR}/target/${TARGET_DIR}/librust.a")
102108
endif()
109+
110+
get_target_property(QMAKE Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION)
111+
103112
add_custom_target(
104113
"${APP_NAME}_rustlib"
105-
COMMAND ${CARGO_CMD}
114+
COMMAND cmake -E env
115+
"CARGO_TARGET_DIR=${CMAKE_CURRENT_SOURCE_DIR}/target"
116+
"QMAKE=${QMAKE}"
117+
${CARGO_CMD}
106118
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
107119
)
108120
add_dependencies(${APP_NAME} "${APP_NAME}_rustlib")

cxx-qt-build/src/lib.rs

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -297,61 +297,6 @@ fn write_cpp_sources_list(paths: &[PathBuf]) {
297297
}
298298
}
299299

300-
/// Write our a given cxx-qt-lib header and source set to the given folder
301-
fn write_cxx_qt_lib_set(
302-
file_name: &str,
303-
target_dir: &str,
304-
header: &str,
305-
source: &str,
306-
) -> Vec<PathBuf> {
307-
let mut paths = vec![];
308-
let path_h = PathBuf::from(format!("{}/include/{}.h", target_dir, file_name));
309-
let path_cpp = PathBuf::from(format!("{}/src/{}.cpp", target_dir, file_name));
310-
311-
let mut file = std::fs::File::create(&path_h).expect("Could not create header file");
312-
file.write_all(header.as_bytes())
313-
.expect("Could not write header file");
314-
paths.push(path_h);
315-
316-
let mut file = std::fs::File::create(&path_cpp).expect("Could not create source file");
317-
file.write_all(source.as_bytes())
318-
.expect("Could not write source file");
319-
paths.push(path_cpp);
320-
321-
paths
322-
}
323-
324-
/// Find all the cxx-qt-lib sources and write them to the target directory
325-
fn write_cxx_qt_lib_sources() -> Vec<PathBuf> {
326-
let cxx_qt_lib_target_dir = format!("{}/target/cxx-qt-lib", manifest_dir());
327-
let cxx_qt_lib_include_dir = format!("{}/include", cxx_qt_lib_target_dir);
328-
let cxx_qt_lib_src_dir = format!("{}/src", cxx_qt_lib_target_dir);
329-
std::fs::create_dir_all(&cxx_qt_lib_include_dir).unwrap();
330-
std::fs::create_dir_all(&cxx_qt_lib_src_dir).unwrap();
331-
332-
let mut paths = vec![];
333-
// Add the hand written qt_types file
334-
paths.append(&mut write_cxx_qt_lib_set(
335-
"qt_types",
336-
&cxx_qt_lib_target_dir,
337-
cxx_qt_lib::QT_TYPES_HEADER,
338-
cxx_qt_lib::QT_TYPES_SOURCE,
339-
));
340-
// Add the generated CXX files
341-
let generated: Vec<GeneratedType> =
342-
serde_json::from_str(cxx_qt_lib::QT_TYPES_CXX_JSON).unwrap();
343-
for gen in generated {
344-
paths.append(&mut write_cxx_qt_lib_set(
345-
&gen.name,
346-
&cxx_qt_lib_target_dir,
347-
&gen.header,
348-
&gen.source,
349-
));
350-
}
351-
352-
paths
353-
}
354-
355300
/// Write out the static header file for both the cxx
356301
fn write_cxx_static_header() {
357302
let manifest_dir = manifest_dir();
@@ -417,19 +362,13 @@ impl CxxQtBuilder {
417362
// TODO: later use the module::object to turn into module/object.h
418363

419364
// Generate files
420-
let mut cpp_paths = write_cxx_generated_files_for_cargo(&self.rust_sources);
365+
let cpp_paths = write_cxx_generated_files_for_cargo(&self.rust_sources);
421366

422367
// TODO: in large projects where where CXX-Qt is used in multiple individual
423368
// components that end up being linked together, having these same static
424369
// files in each one could cause issues.
425370
write_cxx_static_header();
426371

427-
// Check if we have Qt support enabled
428-
if self.qt_enabled {
429-
// Write the cxx-qt-lib sources into the folder
430-
cpp_paths.append(&mut write_cxx_qt_lib_sources());
431-
}
432-
433372
// TODO: find a way to only do this when cargo is called during the config stage of CMake
434373
write_cpp_sources_list(&cpp_paths);
435374
}

cxx-qt-lib/Cargo.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,5 @@ links = "cxx-qt-lib"
1919
cxx = "1.0"
2020

2121
[build-dependencies]
22-
cxx-gen = "0.7"
23-
serde = { version = "1.0", features = ["derive"] }
24-
serde_json = "1.0"
25-
syn = { version = "1.0", features = ["printing"] }
26-
quote = "1.0"
22+
cxx-build = "1.0"
23+
qt-build = { path = "../qt-build" }

cxx-qt-lib/build.rs

Lines changed: 52 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -3,138 +3,66 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6-
use quote::ToTokens;
7-
use serde::{Deserialize, Serialize};
8-
use std::{fs::File, io::Write, path::PathBuf};
9-
10-
/// Representation of a generated CXX header, source, and name
11-
#[derive(Serialize, Deserialize)]
12-
struct GeneratedType {
13-
header: String,
14-
name: String,
15-
source: String,
16-
}
17-
18-
/// Generate a CXX header, source, name for a given Rust file
19-
fn gen_cxx_sources(folder: &str, file_stem: &str) -> GeneratedType {
20-
// Read the rust source files
21-
let path = format!("{}/{}.rs", folder, file_stem);
22-
println!("cargo:rerun-if-changed={}", path);
23-
let content = std::fs::read_to_string(path).expect("Could not read Rust file");
24-
let file = syn::parse_file(&content).unwrap();
25-
26-
// Generate the CXX header and cc for the file
27-
let opt = cxx_gen::Opt::default();
28-
let generated = cxx_gen::generate_header_and_cc(file.into_token_stream(), &opt)
29-
.expect("Could not generate C++ from Rust file");
30-
31-
GeneratedType {
32-
header: String::from_utf8(generated.header).unwrap(),
33-
name: format!("{}_cxx", file_stem),
34-
source: String::from_utf8(generated.implementation).unwrap(),
35-
}
36-
}
37-
38-
/// Write generates types to a given file as JSON
39-
fn write_cxx_sources(gen: &Vec<GeneratedType>, path: &str) {
40-
let file = std::fs::File::create(path).expect("Could not create generated file");
41-
serde_json::to_writer(file, &gen).unwrap();
42-
}
43-
44-
fn create_and_write_file(path: &impl AsRef<std::path::Path>, file_contents: &str) {
45-
let path = path.as_ref();
46-
File::create(&path)
47-
.unwrap_or_else(|_| panic!("Could not create file {}", path.display()))
48-
.write_all(file_contents.as_bytes())
49-
.unwrap_or_else(|_| panic!("Could not write file {}", path.display()));
50-
}
6+
use std::env;
517

528
fn main() {
53-
// Read the cargo folder and out folder
54-
let mut dir_manifest = std::env::var("CARGO_MANIFEST_DIR").expect("Could not get manifest dir");
55-
if cfg!(windows) {
56-
dir_manifest = dir_manifest.replace('\\', "/");
9+
let qt_modules = vec!["Core", "Gui"]
10+
.iter()
11+
.map(|m| String::from(*m))
12+
.collect();
13+
let qtbuild = qt_build::QtBuild::new(qt_modules).expect("Could not find Qt installation");
14+
qtbuild.cargo_link_libraries();
15+
16+
let bridge_files = [
17+
"src/types/qcolor.rs",
18+
"src/types/qdate.rs",
19+
"src/types/qdatetime.rs",
20+
"src/types/qpoint.rs",
21+
"src/types/qpointf.rs",
22+
"src/types/qrect.rs",
23+
"src/types/qrectf.rs",
24+
"src/types/qsize.rs",
25+
"src/types/qsizef.rs",
26+
"src/types/qstring.rs",
27+
"src/types/qtime.rs",
28+
"src/types/qurl.rs",
29+
"src/types/qvariant.rs",
30+
"src/types/update_requester.rs",
31+
];
32+
for bridge_file in bridge_files {
33+
println!("cargo:rerun-if-changed={}", bridge_file);
5734
}
58-
println!("cargo:rerun-if-env-changed=CARGO_MANIFEST_DIR");
5935

60-
let mut dir_out = std::env::var("OUT_DIR").expect("Could not get out dir");
61-
if cfg!(windows) {
62-
dir_out = dir_out.replace('\\', "/");
36+
for include_path in qtbuild.include_paths() {
37+
cxx_build::CFG
38+
.exported_header_dirs
39+
.push(include_path.as_path());
6340
}
64-
println!("cargo:rerun-if-env-changed=OUT_DIR");
65-
66-
// Prepare cxx-qt-lib dir we'll write to
67-
let path = format!("{}/cxx-qt-lib", dir_out);
68-
std::fs::create_dir_all(&path).expect("Could not create cxx-qt-lib dir");
6941

70-
// Read the types directory for CXX objects
71-
let types_dir = format!("{}/src/types/", dir_manifest);
72-
// If any of the files in this directory change, then we need to re-run
73-
println!("cargo:rerun-if-changed={}", types_dir);
74-
75-
let mut generated = vec![];
76-
77-
for entry in std::fs::read_dir(&types_dir).expect("Could not open types folder") {
78-
let path = entry.expect("Could not open file").path();
79-
let file_stem = path
80-
.file_stem()
81-
.expect("Could not find file name")
82-
.to_str()
83-
.expect("Could not convert to unicode");
84-
85-
// Check we are a file and not the mod.rs
86-
if path.is_file() && file_stem != "mod" {
87-
generated.push(gen_cxx_sources(&types_dir, file_stem));
88-
}
42+
let mut builder = cxx_build::bridges(&bridge_files);
43+
for cpp_file in ["src/qt_types.cpp"] {
44+
builder.file(cpp_file);
45+
println!("cargo:rerun-if-changed={}", cpp_file);
8946
}
90-
91-
// Write the generated sources to a qt_types_cxx.json file
92-
write_cxx_sources(&generated, &format!("{}/qt_types_cxx.json", path));
93-
94-
// Write the generated sources to CXX_QT_LIB_OUT_DIR if set
95-
println!("cargo:rerun-if-env-changed=CXX_QT_LIB_OUT_DIR");
96-
if let Ok(env_var) = std::env::var("CXX_QT_LIB_OUT_DIR") {
97-
let directory = PathBuf::from(env_var);
98-
if !directory.is_dir() {
99-
panic!(
100-
"CXX_QT_LIB_OUT_DIR {} is not a directory",
101-
directory.display()
102-
);
103-
}
104-
105-
let include_directory_path = PathBuf::from(format!("{}/include", &directory.display()));
106-
std::fs::create_dir_all(&include_directory_path)
107-
.expect("Could not create CXX_QT_LIB_OUT_DIR include dir");
108-
let source_directory_path = PathBuf::from(format!("{}/src", &directory.display()));
109-
std::fs::create_dir_all(&source_directory_path)
110-
.expect("Could not create CXX_QT_LIB_OUT_DIR source dir");
111-
112-
std::fs::copy(
113-
format!("{}/include/qt_types.h", dir_manifest),
114-
format!("{}/qt_types.h", include_directory_path.display()),
115-
)
116-
.expect("Could not copy qt_types.h to CXX_QT_LIB_OUT_DIR");
47+
// MSVC
48+
builder.flag_if_supported("/std:c++17");
49+
builder.flag_if_supported("/Zc:__cplusplus");
50+
// GCC + Clang
51+
builder.flag_if_supported("-std=c++17");
52+
builder.compile("cxx-qt-lib");
53+
54+
// Copy qt_types.h so CMake can include it.
55+
// By design, CARGO_TARGET_DIR is not set by cargo when running build scripts.
56+
// Copying the header is only needed for making the header available to a C++
57+
// build system, in which case CARGO_TARGET_DIR will be set by
58+
// the C++ build system.
59+
if let Ok(target_dir) = env::var("CARGO_TARGET_DIR") {
60+
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
61+
std::fs::create_dir_all(&format!("{}/cxxbridge/cxx-qt-lib/include", target_dir)).unwrap();
11762
std::fs::copy(
118-
format!("{}/src/qt_types.cpp", dir_manifest),
119-
format!("{}/qt_types.cpp", source_directory_path.display()),
63+
&format!("{}/include/qt_types.h", manifest_dir),
64+
&format!("{}/cxxbridge/cxx-qt-lib/include/qt_types.h", target_dir),
12065
)
121-
.expect("Could not copy qt_types.cpp to CXX_QT_LIB_OUT_DIR");
122-
123-
create_and_write_file(
124-
&format!("{}/cxx.h", include_directory_path.display()),
125-
cxx_gen::HEADER,
126-
);
127-
128-
for class in generated {
129-
create_and_write_file(
130-
&format!("{}/{}.h", include_directory_path.display(), class.name),
131-
&class.header,
132-
);
133-
134-
create_and_write_file(
135-
&format!("{}/{}.cpp", source_directory_path.display(), class.name),
136-
&class.source,
137-
);
138-
}
66+
.unwrap();
13967
}
14068
}

cxx-qt-lib/src/lib.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,6 @@ pub use types::*;
1111
// this is because include_str requires th correct and non-mixed path separators
1212
//
1313
// https://github.com/rust-lang/rust/issues/75075
14-
#[cfg(not(windows))]
15-
macro_rules! sep {
16-
() => {
17-
"/"
18-
};
19-
}
20-
21-
#[cfg(windows)]
22-
macro_rules! sep {
23-
() => {
24-
"\\"
25-
};
26-
}
27-
28-
/// JSON representation of the generated CXX sources for qt_types
29-
pub const QT_TYPES_CXX_JSON: &str = include_str!(concat!(
30-
env!("OUT_DIR"),
31-
sep!(),
32-
"cxx-qt-lib",
33-
sep!(),
34-
"qt_types_cxx.json"
35-
));
36-
/// The header for qt_types
37-
pub const QT_TYPES_HEADER: &str =
38-
include_str!(concat!("..", sep!(), "include", sep!(), "qt_types.h"));
39-
/// The source for qt_types
40-
pub const QT_TYPES_SOURCE: &str = include_str!("qt_types.cpp");
4114

4215
pub trait UpdateRequestHandler<C> {
4316
fn handle_update_request(&mut self, cpp: &mut C);

tests/basic_cxx_qt/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ set(CMAKE_AUTOMOC ON)
1616
set(CMAKE_CXX_STANDARD 17)
1717
set(CMAKE_CXX_STANDARD_REQUIRED ON)
1818

19-
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Test REQUIRED)
20-
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Test REQUIRED)
21-
2219
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake")
2320
include(CxxQt)
2421

0 commit comments

Comments
 (0)