Skip to content
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
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

* Emit `&str`, `&CStr`, and arrays thereof as C string literals, and byte-string
(`b"..."`) constants as `uint8_t[]` arrays, instead of dropping them.
* Respect `#[cfg]` on struct fields in `#define` constants by emitting a
helper macro per condition (`#if` cannot appear inside a `#define`).

# 0.29.4

Expand Down
2 changes: 2 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ As cbindgen spiders through your crate, it will make note of all the cfgs it fou

However cbindgen has no way of knowing how you want to map those cfgs to defines. You will need to use the `[defines]` section in your cbindgen.toml to specify all the different mappings. It natively understands concepts like any() and all(), so you only need to tell it how you want to translate base concepts like `target_os = "freebsd"` or `feature = "serde"`.

When a struct-literal constant is emitted as a `#define`, `#[cfg]`-gated fields cannot be wrapped in `#if` because preprocessor directives are not allowed inside a macro replacement list. Instead, cbindgen emits one helper macro per condition (`__CBINDGEN_CFG_<condition>(...)`, which expands to its arguments when the condition holds and to nothing otherwise) and wraps each gated field initializer, including its trailing comma, in it. These helpers stay defined because the constants expand to them at their use sites.

Note that because cbindgen just parses the source of your crate, you mostly don't need to worry about what crate features or what platform you're targetting. Every possible configuration should be visible to the parser. Our primitive mappings should also be completely platform agnostic (i32 is int32_t regardless of your target).

While modules within a crate form a tree with uniquely defined paths to each item, and therefore uniquely defined cfgs for those items, dependencies do not. If you depend on a crate in multiple ways, and those ways produce different cfgs, one of them will be arbitrarily chosen for any types found in that crate.
Expand Down
88 changes: 87 additions & 1 deletion src/bindgen/ir/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ impl ToCondition for Cfg {
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Condition {
Define(String),
Any(Vec<Condition>),
Expand All @@ -266,6 +266,63 @@ pub enum Condition {
}

impl Condition {
/// Name of the preprocessor helper macro that expands to its arguments iff this
/// condition holds. Used so `#define` constants can mention cfg-gated
/// struct fields (`#if` is not legal inside a `#define`).
pub fn match_macro_name(&self) -> String {
let mut name = String::from("__CBINDGEN_CFG_");
self.append_macro_ident(&mut name);
name
}

fn append_macro_ident(&self, out: &mut String) {
match self {
Condition::Define(define) => {
out.push('D');
out.push_str(&define.len().to_string());
out.push('_');
Comment on lines +281 to +283

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little bit of mangling to avoid accidental name collision. please tell me a better approach if this is not the best

out.push_str(define);
}
Condition::Not(inner) => {
out.push_str("N_");
inner.append_macro_ident(out);
}
Condition::All(conditions) => {
out.push('A');
out.push_str(&conditions.len().to_string());
for condition in conditions {
out.push('_');
condition.append_macro_ident(out);
}
}
Condition::Any(conditions) => {
out.push('O');
out.push_str(&conditions.len().to_string());
for condition in conditions {
out.push('_');
condition.append_macro_ident(out);
}
}
}
}

/// `#define NAME(...) __VA_ARGS__` when `self` holds, empty otherwise.
pub fn write_match_macro<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
let name = self.match_macro_name();
out.push_set_spaces(0);
out.write("#if ");
self.write(config, out);
out.pop_set_spaces();
out.new_line();
write!(out, "#define {name}(...) __VA_ARGS__");
out.new_line();
out.write("#else");
out.new_line();
write!(out, "#define {name}(...)");
out.new_line();
out.write("#endif");
}

fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
match *self {
Condition::Define(ref define) => {
Expand Down Expand Up @@ -352,3 +409,32 @@ impl ConditionWrite for Option<Condition> {
}
}
}

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

#[test]
fn match_macro_names_encode_condition_structure() {
let first = Condition::All(vec![
Condition::Define("A".to_owned()),
Condition::Define("B_C".to_owned()),
]);
let second = Condition::All(vec![
Condition::Define("A_B".to_owned()),
Condition::Define("C".to_owned()),
]);

assert_eq!(first.match_macro_name(), "__CBINDGEN_CFG_A2_D1_A_D3_B_C");
assert_eq!(second.match_macro_name(), "__CBINDGEN_CFG_A2_D3_A_B_D1_C");
assert_ne!(first.match_macro_name(), second.match_macro_name());
}

#[test]
fn match_macro_names_distinguish_nodes_from_define_text() {
let define = Condition::Define("N_D1_A".to_owned());
let negated = Condition::Not(Box::new(Condition::Define("A".to_owned())));

assert_ne!(define.match_macro_name(), negated.match_macro_name());
}
}
4 changes: 2 additions & 2 deletions src/bindgen/ir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ impl Literal {
!self.has_pointer_casts()
}

fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool {
pub(crate) fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool {
if !visitor(self) {
return false;
}
Expand Down Expand Up @@ -939,7 +939,7 @@ impl Constant {
}
Language::Cxx | Language::C => {
write!(out, "#define {name} ");
language_backend.write_literal(out, value);
language_backend.write_macro_literal(out, value);
}
Language::Cython => {
if !write_field_prepends_const(&self.ty) {
Expand Down
105 changes: 98 additions & 7 deletions src/bindgen/language_backend/clike.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
use crate::bindgen::ir::{
to_known_assoc_constant, ConditionWrite, DeprecatedNoteKind, Documentation, Enum, EnumVariant,
Field, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type,
Typedef, Union,
to_known_assoc_constant, Condition, ConditionWrite, DeprecatedNoteKind, Documentation, Enum,
EnumVariant, Field, GenericParams, Item, ItemContainer, Literal, OpaqueItem, ReprAlign, Static,
Struct, ToCondition, Type, Typedef, Union,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::rename::IdentifierType;
use crate::bindgen::writer::{ListType, SourceWriter};
use crate::bindgen::{cdecl, Bindings, Config, Language};
use crate::bindgen::{DocumentationLength, DocumentationStyle};
use std::collections::BTreeSet;
use std::io::Write;

pub struct CLikeLanguageBackend<'a> {
config: &'a Config,
writing_macro: bool,
}

impl<'a> CLikeLanguageBackend<'a> {
pub fn new(config: &'a Config) -> Self {
Self { config }
Self {
config,
writing_macro: false,
}
}

fn write_enum_variant<W: Write>(&mut self, out: &mut SourceWriter<W>, u: &EnumVariant) {
Expand Down Expand Up @@ -445,6 +450,22 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
}
}

fn write_cfg_macros<W: Write>(&mut self, out: &mut SourceWriter<W>, b: &Bindings) {
let conditions = collect_cfg_field_conditions(b, self.config);
if conditions.is_empty() {
return;
}

out.new_line_if_not_start();
for (i, condition) in conditions.iter().enumerate() {
if i > 0 {
out.new_line();
}
condition.write_match_macro(self.config, out);
}
out.new_line();
}

fn open_namespaces<W: Write>(&mut self, out: &mut SourceWriter<W>) {
self.open_close_namespaces(out, true);
}
Expand Down Expand Up @@ -905,7 +926,8 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
path,
} => {
let allow_constexpr = self.config.constant.allow_constexpr && l.can_be_constexpr();
let is_constexpr = self.config.language == Language::Cxx
let is_constexpr = !self.writing_macro
&& self.config.language == Language::Cxx
&& (self.config.constant.allow_static_const || allow_constexpr);
if self.config.language == Language::C {
write!(out, "({export_name})");
Expand All @@ -921,9 +943,11 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
}
// In C++, same order as defined is required.
let ordered_fields = out.bindings().struct_field_names(path);
let mut separator = "";
for (i, ordered_key) in ordered_fields.iter().enumerate() {
if let Some(lit) = fields.get(ordered_key) {
let condition = lit.cfg.to_condition(self.config);
let use_match_macro = condition.is_some() && !is_constexpr;
if is_constexpr {
out.new_line();

Expand All @@ -937,8 +961,9 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
}
condition.write_after(self.config, out);
} else {
if i > 0 {
write!(out, ", ");
out.write(separator);
if use_match_macro {
write!(out, "{}(", condition.as_ref().unwrap().match_macro_name());
}

if self.config.language == Language::Cxx {
Expand All @@ -949,6 +974,14 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
write!(out, ".{ordered_key} = ");
}
self.write_literal(out, &lit.value);
// The trailing comma lives inside the macro so the
// initializer stays valid when the field is omitted.
separator = if use_match_macro {
write!(out, ",)");
" "
} else {
", "
};
}
}
}
Expand All @@ -963,6 +996,13 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
}
}

fn write_macro_literal<W: Write>(&mut self, out: &mut SourceWriter<W>, l: &Literal) {
let was_writing_macro = self.writing_macro;
self.writing_macro = true;
self.write_literal(out, l);
self.writing_macro = was_writing_macro;
}

fn write_globals<W: Write>(&mut self, out: &mut SourceWriter<W>, b: &Bindings) {
// Override default method to open various blocs containing both globals and functions
// these blocks are closed in [`write_functions`] that is also overridden
Expand Down Expand Up @@ -1021,3 +1061,54 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
}
}
}

/// Collect structurally unique field-level conditions needed by macro output.
fn collect_cfg_field_conditions(b: &Bindings, config: &Config) -> Vec<Condition> {
let mut conditions = BTreeSet::new();

for item in &b.items {
if let ItemContainer::Struct(s) = item {
for constant in &s.associated_constants {
if constant_uses_macro(&constant.value, config) {
add_literal_cfg_conditions(&mut conditions, &constant.value, config);
}
}
}
}

for constant in &b.constants {
if constant_uses_macro(&constant.value, config) {
add_literal_cfg_conditions(&mut conditions, &constant.value, config);
}
}

conditions.into_iter().collect()
}

fn constant_uses_macro(lit: &Literal, config: &Config) -> bool {
match config.language {
Language::C => true,
Language::Cxx => {
!config.constant.allow_static_const
&& !(config.constant.allow_constexpr && lit.can_be_constexpr())
}
Language::Cython => false,
}
}

fn add_literal_cfg_conditions(
conditions: &mut BTreeSet<Condition>,
lit: &Literal,
config: &Config,
) {
lit.visit(&mut |inner| {
if let Literal::Struct { fields, .. } = inner {
for field in fields.values() {
if let Some(condition) = field.cfg.to_condition(config) {
conditions.insert(condition);
}
}
}
true
});
}
5 changes: 5 additions & 0 deletions src/bindgen/language_backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub trait LanguageBackend: Sized {
fn open_namespaces<W: Write>(&mut self, out: &mut SourceWriter<W>);
fn close_namespaces<W: Write>(&mut self, out: &mut SourceWriter<W>);
fn write_headers<W: Write>(&self, out: &mut SourceWriter<W>, package_version: &str);
fn write_cfg_macros<W: Write>(&mut self, _out: &mut SourceWriter<W>, _b: &Bindings) {}
fn write_footers<W: Write>(&mut self, out: &mut SourceWriter<W>);
fn write_enum<W: Write>(&mut self, out: &mut SourceWriter<W>, e: &Enum);
fn write_struct<W: Write>(&mut self, out: &mut SourceWriter<W>, s: &Struct);
Expand Down Expand Up @@ -115,9 +116,13 @@ pub trait LanguageBackend: Sized {
fn write_type<W: Write>(&mut self, out: &mut SourceWriter<W>, t: &Type);
fn write_documentation<W: Write>(&mut self, out: &mut SourceWriter<W>, d: &Documentation);
fn write_literal<W: Write>(&mut self, out: &mut SourceWriter<W>, l: &Literal);
fn write_macro_literal<W: Write>(&mut self, out: &mut SourceWriter<W>, l: &Literal) {
self.write_literal(out, l);
}

fn write_bindings<W: Write>(&mut self, out: &mut SourceWriter<W>, b: &Bindings) {
self.write_headers(out, &b.package_version);
self.write_cfg_macros(out, b);
self.open_namespaces(out);
self.write_primitive_constants(out, b);
self.write_items(out, b);
Expand Down
10 changes: 8 additions & 2 deletions tests/expectations/cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ DEF M_32 = 0
#include <stdint.h>
#include <stdlib.h>

#if defined(X11)
#define __CBINDGEN_CFG_D3_X11(...) __VA_ARGS__
#else
#define __CBINDGEN_CFG_D3_X11(...)
#endif

#if (defined(PLATFORM_UNIX) && defined(X11))
enum FooType
#if __STDC_VERSION__ >= 202311L
Expand Down Expand Up @@ -123,8 +129,8 @@ typedef struct {
#endif
;
} ConditionalField;
#define ConditionalField_ZERO (ConditionalField){ .field = 0 }
#define ConditionalField_ONE (ConditionalField){ .field = 1 }
#define ConditionalField_ZERO (ConditionalField){ __CBINDGEN_CFG_D3_X11(.field = 0,) }
#define ConditionalField_ONE (ConditionalField){ __CBINDGEN_CFG_D3_X11(.field = 1,) }

typedef struct {
int32_t x;
Expand Down
10 changes: 8 additions & 2 deletions tests/expectations/cfg.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ DEF M_32 = 0
#include <stdint.h>
#include <stdlib.h>

#if defined(X11)
#define __CBINDGEN_CFG_D3_X11(...) __VA_ARGS__
#else
#define __CBINDGEN_CFG_D3_X11(...)
#endif

#if (defined(PLATFORM_UNIX) && defined(X11))
enum FooType
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
Expand Down Expand Up @@ -129,8 +135,8 @@ typedef struct {
#endif
;
} ConditionalField;
#define ConditionalField_ZERO (ConditionalField){ .field = 0 }
#define ConditionalField_ONE (ConditionalField){ .field = 1 }
#define ConditionalField_ZERO (ConditionalField){ __CBINDGEN_CFG_D3_X11(.field = 0,) }
#define ConditionalField_ONE (ConditionalField){ __CBINDGEN_CFG_D3_X11(.field = 1,) }

typedef struct {
int32_t x;
Expand Down
Loading
Loading