cxx_qt_build/
dependencies.rs1use serde::{Deserialize, Serialize};
9
10use std::collections::{HashMap, HashSet};
11use std::path::{Path, PathBuf};
12
13pub struct Interface {
16 pub(crate) compile_definitions: HashMap<String, Option<String>>,
17 pub(crate) initializers: Vec<PathBuf>,
18 pub(crate) reexport_links: HashSet<String>,
20 pub(crate) exported_include_prefixes: Vec<String>,
21 pub(crate) exported_include_directories: Vec<(PathBuf, String)>,
22 }
28
29impl Default for Interface {
30 fn default() -> Self {
31 Self {
32 compile_definitions: HashMap::new(),
33 initializers: Vec::new(),
34 reexport_links: HashSet::new(),
35 exported_include_prefixes: vec![super::crate_name()],
36 exported_include_directories: Vec::new(),
37 }
38 }
39}
40
41impl Interface {
42 pub fn define(mut self, variable: &str, value: Option<&str>) -> Self {
51 use std::collections::hash_map::Entry::*;
52
53 let entry = self.compile_definitions.entry(variable.to_owned());
54 match entry {
55 Vacant(entry) => entry.insert(value.map(String::from)),
56 Occupied(entry) => {
57 if entry.get().as_deref() == value {
58 println!("Warning: Silently ignoring duplicate compiler definition for {variable} with {value:?}.");
59 }
60 panic!(
61 "Cxx-Qt-build - Error: Interface::define - Duplicate compile-time definition for variable {variable} with value {value:?}!"
62 );
63 }
64 };
65 self
66 }
67
68 pub fn initializer(mut self, path: impl AsRef<Path>) -> Self {
75 let path = PathBuf::from(path.as_ref());
76 let path = path
77 .canonicalize()
78 .expect("Failed to canonicalize path to initializer! Does the path exist?");
79 self.initializers.push(path);
80 self
81 }
82
83 pub fn export_include_prefixes<'a>(
91 mut self,
92 prefixes: impl IntoIterator<Item = &'a str>,
93 ) -> Self {
94 let prefixes = prefixes.into_iter().map(|item| item.to_string()).collect();
95
96 let mut exported_prefixes = self
97 .exported_include_directories
98 .iter()
99 .map(|(_path, prefix)| prefix);
100 for prefix in &prefixes {
101 if let Some(duplicate) =
102 exported_prefixes.find(|exported_prefix| exported_prefix.starts_with(prefix))
103 {
104 panic!("Duplicate export_prefix! Cannot export `{prefix}`, as `{duplicate}` is already exported as an export_include_directory!");
105 }
106 }
107
108 self.exported_include_prefixes = prefixes;
109 self
110 }
111
112 pub fn export_include_directory(mut self, directory: impl AsRef<Path>, prefix: &str) -> Self {
118 let mut exported_prefixes = self.exported_include_prefixes.iter().chain(
119 self.exported_include_directories
120 .iter()
121 .map(|(_path, prefix)| prefix),
122 );
123 if let Some(duplicate) =
124 exported_prefixes.find(|exported_prefix| exported_prefix.starts_with(prefix))
125 {
126 panic!("Duplicate export_prefix! Cannot export `{prefix}`, as `{duplicate}` is already exported!");
127 }
128
129 self.exported_include_directories
130 .push((directory.as_ref().into(), prefix.to_owned()));
131 self
132 }
133
134 pub fn reexport_dependency(mut self, link_name: &str) -> Self {
143 self.reexport_links.insert(link_name.to_owned());
144 self
145 }
146}
147
148#[derive(Clone, Serialize, Deserialize)]
149pub(crate) struct Manifest {
152 pub(crate) name: String,
153 pub(crate) link_name: String,
154 pub(crate) qt_modules: Vec<String>,
155 pub(crate) defines: Vec<(String, Option<String>)>,
156 pub(crate) initializers: Vec<PathBuf>,
157 pub(crate) exported_include_prefixes: Vec<String>,
158}
159
160#[derive(Clone)]
161pub(crate) struct Dependency {
164 pub(crate) path: PathBuf,
166 pub(crate) manifest: Manifest,
168}
169
170impl Dependency {
171 pub(crate) fn find_all() -> Vec<Dependency> {
180 std::env::vars_os()
181 .map(|(var, value)| (var.to_string_lossy().to_string(), value))
182 .filter(|(var, _)| var.starts_with("DEP_") && var.ends_with("_CXX_QT_MANIFEST_PATH"))
183 .map(|(_, manifest_path)| {
184 let manifest_path = PathBuf::from(manifest_path);
185 let manifest: Manifest = serde_json::from_str(
186 &std::fs::read_to_string(&manifest_path)
187 .expect("Could not read dependency manifest file!"),
188 )
189 .expect("Could not deserialize dependency manifest file!");
190
191 println!(
192 "cxx-qt-build: Discovered dependency `{}` at: {}",
193 manifest.name,
194 manifest_path.to_string_lossy()
195 );
196 Dependency {
197 path: manifest_path.parent().unwrap().to_owned(),
198 manifest,
199 }
200 })
201 .collect()
202 }
203}
204
205pub(crate) fn initializer_paths(
206 interface: Option<&Interface>,
207 dependencies: &[Dependency],
208) -> HashSet<PathBuf> {
209 dependencies
210 .iter()
211 .flat_map(|dep| dep.manifest.initializers.iter().cloned())
212 .chain(
213 interface
214 .iter()
215 .flat_map(|interface| interface.initializers.iter().cloned()),
216 )
217 .collect()
218}
219
220pub(crate) fn all_include_prefixes(
221 interface: &Interface,
222 dependencies: &[Dependency],
223) -> Vec<String> {
224 interface
225 .exported_include_prefixes
226 .iter()
227 .cloned()
228 .chain(
229 interface
230 .exported_include_directories
231 .iter()
232 .map(|(_path, prefix)| prefix.clone()),
233 )
234 .chain(
235 dependencies
236 .iter()
237 .flat_map(|dep| &dep.manifest.exported_include_prefixes)
238 .cloned(),
239 )
240 .collect()
241}
242
243pub(crate) fn reexported_dependencies(
244 interface: &Interface,
245 dependencies: &[Dependency],
246) -> Vec<Dependency> {
247 let mut exported_dependencies = Vec::new();
248 for link_name in &interface.reexport_links {
249 if let Some(dependency) = dependencies
250 .iter()
251 .find(|dep| &dep.manifest.link_name == link_name)
252 {
253 exported_dependencies.push(dependency.clone());
254 } else {
255 panic!("Could not find dependency with link name `{link_name}` to reexport!");
256 }
257 }
258 exported_dependencies
259}
260
261pub(crate) fn all_compile_definitions(
262 interface: Option<&Interface>,
263 dependencies: &[Dependency],
264) -> Vec<(String, Option<String>)> {
265 let mut definitions: HashMap<String, (Option<String>, String)> = interface
268 .iter()
269 .flat_map(|interface| &interface.compile_definitions)
270 .map(|(key, value)| (key.clone(), (value.clone(), crate::crate_name())))
271 .collect();
272
273 for dependency in dependencies {
274 for (variable, value) in &dependency.manifest.defines {
275 use std::collections::hash_map::Entry::*;
276 let entry = definitions.entry(variable.to_owned());
277
278 match entry {
279 Vacant(entry) => {
280 entry.insert((value.to_owned(), dependency.manifest.name.clone()));
281 }
282 Occupied(entry) => {
283 let existing_value = &entry.get().0;
284 if existing_value != value {
286 panic!("Conflicting compiler definitions requested!\nCrate {existing} exports {variable}={existing_value:?}, and crate {conflicting} exports {variable}={value:?}",
287 existing=entry.get().1,
288 conflicting = dependency.manifest.name);
289 }
290 }
291 }
292 }
293 }
294
295 definitions
296 .into_iter()
297 .map(|(key, (value, _crate_name))| (key, value))
298 .collect()
299}