Skip to content
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

cxx-qt-gen: add support for passing in CfgEvaluator #1144

Closed
wants to merge 3 commits into from
Closed
Changes from 1 commit
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
Next Next commit
cxx-qt-gen: add support for passing in CfgEvaluator
ahayzen-kdab committed Jan 27, 2025
commit e49a9bba2282ab69b17a41ac72163f0e3cbd95f3
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `QDateTime::from_string` to parse `QDateTime` from a `QString`.
- Support for further types: `QUuid`
- New example: Basic greeter app
- Support for `cfg` attributes through to C++ generation

### Fixed

7 changes: 5 additions & 2 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ use std::{
};

use cxx_qt_gen::{
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks,
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks, GeneratedOpt,
GeneratedRustBlocks, Parser,
};

@@ -103,6 +103,9 @@ impl GeneratedCpp {
// The include path we inject needs any prefix (eg the crate name) too
let include_ident = format!("{include_prefix}/{file_ident}");

let mut cxx_qt_opt = GeneratedOpt::default();
cxx_qt_opt.cfg_evaluator = Box::new(cfg_evaluator::CargoEnvCfgEvaluator);

// Loop through the items looking for any CXX or CXX-Qt blocks
let mut found_bridge = false;
for item in &file.items {
@@ -132,7 +135,7 @@ impl GeneratedCpp {
let parser = Parser::from(m.clone())
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
let generated_cpp = GeneratedCppBlocks::from(&parser)
let generated_cpp = GeneratedCppBlocks::from(&parser, &cxx_qt_opt)
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
let generated_rust = GeneratedRustBlocks::from(&parser)
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ exclude = ["update_expected.sh"]
rust-version = "1.64.0"

[dependencies]
cxx-gen.workspace = true
proc-macro2.workspace = true
syn.workspace = true
quote.workspace = true
222 changes: 222 additions & 0 deletions crates/cxx-qt-gen/src/generator/cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// SPDX-FileCopyrightText: CXX Authors
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
// SPDX-FileContributor: David Tolnay <dtolnay@gmail.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::syntax::cfg::{parse_attribute, CfgExpr};
use cxx_gen::{CfgEvaluator, CfgResult};
use quote::quote;
use syn::{Attribute, Error, LitStr};

pub(crate) fn try_eval_attributes(
cfg_evaluator: &dyn CfgEvaluator,
attrs: &[Attribute],
) -> Result<bool, Error> {
// Build a single CfgExpr from the Attributes
let cfg_expr = attrs
.iter()
.map(parse_attribute)
.collect::<Result<Vec<CfgExpr>, Error>>()?
.into_iter()
.reduce(|mut acc, e| {
acc.merge(e);
acc
});

// Evaluate the CfgExpr against the CfgEvaluator
if let Some(cfg_expr) = cfg_expr {
try_eval(cfg_evaluator, &cfg_expr).map_err(|errs| {
errs.into_iter()
.reduce(|mut acc, e| {
acc.combine(e);
acc
})
.expect("There should be at least one error")
})
} else {
Ok(true)
}
}

fn try_eval(cfg_evaluator: &dyn CfgEvaluator, expr: &CfgExpr) -> Result<bool, Vec<Error>> {
match expr {
CfgExpr::Unconditional => Ok(true),
CfgExpr::Eq(ident, string) => {
let key = ident.to_string();
let value = string.as_ref().map(LitStr::value);
match cfg_evaluator.eval(&key, value.as_deref()) {
CfgResult::True => Ok(true),
CfgResult::False => Ok(false),
CfgResult::Undetermined { msg } => {
let span = quote!(#ident #string);
Err(vec![Error::new_spanned(span, msg)])
}
}
}
CfgExpr::All(list) => {
let mut all_errors = Vec::new();
for subexpr in list {
match try_eval(cfg_evaluator, subexpr) {
Ok(true) => {}
Ok(false) => return Ok(false),
Err(errors) => all_errors.extend(errors),
}
}
if all_errors.is_empty() {
Ok(true)
} else {
Err(all_errors)
}
}
CfgExpr::Any(list) => {
let mut all_errors = Vec::new();
for subexpr in list {
match try_eval(cfg_evaluator, subexpr) {
Ok(true) => return Ok(true),
Ok(false) => {}
Err(errors) => all_errors.extend(errors),
}
}
if all_errors.is_empty() {
Ok(false)
} else {
Err(all_errors)
}
}
CfgExpr::Not(subexpr) => match try_eval(cfg_evaluator, subexpr) {
Ok(value) => Ok(!value),
Err(errors) => Err(errors),
},
}
}

#[cfg(test)]
mod tests {
use super::*;

use crate::generator::UnsupportedCfgEvaluator;
use std::collections::HashMap;
use syn::{parse_quote, ItemMod};

#[derive(Default)]
struct CfgEvaluatorTest<'a> {
cfgs: HashMap<&'a str, Option<&'a str>>,
}

impl<'a> CfgEvaluator for CfgEvaluatorTest<'a> {
fn eval(&self, name: &str, query_value: Option<&str>) -> CfgResult {
if self.cfgs.get(name) == Some(&query_value) {
CfgResult::True
} else {
CfgResult::False
}
}
}

#[test]
fn test_try_eval_attributes_eq() {
let module: ItemMod = parse_quote! {
#[cfg(a = "1")]
#[cfg(b = "2")]
mod module;
};
let mut cfg_evaluator = Box::new(CfgEvaluatorTest::default());
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
false
);

// Insert cfg into map
cfg_evaluator.cfgs.insert("a", Some("1"));
cfg_evaluator.cfgs.insert("b", Some("2"));
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
true
);
}

#[test]
fn test_try_eval_attributes_any() {
let module: ItemMod = parse_quote! {
#[cfg(any(a = "1", b = "2"))]
mod module;
};
let mut cfg_evaluator = Box::new(CfgEvaluatorTest::default());
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
false
);

// Insert cfg into map
cfg_evaluator.cfgs.insert("a", Some("1"));
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
true
);
}

#[test]
fn test_try_eval_attributes_all() {
let module: ItemMod = parse_quote! {
#[cfg(all(a = "1", b = "2"))]
mod module;
};
let mut cfg_evaluator = Box::new(CfgEvaluatorTest::default());
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
false
);

// Insert cfg into map
cfg_evaluator.cfgs.insert("a", Some("1"));
cfg_evaluator.cfgs.insert("b", Some("2"));
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
true
);
}

#[test]
fn test_try_eval_attributes_not() {
let module: ItemMod = parse_quote! {
#[cfg(not(a = "1"))]
mod module;
};
let mut cfg_evaluator = Box::new(CfgEvaluatorTest::default());
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
true
);

// Insert cfg into map
cfg_evaluator.cfgs.insert("a", Some("1"));
assert_eq!(
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
false
);
}

#[test]
fn test_try_eval_unconditional() {
let cfg_expr = CfgExpr::Unconditional;
let cfg_evaluator = Box::new(UnsupportedCfgEvaluator);
assert_eq!(try_eval(cfg_evaluator.as_ref(), &cfg_expr).unwrap(), true);
}

#[test]
fn test_try_eval_attributes_undetermined_err() {
let module: ItemMod = parse_quote! {
#[cfg(a = "1")]
#[cfg(all(a = "1", b = "2"))]
#[cfg(any(a = "1", b = "2"))]
#[cfg(not(a = "1"))]
mod module;
};
let cfg_evaluator = Box::new(UnsupportedCfgEvaluator);
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[0..1]).is_err());
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[1..2]).is_err());
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[2..3]).is_err());
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[3..4]).is_err());
}
}
25 changes: 15 additions & 10 deletions crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs
Original file line number Diff line number Diff line change
@@ -4,8 +4,10 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{
generator::cpp::signal::generate_cpp_signal, naming::TypeNames,
parser::externcxxqt::ParsedExternCxxQt, CppFragment,
generator::{cpp::signal::generate_cpp_signal, GeneratedOpt},
naming::TypeNames,
parser::externcxxqt::ParsedExternCxxQt,
CppFragment,
};
use std::collections::BTreeSet;
use syn::Result;
@@ -23,18 +25,20 @@ pub struct GeneratedCppExternCxxQtBlocks {
pub fn generate(
blocks: &[ParsedExternCxxQt],
type_names: &TypeNames,
opt: &GeneratedOpt,
) -> Result<Vec<GeneratedCppExternCxxQtBlocks>> {
let mut out = vec![];

for block in blocks {
for signal in &block.signals {
let mut block = GeneratedCppExternCxxQtBlocks::default();
let qobject_name = type_names.lookup(&signal.qobject_ident)?;
let data = generate_cpp_signal(signal, qobject_name, type_names)?;
block.includes = data.includes;
block.forward_declares = data.forward_declares;
block.fragments = data.fragments;
let data = generate_cpp_signal(signal, qobject_name, type_names, opt)?;
debug_assert!(data.methods.is_empty());
let block = GeneratedCppExternCxxQtBlocks {
includes: data.includes,
forward_declares: data.forward_declares,
fragments: data.fragments,
};
out.push(block);
}
}
@@ -70,9 +74,10 @@ mod tests {
.unwrap()];

// Unknown types
assert!(generate(&blocks, &TypeNames::default()).is_err());
let opt = GeneratedOpt::default();
assert!(generate(&blocks, &TypeNames::default(), &opt).is_err());

let generated = generate(&blocks, &TypeNames::mock()).unwrap();
let generated = generate(&blocks, &TypeNames::mock(), &opt).unwrap();
assert_eq!(generated.len(), 2);
}

@@ -97,7 +102,7 @@ mod tests {
let mut type_names = TypeNames::default();
type_names.mock_insert("ObjRust", None, Some("ObjCpp"), Some("mynamespace"));

let generated = generate(&blocks, &type_names).unwrap();
let generated = generate(&blocks, &type_names, &GeneratedOpt::default()).unwrap();
assert_eq!(generated.len(), 1);
}
}
Loading