Skip to content

Commit 638f511

Browse files
cxx-qt-build: return an Interface from CxxQtBuilder (#1287)
* cxx-qt-build: split interface into a separate file * cxx-qt-build: move write_manifest to export of Interface This still needs more refactoring later but is a first step. * cxx-qt-build: move write of exported include directories to interface * cxx-qt-build: store manifest and dependencies in Interface for now This allows export() to have no args meaning we can return an Interface from the compile command next. * cxx-qt-build: return an Interface rather than taking as input * cxx-qt-build: check the links key when exporting * cxx-qt-build: remove CxxQtBuilder::library instead use new and export * cxx-qt-build: Add CXX-style automatic inclusion of header files We however have two changes compared to CXX: 1. We allow you to change the root of the automatic include directory. 2. We allow you to include additional directories. Both of these changes are there to accomodate crates like cxx-qt-lib, that want to use e.g. `<cxx-qt-lib/qstring.h>` instead of `<cxx-qt-lib/include/qstring.h>`. Also cxx-qt-lib generates a definitions header which we need to include. * cxx-qt-build: Remove Interface::export_include_dir This is no longer needed. Also clean up some documentation on the API. * Only allow exporting a single Interface The function will now panic if it is called multiple times. --------- Co-authored-by: Andrew Hayzen <[email protected]>
1 parent cb64b22 commit 638f511

File tree

167 files changed

+914
-437
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

167 files changed

+914
-437
lines changed

crates/cxx-qt-build/src/dependencies.rs

Lines changed: 4 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -7,100 +7,9 @@
77
88
use serde::{Deserialize, Serialize};
99

10-
use std::collections::HashSet;
11-
use std::path::{Path, PathBuf};
10+
use std::path::PathBuf;
1211

13-
/// When generating a library with cxx-qt-build, the library may need to export certain flags or headers.
14-
/// These are all specified by this Interface struct, which should be passed to the [crate::CxxQtBuilder::library] function.
15-
pub struct Interface {
16-
// The name of the links keys, whose CXX-Qt dependencies to reexport
17-
pub(crate) reexport_links: HashSet<String>,
18-
pub(crate) exported_include_prefixes: Vec<String>,
19-
pub(crate) exported_include_directories: Vec<(PathBuf, String)>,
20-
// TODO: In future, we want to also set up the include paths so that you can include anything
21-
// from the crates source directory.
22-
// Once this is done, this flag should indicate whether or not to export our own crates source
23-
// directory to downstream dependencies?
24-
// export_crate_directory: bool,
25-
}
26-
27-
impl Default for Interface {
28-
fn default() -> Self {
29-
Self {
30-
reexport_links: HashSet::new(),
31-
exported_include_prefixes: vec![super::crate_name()],
32-
exported_include_directories: Vec::new(),
33-
}
34-
}
35-
}
36-
37-
impl Interface {
38-
/// Export all headers with the given prefix to downstream dependencies
39-
///
40-
/// Note: This will overwrite any previously specified header_prefixes, including the default
41-
/// header_prefix of this crate.
42-
///
43-
/// This function will panic if any of the given prefixes are already exported through the
44-
/// [Self::export_include_directory] function.
45-
pub fn export_include_prefixes<'a>(
46-
mut self,
47-
prefixes: impl IntoIterator<Item = &'a str>,
48-
) -> Self {
49-
let prefixes = prefixes.into_iter().map(|item| item.to_string()).collect();
50-
51-
let mut exported_prefixes = self
52-
.exported_include_directories
53-
.iter()
54-
.map(|(_path, prefix)| prefix);
55-
for prefix in &prefixes {
56-
if let Some(duplicate) =
57-
exported_prefixes.find(|exported_prefix| exported_prefix.starts_with(prefix))
58-
{
59-
panic!("Duplicate export_prefix! Cannot export `{prefix}`, as `{duplicate}` is already exported as an export_include_directory!");
60-
}
61-
}
62-
63-
self.exported_include_prefixes = prefixes;
64-
self
65-
}
66-
67-
/// Add a directory that will be added as an include directory under the given prefix.
68-
///
69-
/// The prefix will automatically be exported (see also: [Self::export_include_prefixes])
70-
///
71-
/// This function will panic if the given prefix is already exported.
72-
pub fn export_include_directory(mut self, directory: impl AsRef<Path>, prefix: &str) -> Self {
73-
let mut exported_prefixes = self.exported_include_prefixes.iter().chain(
74-
self.exported_include_directories
75-
.iter()
76-
.map(|(_path, prefix)| prefix),
77-
);
78-
if let Some(duplicate) =
79-
exported_prefixes.find(|exported_prefix| exported_prefix.starts_with(prefix))
80-
{
81-
panic!("Duplicate export_prefix! Cannot export `{prefix}`, as `{duplicate}` is already exported!");
82-
}
83-
84-
self.exported_include_directories
85-
.push((directory.as_ref().into(), prefix.to_owned()));
86-
self
87-
}
88-
89-
/// Reexport the dependency with the given link name.
90-
/// This will make the dependency available to downstream dependencies.
91-
///
92-
/// Specifically it will reexport all include_prefixes of the given dependency
93-
/// as well as any definitions made by that dependency.
94-
///
95-
/// Note that the link name may differ from the crate name.
96-
/// Check your dependencies manifest file for the correct link name.
97-
pub fn reexport_dependency(mut self, link_name: &str) -> Self {
98-
self.reexport_links.insert(link_name.to_owned());
99-
self
100-
}
101-
}
102-
103-
#[derive(Clone, Serialize, Deserialize)]
12+
#[derive(Clone, Default, Serialize, Deserialize)]
10413
/// This struct is used by cxx-qt-build internally to propagate data through to downstream
10514
/// dependencies
10615
pub(crate) struct Manifest {
@@ -122,8 +31,8 @@ pub(crate) struct Dependency {
12231
}
12332

12433
impl Dependency {
125-
/// This function will search the environment for all dependencies that have been set up with
126-
/// CxxQtBuilder::library.
34+
/// This function will search the environment for all direct dependencies that have exported
35+
/// their Interface via [crate::Interface::export].
12736
/// They export their manifest paths as metadata, which will be exposed to us as an environment
12837
/// variable.
12938
/// We extract those paths here, parse the manifest and make sure to set it up correctly as a
@@ -162,44 +71,3 @@ pub(crate) fn initializers(dependencies: &[Dependency]) -> Vec<qt_build_utils::I
16271
.flat_map(|dep| dep.manifest.initializers.iter().cloned())
16372
.collect()
16473
}
165-
166-
pub(crate) fn all_include_prefixes(
167-
interface: &Interface,
168-
dependencies: &[Dependency],
169-
) -> Vec<String> {
170-
interface
171-
.exported_include_prefixes
172-
.iter()
173-
.cloned()
174-
.chain(
175-
interface
176-
.exported_include_directories
177-
.iter()
178-
.map(|(_path, prefix)| prefix.clone()),
179-
)
180-
.chain(
181-
dependencies
182-
.iter()
183-
.flat_map(|dep| &dep.manifest.exported_include_prefixes)
184-
.cloned(),
185-
)
186-
.collect()
187-
}
188-
189-
pub(crate) fn reexported_dependencies(
190-
interface: &Interface,
191-
dependencies: &[Dependency],
192-
) -> Vec<Dependency> {
193-
let mut exported_dependencies = Vec::new();
194-
for link_name in &interface.reexport_links {
195-
if let Some(dependency) = dependencies
196-
.iter()
197-
.find(|dep| &dep.manifest.link_name == link_name)
198-
{
199-
exported_dependencies.push(dependency.clone());
200-
} else {
201-
panic!("Could not find dependency with link name `{link_name}` to reexport!");
202-
}
203-
}
204-
exported_dependencies
205-
}

crates/cxx-qt-build/src/dir.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ pub(crate) fn initializers(key: &str) -> PathBuf {
136136
path
137137
}
138138

139+
pub(crate) fn manifest() -> Option<PathBuf> {
140+
std::env::var("CARGO_MANIFEST_DIR").ok().map(PathBuf::from)
141+
}
142+
139143
#[cfg(unix)]
140144
pub(crate) fn symlink_or_copy_directory(
141145
source: impl AsRef<Path>,

crates/cxx-qt-build/src/interface.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Leon Matthes <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
//! This modules contains utilities for specifying interfaces with cxx-qt-build.
7+
8+
use core::panic;
9+
use std::collections::HashSet;
10+
11+
use crate::{dir, Dependency, Manifest};
12+
13+
/// When generating a library with cxx-qt-build, the library may need to export certain flags or headers.
14+
/// These are all specified by this Interface struct.
15+
pub struct Interface {
16+
// The name of the links keys, whose CXX-Qt dependencies to reexport
17+
pub(crate) reexport_links: HashSet<String>,
18+
pub(crate) exported_include_prefixes: Vec<String>,
19+
pub(crate) manifest: Manifest,
20+
pub(crate) dependencies: Vec<Dependency>,
21+
}
22+
23+
impl Default for Interface {
24+
fn default() -> Self {
25+
Self {
26+
reexport_links: HashSet::new(),
27+
// TODO: This doesn't currently match the include_prefix that is specified by e.g.
28+
// cxx-qt-lib build script.
29+
// In this case this is a happy accident, as we don't want to actually export the
30+
// `include_prefix` in cxx-qt-lib (which is "private/").
31+
// But we do need to unify this.
32+
exported_include_prefixes: vec![super::crate_name()],
33+
manifest: Manifest::default(),
34+
dependencies: Vec::new(),
35+
}
36+
}
37+
}
38+
39+
impl Interface {
40+
/// Export all headers with the given prefix to downstream dependencies
41+
///
42+
/// Note: This will overwrite any previously specified header_prefixes, including the default
43+
/// header_prefix of this crate.
44+
pub fn export_include_prefixes<'a>(
45+
mut self,
46+
prefixes: impl IntoIterator<Item = &'a str>,
47+
) -> Self {
48+
let prefixes = prefixes.into_iter().map(|item| item.to_string()).collect();
49+
50+
self.exported_include_prefixes = prefixes;
51+
self
52+
}
53+
54+
/// Reexport the dependency with the given link name.
55+
/// This will make the dependency available to downstream dependencies.
56+
///
57+
/// Specifically it will reexport all include_prefixes of the given dependency.
58+
///
59+
/// Note that the link name may differ from the crate name.
60+
/// Check your dependencies Cargo.toml for the correct link name.
61+
pub fn reexport_dependency(mut self, link_name: &str) -> Self {
62+
self.reexport_links.insert(link_name.to_owned());
63+
self
64+
}
65+
66+
/// Export the Interface for this crate so that it can be used by downstream
67+
/// crates.
68+
///
69+
/// # Panics
70+
///
71+
/// Currently it is only possible to export a single Interface per crate.
72+
/// If you try to call this method multiple times, it will panic.
73+
pub fn export(mut self) {
74+
use std::sync::atomic::{AtomicBool, Ordering};
75+
static HAS_EXPORTED: AtomicBool = AtomicBool::new(false);
76+
if HAS_EXPORTED
77+
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
78+
.is_err()
79+
{
80+
panic!("cxx-qt-build can only export a single Interface per crate.\nConsider splitting your project into multiple crates.");
81+
}
82+
83+
// Ensure that a link name has been set
84+
if self.manifest.link_name.is_empty() {
85+
panic!("The links key must be set when exporting with CXX-Qt-build");
86+
}
87+
88+
// We automatically reexport all qt_modules and downstream dependencies
89+
// as they will always need to be enabled in the final binary.
90+
// However, we only reexport the headers of libraries that
91+
// are marked as re-export.
92+
let dependencies = reexported_dependencies(&self, &self.dependencies);
93+
94+
self.manifest.exported_include_prefixes = all_include_prefixes(&self, &dependencies);
95+
96+
let manifest_path = dir::crate_target().join("manifest.json");
97+
let manifest_json = serde_json::to_string_pretty(&self.manifest)
98+
.expect("Failed to convert Manifest to JSON!");
99+
std::fs::write(&manifest_path, manifest_json).expect("Failed to write manifest.json!");
100+
println!(
101+
"cargo::metadata=CXX_QT_MANIFEST_PATH={}",
102+
manifest_path.to_string_lossy()
103+
);
104+
}
105+
}
106+
107+
fn all_include_prefixes(interface: &Interface, dependencies: &[Dependency]) -> Vec<String> {
108+
interface
109+
.exported_include_prefixes
110+
.iter()
111+
.cloned()
112+
.chain(
113+
dependencies
114+
.iter()
115+
.flat_map(|dep| &dep.manifest.exported_include_prefixes)
116+
.cloned(),
117+
)
118+
.collect()
119+
}
120+
121+
fn reexported_dependencies(interface: &Interface, dependencies: &[Dependency]) -> Vec<Dependency> {
122+
let mut exported_dependencies = Vec::new();
123+
for link_name in &interface.reexport_links {
124+
if let Some(dependency) = dependencies
125+
.iter()
126+
.find(|dep| &dep.manifest.link_name == link_name)
127+
{
128+
exported_dependencies.push(dependency.clone());
129+
} else {
130+
panic!("Could not find dependency with link name `{link_name}` to reexport!");
131+
}
132+
}
133+
exported_dependencies
134+
}

0 commit comments

Comments
 (0)