diff --git a/.github/workflows/github-cxx-qt-tests.yml b/.github/workflows/github-cxx-qt-tests.yml index 4c77695dc..66fbe68ee 100644 --- a/.github/workflows/github-cxx-qt-tests.yml +++ b/.github/workflows/github-cxx-qt-tests.yml @@ -71,11 +71,22 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + # Note we need to match the LLVM and Rust versions + # + # See versions from the table in this link + # https://github.com/taiki-e/cargo-llvm-cov?tab=readme-ov-file#get-coverage-of-cc-code-linked-to-rust-librarybinary + - name: Install llvm 17 + run: | + sudo apt-get update && sudo apt-get install -y llvm-17 + test -d /usr/lib/llvm-17/bin/ - name: Setup toolchain run: | + # Note that the llvm version needs to match, see the link above rustup default 1.77.2 cargo install grcov - rustup component add llvm-tools rustfmt + rustup component add rustfmt + # Ensure we do not have any existing coverage files + - run: rm -f coverage/*.profraw - name: build env: RUSTFLAGS: -Cinstrument-coverage @@ -87,7 +98,7 @@ jobs: LLVM_PROFILE_FILE: coverage/coverage_data-%p-%m.profraw run: cargo test --lib --package cxx-qt-gen - name: generate-report - run: grcov . -s . --binary-path ./target/debug/ -t lcov --branch --ignore-not-existing -o ./target/debug/lcov.info --excl-start CODECOV_EXCLUDE_START --excl-stop CODECOV_EXCLUDE_STOP + run: grcov . -s . --binary-path ./target/debug/ -t lcov --branch --ignore-not-existing --llvm --llvm-path /usr/lib/llvm-17/bin/ -o ./target/debug/lcov.info --excl-start CODECOV_EXCLUDE_START --excl-stop CODECOV_EXCLUDE_STOP - name: upload-report uses: codecov/codecov-action@v5 with: @@ -96,6 +107,13 @@ jobs: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} verbose: true + - name: Upload GitHub Actions artifacts of lcov + if: always() + uses: actions/upload-artifact@v4 + with: + name: lcov + path: ./target/debug/lcov.info + if-no-files-found: ignore build-wasm: name: Ubuntu 24.04 (wasm_32) Qt 6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9986f70..755d41633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index d56a8f44c..c76f4f138 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -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) diff --git a/crates/cxx-qt-gen/Cargo.toml b/crates/cxx-qt-gen/Cargo.toml index 1b33cce8e..0191461bf 100644 --- a/crates/cxx-qt-gen/Cargo.toml +++ b/crates/cxx-qt-gen/Cargo.toml @@ -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 diff --git a/crates/cxx-qt-gen/src/generator/cfg.rs b/crates/cxx-qt-gen/src/generator/cfg.rs new file mode 100644 index 000000000..0aa26e9cb --- /dev/null +++ b/crates/cxx-qt-gen/src/generator/cfg.rs @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: CXX Authors +// SPDX-FileContributor: Andrew Hayzen +// SPDX-FileContributor: David Tolnay +// +// 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 { + // Build a single CfgExpr from the Attributes + let cfg_expr = attrs + .iter() + .map(parse_attribute) + .collect::, 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> { + 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()); + } +} diff --git a/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs b/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs index 09453dbbd..aa5ffcf5d 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs @@ -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> { 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); } } diff --git a/crates/cxx-qt-gen/src/generator/cpp/inherit.rs b/crates/cxx-qt-gen/src/generator/cpp/inherit.rs index c6dbcf98a..cbd3eb39e 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/inherit.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/inherit.rs @@ -5,7 +5,11 @@ use indoc::formatdoc; use crate::{ - generator::cpp::{fragment::CppFragment, qobject::GeneratedCppQObjectBlocks}, + generator::{ + cfg::try_eval_attributes, + cpp::{fragment::CppFragment, qobject::GeneratedCppQObjectBlocks}, + GeneratedOpt, + }, naming::cpp::syn_type_to_cpp_return_type, naming::TypeNames, parser::inherit::ParsedInheritedMethod, @@ -17,10 +21,16 @@ pub fn generate( inherited_methods: &[&ParsedInheritedMethod], base_class: &Option, type_names: &TypeNames, + opt: &GeneratedOpt, ) -> Result { let mut result = GeneratedCppQObjectBlocks::default(); for &method in inherited_methods { + // Skip if the cfg attributes are not resolved to true + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &method.cfgs)? { + continue; + } + let return_type = syn_type_to_cpp_return_type(&method.method.sig.output, type_names)?; // Note that no qobject macro with no base class is an error // @@ -47,6 +57,7 @@ pub fn generate( #[cfg(test)] mod tests { + use crate::generator::TestCfgEvaluator; use pretty_assertions::assert_str_eq; use syn::{parse_quote, ForeignItemFn}; @@ -62,7 +73,12 @@ mod tests { let method = ParsedInheritedMethod::parse(method, Safety::Safe, CaseConversion::none())?; let inherited_methods = vec![&method]; let base_class = base_class.map(|s| s.to_owned()); - generate(&inherited_methods, &base_class, &TypeNames::default()) + generate( + &inherited_methods, + &base_class, + &TypeNames::default(), + &GeneratedOpt::default(), + ) } fn assert_generated_eq(expected: &str, generated: &GeneratedCppQObjectBlocks) { @@ -71,6 +87,27 @@ mod tests { assert_str_eq!(header, expected); } + #[test] + fn test_cfg() { + let method = parse_quote! { + #[cfg(test_cfg_disabled)] + fn test(self: &T, a: B, b: C); + }; + let method = + ParsedInheritedMethod::parse(method, Safety::Safe, CaseConversion::none()).unwrap(); + let inherited_methods = vec![&method]; + let base_class = Some("TestBaseClass".to_owned()); + let opt = GeneratedOpt { + cfg_evaluator: Box::new(TestCfgEvaluator { + result: Some(false), + }), + }; + let generated = + generate(&inherited_methods, &base_class, &TypeNames::default(), &opt).unwrap(); + + assert!(generated.methods.is_empty()); + } + #[test] fn test_immutable() { let generated = generate_from_foreign( diff --git a/crates/cxx-qt-gen/src/generator/cpp/method.rs b/crates/cxx-qt-gen/src/generator/cpp/method.rs index 4281db665..210c8603e 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/method.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/method.rs @@ -3,11 +3,15 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::cpp::get_cpp_params; use crate::{ - generator::cpp::{ - fragment::{CppFragment, CppNamedType}, - qobject::GeneratedCppQObjectBlocks, + generator::{ + cfg::try_eval_attributes, + cpp::{ + fragment::{CppFragment, CppNamedType}, + get_cpp_params, + qobject::GeneratedCppQObjectBlocks, + GeneratedOpt, + }, }, naming::cpp::{syn_return_type_to_cpp_except, syn_type_to_cpp_return_type}, naming::TypeNames, @@ -18,9 +22,15 @@ use syn::Result; pub fn generate_cpp_methods( invokables: &Vec<&ParsedMethod>, type_names: &TypeNames, + opt: &GeneratedOpt, ) -> Result { let mut generated = GeneratedCppQObjectBlocks::default(); for &invokable in invokables { + // Skip if the cfg attributes are not resolved to true + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &invokable.cfgs)? { + continue; + } + let return_cxx_ty = syn_type_to_cpp_return_type(&invokable.method.sig.output, type_names)?; let parameters: Vec = get_cpp_params(&invokable.method, type_names)?; @@ -77,10 +87,31 @@ mod tests { use super::*; use crate::generator::cpp::property::tests::require_header; + use crate::generator::TestCfgEvaluator; use pretty_assertions::assert_str_eq; use std::collections::HashSet; use syn::{parse_quote, ForeignItemFn}; + #[test] + fn test_generate_cpp_invokables_cfg() { + let method: ForeignItemFn = parse_quote! { + #[cfg(test_cfg_disabled)] + #[cxx_name = "voidInvokable"] + fn void_invokable(self: &MyObject); + }; + let invokables = vec![ParsedMethod::mock_qinvokable(&method)]; + let type_names = TypeNames::mock(); + let opt = GeneratedOpt { + cfg_evaluator: Box::new(TestCfgEvaluator { + result: Some(false), + }), + }; + let generated = + generate_cpp_methods(&invokables.iter().collect(), &type_names, &opt).unwrap(); + + assert!(generated.methods.is_empty()); + } + #[test] fn test_generate_cpp_invokables() { let method1: ForeignItemFn = parse_quote! { @@ -122,7 +153,12 @@ mod tests { let mut type_names = TypeNames::mock(); type_names.mock_insert("QColor", None, None, None); - let generated = generate_cpp_methods(&invokables.iter().collect(), &type_names).unwrap(); + let generated = generate_cpp_methods( + &invokables.iter().collect(), + &type_names, + &GeneratedOpt::default(), + ) + .unwrap(); // methods assert_eq!(generated.methods.len(), 5); @@ -168,7 +204,8 @@ mod tests { type_names.mock_insert("A", None, Some("A1"), None); type_names.mock_insert("B", None, Some("B2"), None); - let generated = generate_cpp_methods(&invokables, &type_names).unwrap(); + let generated = + generate_cpp_methods(&invokables, &type_names, &GeneratedOpt::default()).unwrap(); // methods assert_eq!(generated.methods.len(), 1); diff --git a/crates/cxx-qt-gen/src/generator/cpp/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/mod.rs index 0f26e6281..5d5b00504 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/mod.rs @@ -23,7 +23,10 @@ use std::collections::BTreeSet; use crate::generator::cpp::fragment::CppNamedType; use crate::naming::cpp::syn_type_to_cpp_type; use crate::naming::TypeNames; -use crate::{generator::structuring, parser::Parser}; +use crate::{ + generator::{structuring, GeneratedOpt}, + parser::Parser, +}; use externcxxqt::GeneratedCppExternCxxQtBlocks; use qobject::GeneratedCppQObject; use syn::{FnArg, ForeignItemFn, Pat, PatIdent, PatType, Result}; @@ -42,7 +45,7 @@ pub struct GeneratedCppBlocks { impl GeneratedCppBlocks { /// Create a [GeneratedCppBlocks] from the given [Parser] object - pub fn from(parser: &Parser) -> Result { + pub fn from(parser: &Parser, opt: &GeneratedOpt) -> Result { let structures = structuring::Structures::new(&parser.cxx_qt_data)?; let mut includes = BTreeSet::new(); @@ -66,11 +69,12 @@ impl GeneratedCppBlocks { qobjects: structures .qobjects .iter() - .map(|qobject| GeneratedCppQObject::from(qobject, &parser.type_names)) + .map(|qobject| GeneratedCppQObject::from(qobject, &parser.type_names, opt)) .collect::>>()?, extern_cxx_qt: externcxxqt::generate( &parser.cxx_qt_data.extern_cxxqt_blocks, &parser.type_names, + opt, )?, }) } @@ -125,7 +129,8 @@ mod tests { }; let parser = Parser::from(module).unwrap(); - let cpp = GeneratedCppBlocks::from(&parser).unwrap(); + let opt = GeneratedOpt::default(); + let cpp = GeneratedCppBlocks::from(&parser, &opt).unwrap(); assert_eq!(cpp.qobjects.len(), 1); assert_eq!(cpp.qobjects[0].name.namespace(), None); } @@ -143,7 +148,8 @@ mod tests { }; let parser = Parser::from(module).unwrap(); - let cpp = GeneratedCppBlocks::from(&parser).unwrap(); + let opt = GeneratedOpt::default(); + let cpp = GeneratedCppBlocks::from(&parser, &opt).unwrap(); assert_eq!(cpp.qobjects[0].name.namespace(), Some("cxx_qt")); } } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs index f7713b612..aa8c1a83b 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs @@ -7,6 +7,7 @@ use crate::generator::structuring::StructuredQObject; use crate::generator::{ cpp::{qobject::GeneratedCppQObjectBlocks, signal::generate_cpp_signals}, naming::{property::QPropertyNames, qobject::QObjectNames}, + GeneratedOpt, }; use crate::{ naming::cpp::syn_type_to_cpp_type, naming::TypeNames, parser::property::ParsedQProperty, @@ -23,6 +24,7 @@ pub fn generate_cpp_properties( qobject_idents: &QObjectNames, type_names: &TypeNames, structured_qobject: &StructuredQObject, + opt: &GeneratedOpt, ) -> Result { let mut generated = GeneratedCppQObjectBlocks::default(); let mut signals = vec![]; @@ -53,6 +55,7 @@ pub fn generate_cpp_properties( &signals.iter().collect(), qobject_idents, type_names, + opt, )?); Ok(generated) @@ -111,6 +114,7 @@ pub mod tests { &qobject_idents, &type_names, &structured_qobject, + &GeneratedOpt::default(), ) } @@ -175,6 +179,7 @@ pub mod tests { &qobject_idents, &type_names, structured_qobject, + &GeneratedOpt::default(), ) .unwrap(); @@ -213,6 +218,7 @@ pub mod tests { &qobject_idents, &type_names, structured_qobject, + &GeneratedOpt::default(), ) .unwrap(); @@ -255,6 +261,7 @@ pub mod tests { &qobject_idents, &type_names, structured_qobject, + &GeneratedOpt::default(), ) .unwrap(); @@ -305,6 +312,7 @@ pub mod tests { &qobject_idents, &type_names, &structured_qobject, + &GeneratedOpt::default(), ) .unwrap(); @@ -487,6 +495,7 @@ pub mod tests { &qobject_idents, &type_names, &structured_qobject, + &GeneratedOpt::default(), ) .unwrap(); diff --git a/crates/cxx-qt-gen/src/generator/cpp/qenum.rs b/crates/cxx-qt-gen/src/generator/cpp/qenum.rs index cb183b1ff..d8ab2f82a 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qenum.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qenum.rs @@ -8,7 +8,11 @@ use std::collections::BTreeSet; use indoc::formatdoc; use syn::Result; -use crate::{parser::qenum::ParsedQEnum, writer::cpp::namespaced}; +use crate::{ + generator::{cfg::try_eval_attributes, GeneratedOpt}, + parser::qenum::ParsedQEnum, + writer::cpp::namespaced, +}; use super::{qobject::GeneratedCppQObjectBlocks, utils::Indent}; @@ -56,10 +60,16 @@ pub fn generate_declaration(qenum: &ParsedQEnum, includes: &mut BTreeSet pub fn generate_on_qobject<'a>( qenums: impl Iterator, + opt: &GeneratedOpt, ) -> Result { let mut generated = GeneratedCppQObjectBlocks::default(); for qenum in qenums { + // Skip if the cfg attributes are not resolved to true + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &qenum.cfgs)? { + continue; + } + let mut qualified_name = qenum.name.cxx_qualified(); let enum_name = qenum.name.cxx_unqualified(); // TODO: this is a workaround for cxx_qualified not returning a fully-qualified @@ -90,11 +100,36 @@ mod tests { use std::assert_eq; use super::*; + use crate::generator::TestCfgEvaluator; use indoc::indoc; use pretty_assertions::assert_str_eq; use quote::format_ident; use syn::parse_quote; + #[test] + fn test_cfg() { + let qenums = [ParsedQEnum::parse( + parse_quote! { + #[cfg(test_cfg_disabled)] + enum MyEnum { + A, B, C + } + }, + Some(format_ident!("MyObject")), + None, + &format_ident!("qobject"), + ) + .unwrap()]; + let opt = GeneratedOpt { + cfg_evaluator: Box::new(TestCfgEvaluator { + result: Some(false), + }), + }; + let generated = generate_on_qobject(qenums.iter(), &opt).unwrap(); + + assert!(generated.methods.is_empty()); + } + #[test] fn generates() { let qenums = [ParsedQEnum::parse( @@ -109,7 +144,7 @@ mod tests { ) .unwrap()]; - let generated = generate_on_qobject(qenums.iter()).unwrap(); + let generated = generate_on_qobject(qenums.iter(), &GeneratedOpt::default()).unwrap(); assert_eq!(generated.includes.len(), 1); assert!(generated.includes.contains("#include ")); assert_eq!(generated.metaobjects.len(), 1); diff --git a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs index 937ff9203..47183a1ee 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs @@ -13,6 +13,7 @@ use crate::{ structuring::StructuredQObject, }, naming::Name, + GeneratedOpt, }; use crate::{naming::TypeNames, parser::qobject::ParsedQObject}; use std::collections::BTreeSet; @@ -99,6 +100,7 @@ impl GeneratedCppQObject { pub fn from( structured_qobject: &StructuredQObject, type_names: &TypeNames, + opt: &GeneratedOpt, ) -> Result { let qobject = structured_qobject.declaration; @@ -136,24 +138,29 @@ impl GeneratedCppQObject { &qobject_idents, type_names, structured_qobject, + opt, )?); generated.blocks.append(&mut generate_cpp_methods( &structured_qobject.methods, type_names, + opt, )?); generated.blocks.append(&mut generate_cpp_signals( &structured_qobject.signals, &qobject_idents, type_names, + opt, )?); generated.blocks.append(&mut inherit::generate( &structured_qobject.inherited_methods, &qobject.base_class.as_ref().map(|ident| ident.to_string()), type_names, + opt, )?); generated.blocks.append(&mut qenum::generate_on_qobject( structured_qobject.qenums.iter().cloned(), + opt, )?); let mut class_initializers = vec![]; @@ -201,9 +208,12 @@ mod tests { let parser = Parser::from(module).unwrap(); let structures = Structures::new(&parser.cxx_qt_data).unwrap(); - let cpp = - GeneratedCppQObject::from(structures.qobjects.first().unwrap(), &TypeNames::mock()) - .unwrap(); + let cpp = GeneratedCppQObject::from( + structures.qobjects.first().unwrap(), + &TypeNames::mock(), + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(cpp.name.cxx_unqualified(), "MyObject"); assert_eq!(cpp.rust_struct.cxx_unqualified(), "MyObjectRust"); assert_eq!(cpp.namespace_internals, "cxx_qt_MyObject"); @@ -240,8 +250,12 @@ mod tests { None, ); - let cpp = - GeneratedCppQObject::from(structures.qobjects.first().unwrap(), &type_names).unwrap(); + let cpp = GeneratedCppQObject::from( + structures.qobjects.first().unwrap(), + &type_names, + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(cpp.namespace_internals, "cxx_qt::cxx_qt_MyObject"); assert_eq!(cpp.blocks.base_classes.len(), 2); assert_eq!(cpp.blocks.base_classes[0], "QStringListModel"); @@ -270,8 +284,12 @@ mod tests { let mut type_names = TypeNames::default(); type_names.mock_insert("MyNamedObject", None, None, None); type_names.mock_insert("MyNamedObjectRust", None, None, None); - let cpp = - GeneratedCppQObject::from(structures.qobjects.first().unwrap(), &type_names).unwrap(); + let cpp = GeneratedCppQObject::from( + structures.qobjects.first().unwrap(), + &type_names, + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(cpp.name.cxx_unqualified(), "MyNamedObject"); assert_eq!(cpp.blocks.metaobjects.len(), 1); assert_eq!( @@ -286,9 +304,12 @@ mod tests { let parser = Parser::from(module).unwrap(); let structures = Structures::new(&parser.cxx_qt_data).unwrap(); - let cpp = - GeneratedCppQObject::from(structures.qobjects.first().unwrap(), &TypeNames::mock()) - .unwrap(); + let cpp = GeneratedCppQObject::from( + structures.qobjects.first().unwrap(), + &TypeNames::mock(), + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(cpp.name.cxx_unqualified(), "MyObject"); assert_eq!(cpp.blocks.metaobjects.len(), 2); assert_eq!( @@ -314,9 +335,12 @@ mod tests { let parser = Parser::from(module).unwrap(); let structures = Structures::new(&parser.cxx_qt_data).unwrap(); - let cpp = - GeneratedCppQObject::from(structures.qobjects.first().unwrap(), &TypeNames::mock()) - .unwrap(); + let cpp = GeneratedCppQObject::from( + structures.qobjects.first().unwrap(), + &TypeNames::mock(), + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(cpp.name.cxx_unqualified(), "MyObject"); assert_eq!(cpp.blocks.metaobjects.len(), 2); assert_eq!( diff --git a/crates/cxx-qt-gen/src/generator/cpp/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/signal.rs index a15fa4bf8..44c9cccb7 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/signal.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/signal.rs @@ -5,11 +5,13 @@ use crate::{ generator::{ + cfg::try_eval_attributes, cpp::{fragment::CppFragment, qobject::GeneratedCppQObjectBlocks}, naming::{ qobject::QObjectNames, signals::{QSignalHelperNames, QSignalNames}, }, + GeneratedOpt, }, naming::{cpp::syn_type_to_cpp_type, Name, TypeNames}, parser::{parameter::ParsedFunctionParameter, signals::ParsedSignal}, @@ -82,9 +84,15 @@ pub fn generate_cpp_signal( signal: &ParsedSignal, qobject_name: &Name, type_names: &TypeNames, + opt: &GeneratedOpt, ) -> Result { let mut generated = CppSignalFragment::default(); + // Skip if the cfg attributes are not resolved to true + if !try_eval_attributes(opt.cfg_evaluator.as_ref(), &signal.cfgs)? { + return Ok(generated); + } + // Add the include we need generated .includes @@ -192,16 +200,19 @@ pub fn generate_cpp_signals( signals: &Vec<&ParsedSignal>, qobject_idents: &QObjectNames, type_names: &TypeNames, + opt: &GeneratedOpt, ) -> Result { let mut generated = GeneratedCppQObjectBlocks::default(); for &signal in signals { - let mut block = GeneratedCppQObjectBlocks::default(); - let data = generate_cpp_signal(signal, &qobject_idents.name, type_names)?; - block.includes = data.includes; - block.forward_declares_namespaced = data.forward_declares; - block.fragments = data.fragments; - block.methods = data.methods; + let data = generate_cpp_signal(signal, &qobject_idents.name, type_names, opt)?; + let mut block = GeneratedCppQObjectBlocks { + includes: data.includes, + forward_declares_namespaced: data.forward_declares, + fragments: data.fragments, + methods: data.methods, + ..Default::default() + }; generated.append(&mut block); } @@ -214,10 +225,32 @@ mod tests { use crate::generator::cpp::property::tests::{require_header, require_pair}; use crate::generator::naming::qobject::tests::create_qobjectname; + use crate::generator::TestCfgEvaluator; use indoc::indoc; use pretty_assertions::assert_str_eq; use syn::{parse_quote, ForeignItemFn}; + #[test] + fn test_generate_cpp_signal_cfg() { + let method: ForeignItemFn = parse_quote! { + #[cfg(test_cfg_disabled)] + #[cxx_name = "dataChanged"] + fn data_changed(self: Pin<&mut MyObject>, trivial: i32, opaque: UniquePtr); + }; + let signal = ParsedSignal::mock(&method); + let signals = vec![&signal]; + let qobject_idents = create_qobjectname(); + let type_names = TypeNames::mock(); + let opt = GeneratedOpt { + cfg_evaluator: Box::new(TestCfgEvaluator { + result: Some(false), + }), + }; + let generated = generate_cpp_signals(&signals, &qobject_idents, &type_names, &opt).unwrap(); + + assert_eq!(generated.methods.len(), 0); + } + #[test] fn test_generate_cpp_signals() { let method: ForeignItemFn = parse_quote! { @@ -230,7 +263,13 @@ mod tests { let mut type_names = TypeNames::mock(); type_names.mock_insert("QColor", None, None, None); - let generated = generate_cpp_signals(&signals, &qobject_idents, &type_names).unwrap(); + let generated = generate_cpp_signals( + &signals, + &qobject_idents, + &type_names, + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(generated.methods.len(), 1); let header = require_header(&generated.methods[0]).unwrap(); @@ -311,7 +350,13 @@ mod tests { let mut type_names = TypeNames::mock(); type_names.mock_insert("A", None, Some("A1"), None); - let generated = generate_cpp_signals(&signals, &qobject_idents, &type_names).unwrap(); + let generated = generate_cpp_signals( + &signals, + &qobject_idents, + &type_names, + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(generated.methods.len(), 1); let header = require_header(&generated.methods[0]).unwrap(); @@ -388,8 +433,13 @@ mod tests { let signals = vec![&signal]; let qobject_idents = create_qobjectname(); - let generated = - generate_cpp_signals(&signals, &qobject_idents, &TypeNames::mock()).unwrap(); + let generated = generate_cpp_signals( + &signals, + &qobject_idents, + &TypeNames::mock(), + &GeneratedOpt::default(), + ) + .unwrap(); assert_eq!(generated.methods.len(), 0); assert_eq!(generated.fragments.len(), 1); @@ -464,7 +514,9 @@ mod tests { let mut type_names = TypeNames::default(); type_names.mock_insert("MyObject", None, None, None); let qobject_name = type_names.lookup(&signal.qobject_ident).unwrap(); - let generated = generate_cpp_signal(&signal, qobject_name, &type_names).unwrap(); + let generated = + generate_cpp_signal(&signal, qobject_name, &type_names, &GeneratedOpt::default()) + .unwrap(); assert_eq!(generated.methods.len(), 0); @@ -541,7 +593,9 @@ mod tests { let mut type_names = TypeNames::default(); type_names.mock_insert("MyObject", None, Some("ObjCpp"), Some("mynamespace")); let qobject_name = type_names.lookup(&signal.qobject_ident).unwrap(); - let generated = generate_cpp_signal(&signal, qobject_name, &type_names).unwrap(); + let generated = + generate_cpp_signal(&signal, qobject_name, &type_names, &GeneratedOpt::default()) + .unwrap(); assert_eq!(generated.methods.len(), 0); diff --git a/crates/cxx-qt-gen/src/generator/mod.rs b/crates/cxx-qt-gen/src/generator/mod.rs index 58d50ecca..6d512f6d7 100644 --- a/crates/cxx-qt-gen/src/generator/mod.rs +++ b/crates/cxx-qt-gen/src/generator/mod.rs @@ -3,13 +3,61 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use cxx_gen::{CfgEvaluator, CfgResult}; #[cfg(test)] use syn::{parse_quote, ItemMod}; + +pub mod cfg; pub mod cpp; pub mod naming; pub mod rust; pub mod structuring; +/// Options for C++ code generation. +#[non_exhaustive] +pub struct GeneratedOpt { + /// Impl for handling conditional compilation attributes. + pub cfg_evaluator: Box, +} + +impl Default for GeneratedOpt { + fn default() -> Self { + Self { + cfg_evaluator: Box::new(UnsupportedCfgEvaluator), + } + } +} + +pub(super) struct UnsupportedCfgEvaluator; + +impl CfgEvaluator for UnsupportedCfgEvaluator { + fn eval(&self, name: &str, value: Option<&str>) -> CfgResult { + let _ = name; + let _ = value; + let msg = "cfg attribute is not supported".to_owned(); + CfgResult::Undetermined { msg } + } +} + +#[cfg(test)] +pub(super) struct TestCfgEvaluator { + // CfgResult cannot be cloned so emulate with Option + pub result: Option, +} + +#[cfg(test)] +impl CfgEvaluator for TestCfgEvaluator { + fn eval(&self, _name: &str, _value: Option<&str>) -> CfgResult { + match self.result { + Some(true) => CfgResult::True, + Some(false) => CfgResult::False, + None => CfgResult::Undetermined { + msg: "Undetermined".to_owned(), + }, + } + } +} + #[cfg(test)] /// Mocks a module containing a singleton type pub fn mock_qml_singleton() -> ItemMod { @@ -25,3 +73,30 @@ pub fn mock_qml_singleton() -> ItemMod { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cfg_unsupported() { + let evaluator = UnsupportedCfgEvaluator {}; + let result = evaluator.eval("test", Some("test")); + assert!(matches!(result, CfgResult::Undetermined { .. })); + } + + #[test] + fn test_cfg_test() { + let mut evaluator = TestCfgEvaluator { result: None }; + let result_none = evaluator.eval("test", Some("test")); + assert!(matches!(result_none, CfgResult::Undetermined { .. })); + + evaluator.result = Some(true); + let result_true = evaluator.eval("test", Some("test")); + assert!(matches!(result_true, CfgResult::True)); + + evaluator.result = Some(false); + let result_false = evaluator.eval("test", Some("test")); + assert!(matches!(result_false, CfgResult::False)); + } +} diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 1844a1bcc..814ba0ed0 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -136,10 +136,12 @@ pub fn generate_rust_signal( cxx_bridge.push(quote! { unsafe extern "C++" { + #(#cfgs)* #[doc(hidden)] #[namespace = #namespace_str] type #signal_handler_alias = cxx_qt::signalhandler::CxxQtSignalHandler; + #(#cfgs)* #[doc(hidden)] #[namespace = #namespace_str] #[cxx_name = #free_connect_ident_cpp] @@ -150,9 +152,11 @@ pub fn generate_rust_signal( cxx_bridge.push(quote! { #[namespace = #namespace_str] extern "Rust" { + #(#cfgs)* #[doc(hidden)] fn #signal_handler_drop(handler: #signal_handler_alias); + #(#cfgs)* #[doc(hidden)] #unsafe_call fn #signal_handler_call(handler: &mut #signal_handler_alias, self_value: #self_type_cxx, #(#parameters_cxx),*); } @@ -162,6 +166,7 @@ pub fn generate_rust_signal( cxx_bridge, implementation: vec![ quote! { + #(#cfgs)* impl #qualified_impl { #[doc = "Connect the given function pointer to the signal "] #[doc = #signal_name_cpp] @@ -177,6 +182,7 @@ pub fn generate_rust_signal( } }, quote! { + #(#cfgs)* impl #qualified_impl { #[doc = "Connect the given function pointer to the signal "] #[doc = #signal_name_cpp] @@ -194,19 +200,23 @@ pub fn generate_rust_signal( } }, quote! { + #(#cfgs)* #[doc(hidden)] pub struct #closure_struct {} }, quote! { + #(#cfgs)* impl cxx_qt::signalhandler::CxxQtSignalHandlerClosure for #closure_struct { type Id = cxx::type_id!(#signal_handler_alias_namespaced_str); type FnType = dyn FnMut(#self_type_qualified, #(#parameters_qualified_type),*) + Send; } }, quote! { + #(#cfgs)* use core::mem::drop as #signal_handler_drop; }, quote! { + #(#cfgs)* fn #signal_handler_call( handler: &mut cxx_qt::signalhandler::CxxQtSignalHandler<#closure_struct>, self_value: #self_type_qualified, @@ -216,9 +226,11 @@ pub fn generate_rust_signal( } }, quote! { + #(#cfgs)* cxx_qt::static_assertions::assert_eq_align!(cxx_qt::signalhandler::CxxQtSignalHandler<#closure_struct>, usize); }, quote! { + #(#cfgs)* cxx_qt::static_assertions::assert_eq_size!(cxx_qt::signalhandler::CxxQtSignalHandler<#closure_struct>, [usize; 2]); }, ], diff --git a/crates/cxx-qt-gen/src/lib.rs b/crates/cxx-qt-gen/src/lib.rs index 1a282884d..4c9cbc8cc 100644 --- a/crates/cxx-qt-gen/src/lib.rs +++ b/crates/cxx-qt-gen/src/lib.rs @@ -17,6 +17,7 @@ mod writer; pub use generator::{ cpp::{fragment::CppFragment, GeneratedCppBlocks}, rust::GeneratedRustBlocks, + GeneratedOpt, }; pub use parser::Parser; pub use syntax::{parse_qt_file, CxxQtFile, CxxQtItem}; @@ -158,7 +159,8 @@ mod tests { ) { let parser = Parser::from(syn::parse_str(input).unwrap()).unwrap(); - let generated_cpp = GeneratedCppBlocks::from(&parser).unwrap(); + let opt = GeneratedOpt::default(); + let generated_cpp = GeneratedCppBlocks::from(&parser, &opt).unwrap(); let (mut header, mut source) = require_pair(&write_cpp(&generated_cpp, "directory/file_ident")).unwrap(); header = sanitize_code(header); diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index a200b966b..f7cdfff04 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -26,7 +26,8 @@ pub struct ParsedSignal { } impl ParsedSignal { - const ALLOWED_ATTRS: [&'static str; 5] = ["cxx_name", "rust_name", "inherit", "doc", "qsignal"]; + const ALLOWED_ATTRS: [&'static str; 6] = + ["cfg", "cxx_name", "rust_name", "inherit", "doc", "qsignal"]; #[cfg(test)] /// Test fn for creating a mocked signal from a method body diff --git a/crates/cxx-qt-gen/src/syntax/cfg.rs b/crates/cxx-qt-gen/src/syntax/cfg.rs new file mode 100644 index 000000000..d15f992e0 --- /dev/null +++ b/crates/cxx-qt-gen/src/syntax/cfg.rs @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: CXX Authors +// SPDX-FileContributor: David Tolnay +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Originally found in the CXX repository +// https://github.com/dtolnay/cxx/blob/e26474acf4284235895d526c5ed12575cd9c0cce/syntax/cfg.rs + +use proc_macro2::Ident; +use std::mem; +use syn::parse::{Error, ParseStream, Result}; +use syn::{parenthesized, token, Attribute, LitStr, Token}; + +#[derive(Clone)] +pub(crate) enum CfgExpr { + Unconditional, + Eq(Ident, Option), + All(Vec), + Any(Vec), + Not(Box), +} + +impl CfgExpr { + pub(crate) fn merge(&mut self, expr: CfgExpr) { + if let CfgExpr::Unconditional = self { + *self = expr; + } else if let CfgExpr::All(list) = self { + list.push(expr); + } else { + let prev = mem::replace(self, CfgExpr::Unconditional); + *self = CfgExpr::All(vec![prev, expr]); + } + } +} + +pub(crate) fn parse_attribute(attr: &Attribute) -> Result { + attr.parse_args_with(|input: ParseStream| { + let cfg_expr = input.call(parse_single)?; + input.parse::>()?; + Ok(cfg_expr) + }) +} + +fn parse_single(input: ParseStream) -> Result { + let ident: Ident = input.parse()?; + let lookahead = input.lookahead1(); + if input.peek(token::Paren) { + let content; + parenthesized!(content in input); + if ident == "all" { + let list = content.call(parse_multiple)?; + Ok(CfgExpr::All(list)) + } else if ident == "any" { + let list = content.call(parse_multiple)?; + Ok(CfgExpr::Any(list)) + } else if ident == "not" { + let expr = content.call(parse_single)?; + content.parse::>()?; + Ok(CfgExpr::Not(Box::new(expr))) + } else { + Err(Error::new(ident.span(), "unrecognized cfg expression")) + } + } else if lookahead.peek(Token![=]) { + input.parse::()?; + let string: LitStr = input.parse()?; + Ok(CfgExpr::Eq(ident, Some(string))) + } else if lookahead.peek(Token![,]) || input.is_empty() { + Ok(CfgExpr::Eq(ident, None)) + } else { + // CODECOV_EXCLUDE_START + Err(lookahead.error()) + // CODECOV_EXCLUDE_STOP + } +} + +fn parse_multiple(input: ParseStream) -> Result> { + let mut vec = Vec::new(); + while !input.is_empty() { + let expr = input.call(parse_single)?; + vec.push(expr); + if input.is_empty() { + break; + } + input.parse::()?; + } + Ok(vec) +} + +#[cfg(test)] +mod tests { + use super::*; + + use syn::{parse_quote, ItemMod}; + + #[test] + fn test_merge() { + let mut cfg = CfgExpr::Unconditional; + + let module: ItemMod = parse_quote! { + #[cfg(all(a, b))] + #[cfg(not(a))] + mod test; + }; + let cfg_all = parse_attribute(&module.attrs[0]).unwrap(); + assert!(matches!(cfg_all, CfgExpr::All(ref items) if items.len() == 2)); + assert!(matches!(cfg, CfgExpr::Unconditional)); + + // Merge all into unconditional + cfg.merge(cfg_all.clone()); + assert!(matches!(cfg, CfgExpr::All(ref items) if items.len() == 2)); + + // Merge all with all + cfg.merge(cfg_all.clone()); + assert!(matches!(cfg, CfgExpr::All(ref items) if items.len() == 3)); + + // Merge not with other + let mut cfg_not = parse_attribute(&module.attrs[1]).unwrap(); + assert!(matches!(cfg_not, CfgExpr::Not(..))); + cfg_not.merge(cfg_all); + assert!(matches!(cfg_not, CfgExpr::All(items) if items.len() == 2)); + } + + #[test] + fn test_parse_attribute() { + let module: ItemMod = parse_quote! { + #[cfg(a = "b")] + #[cfg(a)] + mod test; + }; + let cfg_eq = parse_attribute(&module.attrs[0]).unwrap(); + assert!(matches!(cfg_eq, CfgExpr::Eq(.., Some(..)))); + + let cfg_single = parse_attribute(&module.attrs[1]).unwrap(); + assert!(matches!(cfg_single, CfgExpr::Eq(.., None))); + } + + #[test] + fn test_parse_attribute_parenthesis() { + let module: ItemMod = parse_quote! { + #[cfg(all(a, b))] + #[cfg(any(a, b))] + #[cfg(not(a))] + #[cfg(unknown(a))] + mod test; + }; + + let cfg_all = parse_attribute(&module.attrs[0]).unwrap(); + assert!(matches!(cfg_all, CfgExpr::All(items) if items.len() == 2)); + + let cfg_any = parse_attribute(&module.attrs[1]).unwrap(); + assert!(matches!(cfg_any, CfgExpr::Any(items) if items.len() == 2)); + + let cfg_not = parse_attribute(&module.attrs[2]).unwrap(); + assert!(matches!(cfg_not, CfgExpr::Not(..))); + + let cfg_unknown = parse_attribute(&module.attrs[3]); + assert!(cfg_unknown.is_err()); + } +} diff --git a/crates/cxx-qt-gen/src/syntax/mod.rs b/crates/cxx-qt-gen/src/syntax/mod.rs index 9677ae0ca..e46f661a4 100644 --- a/crates/cxx-qt-gen/src/syntax/mod.rs +++ b/crates/cxx-qt-gen/src/syntax/mod.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pub mod attribute; +pub mod cfg; pub mod expr; pub mod foreignmod; pub mod lifetimes; diff --git a/crates/cxx-qt-gen/src/writer/cpp/header.rs b/crates/cxx-qt-gen/src/writer/cpp/header.rs index 9972233bc..d05559864 100644 --- a/crates/cxx-qt-gen/src/writer/cpp/header.rs +++ b/crates/cxx-qt-gen/src/writer/cpp/header.rs @@ -175,6 +175,7 @@ pub fn write_cpp_header(generated: &GeneratedCppBlocks, include_path: &str) -> S mod tests { use super::*; + use crate::generator::GeneratedOpt; use crate::tests::format_cpp; use crate::writer::cpp::tests::{ create_generated_cpp, create_generated_cpp_multi_qobjects, @@ -250,7 +251,7 @@ mod tests { let parser = Parser::from(module.clone()).unwrap(); - let generated = GeneratedCppBlocks::from(&parser).unwrap(); + let generated = GeneratedCppBlocks::from(&parser, &GeneratedOpt::default()).unwrap(); let header = write_cpp_header(&generated, "cxx-qt-gen/ffi"); let expected = indoc! {r#" #pragma once diff --git a/scripts/grcov_cxx_qt.sh b/scripts/grcov_cxx_qt.sh index 1103d872b..94ffcd0e6 100755 --- a/scripts/grcov_cxx_qt.sh +++ b/scripts/grcov_cxx_qt.sh @@ -5,19 +5,31 @@ # # SPDX-License-Identifier: MIT OR Apache-2.0 -# Assumes you have grcov and llvm-tools +# Assumes you have grcov and llvm in a system path # Install: # cargo install grcov -# rustup component add llvm-tools set -ex + # Ensure we are in the right directory SCRIPT=$(realpath "$0") SCRIPTPATH=$(dirname "$SCRIPT") cd "$SCRIPTPATH/../" +# Ensure coverage folder is cleared +rm -f "$SCRIPTPATH"/coverage/*.profraw + +# Check that the llvm path exists +# +# We can use rustup component add llvm-tools but this can be out of sync +# See versions from the table in this link +# https://github.com/taiki-e/cargo-llvm-cov?tab=readme-ov-file#get-coverage-of-cc-code-linked-to-rust-librarybinary +if [ ! -d /usr/lib/llvm-17/bin/ ]; then + echo "LLVM 17 not found" +fi + export RUSTFLAGS="-Cinstrument-coverage" export LLVM_PROFILE_FILE="$SCRIPTPATH/coverage/coverage_data-%p-%m.profraw" cargo build --package cxx-qt-gen cargo test --package cxx-qt-gen -grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/ --excl-start CODECOV_EXCLUDE_START --excl-stop CODECOV_EXCLUDE_STOP -echo "Coverage html report generated in $(realpath "$SCRIPTPATH"/../target/debug/html)" \ No newline at end of file +grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --llvm --llvm-path=/usr/lib/llvm-17/bin/ -o ./target/debug/ --excl-start CODECOV_EXCLUDE_START --excl-stop CODECOV_EXCLUDE_STOP +echo "Coverage html report generated in $(realpath "$SCRIPTPATH"/../target/debug/html)"