Skip to content

cxx-qt-build: return an Interface from CxxQtBuilder #1287

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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
140 changes: 4 additions & 136 deletions crates/cxx-qt-build/src/dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,100 +7,9 @@

use serde::{Deserialize, Serialize};

use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::path::PathBuf;

/// When generating a library with cxx-qt-build, the library may need to export certain flags or headers.
/// These are all specified by this Interface struct, which should be passed to the [crate::CxxQtBuilder::library] function.
pub struct Interface {
// The name of the links keys, whose CXX-Qt dependencies to reexport
pub(crate) reexport_links: HashSet<String>,
pub(crate) exported_include_prefixes: Vec<String>,
pub(crate) exported_include_directories: Vec<(PathBuf, String)>,
// TODO: In future, we want to also set up the include paths so that you can include anything
// from the crates source directory.
// Once this is done, this flag should indicate whether or not to export our own crates source
// directory to downstream dependencies?
// export_crate_directory: bool,
}

impl Default for Interface {
fn default() -> Self {
Self {
reexport_links: HashSet::new(),
exported_include_prefixes: vec![super::crate_name()],
exported_include_directories: Vec::new(),
}
}
}

impl Interface {
/// Export all headers with the given prefix to downstream dependencies
///
/// Note: This will overwrite any previously specified header_prefixes, including the default
/// header_prefix of this crate.
///
/// This function will panic if any of the given prefixes are already exported through the
/// [Self::export_include_directory] function.
pub fn export_include_prefixes<'a>(
mut self,
prefixes: impl IntoIterator<Item = &'a str>,
) -> Self {
let prefixes = prefixes.into_iter().map(|item| item.to_string()).collect();

let mut exported_prefixes = self
.exported_include_directories
.iter()
.map(|(_path, prefix)| prefix);
for prefix in &prefixes {
if let Some(duplicate) =
exported_prefixes.find(|exported_prefix| exported_prefix.starts_with(prefix))
{
panic!("Duplicate export_prefix! Cannot export `{prefix}`, as `{duplicate}` is already exported as an export_include_directory!");
}
}

self.exported_include_prefixes = prefixes;
self
}

/// Add a directory that will be added as an include directory under the given prefix.
///
/// The prefix will automatically be exported (see also: [Self::export_include_prefixes])
///
/// This function will panic if the given prefix is already exported.
pub fn export_include_directory(mut self, directory: impl AsRef<Path>, prefix: &str) -> Self {
let mut exported_prefixes = self.exported_include_prefixes.iter().chain(
self.exported_include_directories
.iter()
.map(|(_path, prefix)| prefix),
);
if let Some(duplicate) =
exported_prefixes.find(|exported_prefix| exported_prefix.starts_with(prefix))
{
panic!("Duplicate export_prefix! Cannot export `{prefix}`, as `{duplicate}` is already exported!");
}

self.exported_include_directories
.push((directory.as_ref().into(), prefix.to_owned()));
self
}

/// Reexport the dependency with the given link name.
/// This will make the dependency available to downstream dependencies.
///
/// Specifically it will reexport all include_prefixes of the given dependency
/// as well as any definitions made by that dependency.
///
/// Note that the link name may differ from the crate name.
/// Check your dependencies manifest file for the correct link name.
pub fn reexport_dependency(mut self, link_name: &str) -> Self {
self.reexport_links.insert(link_name.to_owned());
self
}
}

#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Default, Serialize, Deserialize)]
/// This struct is used by cxx-qt-build internally to propagate data through to downstream
/// dependencies
pub(crate) struct Manifest {
Expand All @@ -122,8 +31,8 @@ pub(crate) struct Dependency {
}

impl Dependency {
/// This function will search the environment for all dependencies that have been set up with
/// CxxQtBuilder::library.
/// This function will search the environment for all direct dependencies that have exported
/// their Interface via [crate::Interface::export].
/// They export their manifest paths as metadata, which will be exposed to us as an environment
/// variable.
/// We extract those paths here, parse the manifest and make sure to set it up correctly as a
Expand Down Expand Up @@ -162,44 +71,3 @@ pub(crate) fn initializers(dependencies: &[Dependency]) -> Vec<qt_build_utils::I
.flat_map(|dep| dep.manifest.initializers.iter().cloned())
.collect()
}

pub(crate) fn all_include_prefixes(
interface: &Interface,
dependencies: &[Dependency],
) -> Vec<String> {
interface
.exported_include_prefixes
.iter()
.cloned()
.chain(
interface
.exported_include_directories
.iter()
.map(|(_path, prefix)| prefix.clone()),
)
.chain(
dependencies
.iter()
.flat_map(|dep| &dep.manifest.exported_include_prefixes)
.cloned(),
)
.collect()
}

pub(crate) fn reexported_dependencies(
interface: &Interface,
dependencies: &[Dependency],
) -> Vec<Dependency> {
let mut exported_dependencies = Vec::new();
for link_name in &interface.reexport_links {
if let Some(dependency) = dependencies
.iter()
.find(|dep| &dep.manifest.link_name == link_name)
{
exported_dependencies.push(dependency.clone());
} else {
panic!("Could not find dependency with link name `{link_name}` to reexport!");
}
}
exported_dependencies
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-build/src/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ pub(crate) fn initializers(key: &str) -> PathBuf {
path
}

pub(crate) fn manifest() -> Option<PathBuf> {
std::env::var("CARGO_MANIFEST_DIR").ok().map(PathBuf::from)
}

#[cfg(unix)]
pub(crate) fn symlink_or_copy_directory(
source: impl AsRef<Path>,
Expand Down
134 changes: 134 additions & 0 deletions crates/cxx-qt-build/src/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Leon Matthes <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

//! This modules contains utilities for specifying interfaces with cxx-qt-build.

use core::panic;
use std::collections::HashSet;

use crate::{dir, Dependency, Manifest};

/// When generating a library with cxx-qt-build, the library may need to export certain flags or headers.
/// These are all specified by this Interface struct.
pub struct Interface {
// The name of the links keys, whose CXX-Qt dependencies to reexport
pub(crate) reexport_links: HashSet<String>,
pub(crate) exported_include_prefixes: Vec<String>,
pub(crate) manifest: Manifest,
pub(crate) dependencies: Vec<Dependency>,
}

impl Default for Interface {
fn default() -> Self {
Self {
reexport_links: HashSet::new(),
// TODO: This doesn't currently match the include_prefix that is specified by e.g.
// cxx-qt-lib build script.
// In this case this is a happy accident, as we don't want to actually export the
// `include_prefix` in cxx-qt-lib (which is "private/").
// But we do need to unify this.
exported_include_prefixes: vec![super::crate_name()],
manifest: Manifest::default(),
dependencies: Vec::new(),
}
}
}

impl Interface {
/// Export all headers with the given prefix to downstream dependencies
///
/// Note: This will overwrite any previously specified header_prefixes, including the default
/// header_prefix of this crate.
pub fn export_include_prefixes<'a>(
mut self,
prefixes: impl IntoIterator<Item = &'a str>,
) -> Self {
let prefixes = prefixes.into_iter().map(|item| item.to_string()).collect();

self.exported_include_prefixes = prefixes;
self
}

/// Reexport the dependency with the given link name.
/// This will make the dependency available to downstream dependencies.
///
/// Specifically it will reexport all include_prefixes of the given dependency.
///
/// Note that the link name may differ from the crate name.
/// Check your dependencies Cargo.toml for the correct link name.
pub fn reexport_dependency(mut self, link_name: &str) -> Self {
self.reexport_links.insert(link_name.to_owned());
self
}

/// Export the Interface for this crate so that it can be used by downstream
/// crates.
///
/// # Panics
///
/// Currently it is only possible to export a single Interface per crate.
/// If you try to call this method multiple times, it will panic.
pub fn export(mut self) {
use std::sync::atomic::{AtomicBool, Ordering};
static HAS_EXPORTED: AtomicBool = AtomicBool::new(false);
if HAS_EXPORTED
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
panic!("cxx-qt-build can only export a single Interface per crate.\nConsider splitting your project into multiple crates.");
}

// Ensure that a link name has been set
if self.manifest.link_name.is_empty() {
panic!("The links key must be set when exporting with CXX-Qt-build");
}

// We automatically reexport all qt_modules and downstream dependencies
// as they will always need to be enabled in the final binary.
// However, we only reexport the headers of libraries that
// are marked as re-export.
let dependencies = reexported_dependencies(&self, &self.dependencies);

self.manifest.exported_include_prefixes = all_include_prefixes(&self, &dependencies);

let manifest_path = dir::crate_target().join("manifest.json");
let manifest_json = serde_json::to_string_pretty(&self.manifest)
.expect("Failed to convert Manifest to JSON!");
std::fs::write(&manifest_path, manifest_json).expect("Failed to write manifest.json!");
println!(
"cargo::metadata=CXX_QT_MANIFEST_PATH={}",
manifest_path.to_string_lossy()
);
}
}

fn all_include_prefixes(interface: &Interface, dependencies: &[Dependency]) -> Vec<String> {
interface
.exported_include_prefixes
.iter()
.cloned()
.chain(
dependencies
.iter()
.flat_map(|dep| &dep.manifest.exported_include_prefixes)
.cloned(),
)
.collect()
}

fn reexported_dependencies(interface: &Interface, dependencies: &[Dependency]) -> Vec<Dependency> {
let mut exported_dependencies = Vec::new();
for link_name in &interface.reexport_links {
if let Some(dependency) = dependencies
.iter()
.find(|dep| &dep.manifest.link_name == link_name)
{
exported_dependencies.push(dependency.clone());
} else {
panic!("Could not find dependency with link name `{link_name}` to reexport!");
}
}
exported_dependencies
}
Loading
Loading