Skip to content

Commit 90039d7

Browse files
authored
cxx-qt-gen: add support for passing in CfgEvaluator (#1168)
This adds support for a basic subset of `#[cfg()]` directives into the CXX-Qt bridges. It also fixes some issues with Code Coverage that came up during development of this PR. This commit squashes multiple commits of PR #1168: * cxx-qt-gen: add support for passing in CfgEvaluator * codecov: ensure we use host llvm version and clear coverage data * codecov: disable incremental builds as these files slow scanning And may confuse the results. * codecov: keep only crates/ and do not include crates registry This avoids collisions between paths, we were seeing odd issues around some files having a different tracked amount in codecov vs lcov. It appears to be due to having coverage results from cxx_gen with the same file paths. We do not want coverage data for anything else anyway so filter them out. * codecov: add extra notes around important args * cxx-qt-gen: add snapshot test for cfg evaluator * cxx-qt-gen: add support for extern C++Qt QObject's to cfg * cxx-qt-gen: simplify cfg evaluator tests by having shared assert * cxx-qt-gen: ensure that we return for non cfg attributes * cxx-qt-gen: add support for cfg on a qobject in RustQt * cxx-qt-gen: add more variants for snapshot test of cfgs * cxx-qt-gen: add assert between every assert
1 parent c8409c7 commit 90039d7

36 files changed

+2491
-105
lines changed

.github/workflows/github-cxx-qt-tests.yml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,31 +71,54 @@ jobs:
7171
steps:
7272
- name: Checkout repository
7373
uses: actions/checkout@v4
74+
# Note we need to match the LLVM and Rust versions
75+
#
76+
# See versions from the table in this link
77+
# https://github.com/taiki-e/cargo-llvm-cov?tab=readme-ov-file#get-coverage-of-cc-code-linked-to-rust-librarybinary
78+
- name: Install llvm 17
79+
run: |
80+
sudo apt-get update && sudo apt-get install -y llvm-17
81+
test -d /usr/lib/llvm-17/bin/
7482
- name: Setup toolchain
7583
run: |
84+
# Note that the llvm version needs to match, see the link above
7685
rustup default 1.77.2
7786
cargo install grcov
78-
rustup component add llvm-tools rustfmt
87+
rustup component add rustfmt
88+
# Ensure we do not have any existing coverage files
89+
- run: rm -f coverage/*.profraw
7990
- name: build
8091
env:
92+
CARGO_INCREMENTAL: 0
8193
RUSTFLAGS: -Cinstrument-coverage
8294
LLVM_PROFILE_FILE: coverage/coverage_data-%p-%m.profraw
8395
run: cargo build --package cxx-qt-gen
8496
- name: test
8597
env:
98+
CARGO_INCREMENTAL: 0
8699
RUSTFLAGS: -Cinstrument-coverage
87100
LLVM_PROFILE_FILE: coverage/coverage_data-%p-%m.profraw
88101
run: cargo test --lib --package cxx-qt-gen
89102
- name: generate-report
90-
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
103+
# Note that --llvm-path is important here to ensure the matching llvm version to the Rust version (1.77.x)
104+
# Note that --keep-only is important here to ensure crates.io paths don't conflict
105+
run: grcov . -s . --binary-path ./target/debug/ -t lcov --branch --ignore-not-existing --llvm --llvm-path /usr/lib/llvm-17/bin/ --keep-only "crates/*" -o ./target/debug/lcov.info --excl-start CODECOV_EXCLUDE_START --excl-stop CODECOV_EXCLUDE_STOP
91106
- name: upload-report
92107
uses: codecov/codecov-action@v5
93108
with:
94109
directory: ./target/debug/
110+
disable-search: true
95111
files: lcov.info
96112
fail_ci_if_error: true
97113
token: ${{ secrets.CODECOV_TOKEN }}
98114
verbose: true
115+
- name: Upload GitHub Actions artifacts of lcov
116+
if: always()
117+
uses: actions/upload-artifact@v4
118+
with:
119+
name: lcov
120+
path: ./target/debug/lcov.info
121+
if-no-files-found: ignore
99122

100123
build-wasm:
101124
name: Ubuntu 24.04 (wasm_32) Qt 6

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Support for further types: `QUuid`
2424
- New example: Basic greeter app
2525
- Support for further types: `qreal`, `qint64`, `qintptr`, `qsizetype`, `quint64`, `quintptr`
26+
- Support for `cfg` attributes through to C++ generation
2627

2728
### Fixed
2829

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use std::{
4343
};
4444

4545
use cxx_qt_gen::{
46-
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks,
46+
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks, GeneratedOpt,
4747
GeneratedRustBlocks, Parser,
4848
};
4949

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

106+
let mut cxx_qt_opt = GeneratedOpt::default();
107+
cxx_qt_opt.cfg_evaluator = Box::new(cfg_evaluator::CargoEnvCfgEvaluator);
108+
106109
// Loop through the items looking for any CXX or CXX-Qt blocks
107110
let mut found_bridge = false;
108111
for item in &file.items {
@@ -132,7 +135,7 @@ impl GeneratedCpp {
132135
let parser = Parser::from(m.clone())
133136
.map_err(GeneratedError::from)
134137
.map_err(to_diagnostic)?;
135-
let generated_cpp = GeneratedCppBlocks::from(&parser)
138+
let generated_cpp = GeneratedCppBlocks::from(&parser, &cxx_qt_opt)
136139
.map_err(GeneratedError::from)
137140
.map_err(to_diagnostic)?;
138141
let generated_rust = GeneratedRustBlocks::from(&parser)

crates/cxx-qt-gen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ exclude = ["update_expected.sh"]
2020
rust-version = "1.64.0"
2121

2222
[dependencies]
23+
cxx-gen.workspace = true
2324
proc-macro2.workspace = true
2425
syn.workspace = true
2526
quote.workspace = true
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// SPDX-FileCopyrightText: CXX Authors
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
// SPDX-FileContributor: David Tolnay <[email protected]>
4+
//
5+
// SPDX-License-Identifier: MIT OR Apache-2.0
6+
7+
use crate::syntax::cfg::{parse_attribute, CfgExpr};
8+
use cxx_gen::{CfgEvaluator, CfgResult};
9+
use quote::quote;
10+
use syn::{Attribute, Error, LitStr};
11+
12+
pub(crate) fn try_eval_attributes(
13+
cfg_evaluator: &dyn CfgEvaluator,
14+
attrs: &[Attribute],
15+
) -> Result<bool, Error> {
16+
// Build a single CfgExpr from the Attributes
17+
let cfg_expr = attrs
18+
.iter()
19+
.map(parse_attribute)
20+
.collect::<Result<Vec<CfgExpr>, Error>>()?
21+
.into_iter()
22+
.reduce(|mut acc, e| {
23+
acc.merge(e);
24+
acc
25+
});
26+
27+
// Evaluate the CfgExpr against the CfgEvaluator
28+
if let Some(cfg_expr) = cfg_expr {
29+
try_eval(cfg_evaluator, &cfg_expr).map_err(|errs| {
30+
errs.into_iter()
31+
.reduce(|mut acc, e| {
32+
acc.combine(e);
33+
acc
34+
})
35+
.expect("There should be at least one error")
36+
})
37+
} else {
38+
Ok(true)
39+
}
40+
}
41+
42+
fn try_eval(cfg_evaluator: &dyn CfgEvaluator, expr: &CfgExpr) -> Result<bool, Vec<Error>> {
43+
match expr {
44+
CfgExpr::Unconditional => Ok(true),
45+
CfgExpr::Eq(ident, string) => {
46+
let key = ident.to_string();
47+
let value = string.as_ref().map(LitStr::value);
48+
match cfg_evaluator.eval(&key, value.as_deref()) {
49+
CfgResult::True => Ok(true),
50+
CfgResult::False => Ok(false),
51+
CfgResult::Undetermined { msg } => {
52+
let span = quote!(#ident #string);
53+
Err(vec![Error::new_spanned(span, msg)])
54+
}
55+
}
56+
}
57+
CfgExpr::All(list) => {
58+
let mut all_errors = Vec::new();
59+
for subexpr in list {
60+
match try_eval(cfg_evaluator, subexpr) {
61+
Ok(true) => {}
62+
Ok(false) => return Ok(false),
63+
Err(errors) => all_errors.extend(errors),
64+
}
65+
}
66+
if all_errors.is_empty() {
67+
Ok(true)
68+
} else {
69+
Err(all_errors)
70+
}
71+
}
72+
CfgExpr::Any(list) => {
73+
let mut all_errors = Vec::new();
74+
for subexpr in list {
75+
match try_eval(cfg_evaluator, subexpr) {
76+
Ok(true) => return Ok(true),
77+
Ok(false) => {}
78+
Err(errors) => all_errors.extend(errors),
79+
}
80+
}
81+
if all_errors.is_empty() {
82+
Ok(false)
83+
} else {
84+
Err(all_errors)
85+
}
86+
}
87+
CfgExpr::Not(subexpr) => match try_eval(cfg_evaluator, subexpr) {
88+
Ok(value) => Ok(!value),
89+
Err(errors) => Err(errors),
90+
},
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use super::*;
97+
98+
use crate::{generator::UnsupportedCfgEvaluator, tests::CfgEvaluatorTest};
99+
use syn::{parse_quote, ItemMod};
100+
101+
fn assert_eval_insert(module: ItemMod, cfgs: &[&str], [before, after]: [bool; 2]) {
102+
let mut cfg_evaluator = Box::new(CfgEvaluatorTest::default());
103+
assert_eq!(
104+
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
105+
before
106+
);
107+
108+
for chunk in cfgs.chunks(2) {
109+
assert_eq!(
110+
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
111+
before
112+
);
113+
114+
if let [key, value] = chunk {
115+
cfg_evaluator.cfgs.insert(key, Some(value));
116+
}
117+
}
118+
119+
assert_eq!(
120+
try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs).unwrap(),
121+
after
122+
);
123+
}
124+
125+
fn assert_eval_insert_false_true(module: ItemMod, cfgs: &[&str]) {
126+
assert_eval_insert(module, cfgs, [false, true]);
127+
}
128+
129+
#[test]
130+
fn test_try_eval_attributes_eq() {
131+
assert_eval_insert_false_true(
132+
parse_quote! {
133+
#[cfg(a = "1")]
134+
#[cfg(b = "2")]
135+
mod module;
136+
},
137+
&["c", "3", "a", "1", "b", "2"],
138+
);
139+
}
140+
141+
#[test]
142+
fn test_try_eval_attributes_any() {
143+
assert_eval_insert_false_true(
144+
parse_quote! {
145+
#[cfg(any(a = "1", b = "2"))]
146+
mod module;
147+
},
148+
&["c", "3", "a", "1"],
149+
);
150+
}
151+
152+
#[test]
153+
fn test_try_eval_attributes_all() {
154+
assert_eval_insert_false_true(
155+
parse_quote! {
156+
#[cfg(all(a = "1", b = "2"))]
157+
mod module;
158+
},
159+
&["c", "3", "a", "1", "b", "2"],
160+
);
161+
}
162+
163+
#[test]
164+
fn test_try_eval_attributes_not() {
165+
assert_eval_insert(
166+
parse_quote! {
167+
#[cfg(not(a = "1"))]
168+
mod module;
169+
},
170+
&["c", "3", "a", "1"],
171+
[true, false],
172+
);
173+
}
174+
175+
#[test]
176+
fn test_try_eval_unconditional() {
177+
let cfg_expr = CfgExpr::Unconditional;
178+
let cfg_evaluator = Box::new(UnsupportedCfgEvaluator);
179+
assert_eq!(try_eval(cfg_evaluator.as_ref(), &cfg_expr).unwrap(), true);
180+
}
181+
182+
#[test]
183+
fn test_try_eval_attributes_undetermined_err() {
184+
let module: ItemMod = parse_quote! {
185+
#[cfg(a = "1")]
186+
#[cfg(all(a = "1", b = "2"))]
187+
#[cfg(any(a = "1", b = "2"))]
188+
#[cfg(not(a = "1"))]
189+
mod module;
190+
};
191+
let cfg_evaluator = Box::new(UnsupportedCfgEvaluator);
192+
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[0..1]).is_err());
193+
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[1..2]).is_err());
194+
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[2..3]).is_err());
195+
assert!(try_eval_attributes(cfg_evaluator.as_ref(), &module.attrs[3..4]).is_err());
196+
}
197+
}

crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

66
use crate::{
7-
generator::cpp::signal::generate_cpp_signal, naming::TypeNames,
8-
parser::externcxxqt::ParsedExternCxxQt, CppFragment,
7+
generator::{cpp::signal::generate_cpp_signal, GeneratedOpt},
8+
naming::TypeNames,
9+
parser::externcxxqt::ParsedExternCxxQt,
10+
CppFragment,
911
};
1012
use std::collections::BTreeSet;
1113
use syn::Result;
@@ -23,18 +25,20 @@ pub struct GeneratedCppExternCxxQtBlocks {
2325
pub fn generate(
2426
blocks: &[ParsedExternCxxQt],
2527
type_names: &TypeNames,
28+
opt: &GeneratedOpt,
2629
) -> Result<Vec<GeneratedCppExternCxxQtBlocks>> {
2730
let mut out = vec![];
2831

2932
for block in blocks {
3033
for signal in &block.signals {
31-
let mut block = GeneratedCppExternCxxQtBlocks::default();
3234
let qobject_name = type_names.lookup(&signal.qobject_ident)?;
33-
let data = generate_cpp_signal(signal, qobject_name, type_names)?;
34-
block.includes = data.includes;
35-
block.forward_declares = data.forward_declares;
36-
block.fragments = data.fragments;
35+
let data = generate_cpp_signal(signal, qobject_name, type_names, opt)?;
3736
debug_assert!(data.methods.is_empty());
37+
let block = GeneratedCppExternCxxQtBlocks {
38+
includes: data.includes,
39+
forward_declares: data.forward_declares,
40+
fragments: data.fragments,
41+
};
3842
out.push(block);
3943
}
4044
}
@@ -70,9 +74,10 @@ mod tests {
7074
.unwrap()];
7175

7276
// Unknown types
73-
assert!(generate(&blocks, &TypeNames::default()).is_err());
77+
let opt = GeneratedOpt::default();
78+
assert!(generate(&blocks, &TypeNames::default(), &opt).is_err());
7479

75-
let generated = generate(&blocks, &TypeNames::mock()).unwrap();
80+
let generated = generate(&blocks, &TypeNames::mock(), &opt).unwrap();
7681
assert_eq!(generated.len(), 2);
7782
}
7883

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

100-
let generated = generate(&blocks, &type_names).unwrap();
105+
let generated = generate(&blocks, &type_names, &GeneratedOpt::default()).unwrap();
101106
assert_eq!(generated.len(), 1);
102107
}
103108
}

0 commit comments

Comments
 (0)