mozilla / cbindgen

A project for generating C bindings from Rust code
Mozilla Public License 2.0
2.43k stars 315 forks source link

[Feature Request] Have cbindgen set a conditional compilation attribute when generating #1014

Open bavalpey opened 1 month ago

bavalpey commented 1 month ago

Right now, there's no way to hide (or specifically include) code that only cbindgen needs to know about when parsing a file. There should be a conditional compilation attribute, or some other way of specifying code that only cbindgen needs to see. This should work just like the #[cfg(rust_analyzer)] attribute, or #[cfg(doc)] attribute.

There are a few use cases for this, but one of them is to selectively generate documentation, or to omit certain code. There are cases where cbindgen:ignore is not sufficient.

For instance, I don't want the cbindgen annotations cluttering up the public documentation for my code, so I use this odd macro cfg like so:

#[cfg_attr(not(any(doc, rust_analyzer)), doc = "cbindgen:[annotation]"

Another use case is to get cbindgen to output the correct structure or type of something that it isn't doing properly otherwise. For example, I have an issue where a renamed type does not play nicely with how cbindgen unravels *const T. Specifically, I have a type, FfiStr, that is a wrapper for *const c_char. I am currently using renaming to have cbindgen treat this as a const char* (which is correct). But cbindgen doesn't play nicely with this when I need to pass around arrays of these. Typing *const FfiStr results in const const char *.

I can't use a type alias, which cbidngen will just translate to using _ as ... when it sees it. For other reasons, I also can't use a type wrapper in Rust. However, if I were provided an annotation, then I could conditionally define a type annotation that cbindgen never sees.

The feature flag option in cbindgen.toml isn't sufficient for this. For one, this can cause undesirable behavior when invoking other tools that want--features=all or similar. It also doesn't really convey the intent very well: that this code snippet is only meant to be seen by cbindgen.

emilio commented 4 weeks ago

I can't use a type alias, which cbidngen will just translate to using _ as ... when it sees it. For other reasons, I also can't use a type wrapper in Rust. However, if I were provided an annotation, then I could conditionally define a type annotation that cbindgen never sees.

Can you write a concrete example of how would you use this? In any case I think random cfg's are not allowed by the compiler (or were). The ideal for the /// cbindgen: annotations would be #[cbindgen(...)] or so, the doc syntax was pretty much a stop-gap :/

bavalpey commented 4 weeks ago

Can you write a concrete example of how would you use this?

So, I would use this like so:

#[cfg(not(cbindgen)]
type FfiStrArray = *const FfiStr

#[cfg(cbindgen])
struct FfiStrArray {}

#[unsafe(no_mangle)]
pub extern "C" fn some_ffi_str_method(a: FfiStrArray) {
    // Use FfiStrArray here., and have FfiStrArray renamed in cbindgen.toml
}

I also would use it for doc comments. Right now what I am doing is wrapping every /// cbindgen: ... directive in #[cfg_attr(not(any(doc, rust_analyzer)), doc = "cbindgen: ...")].

With a cfg macro added, one could do:

[cfg_attr(cbindgen), doc = "cbindgen: ..."]

In any case I think random cfg's are not allowed by the compiler

I'm not sure when it was added, but rustc supports --cfg [var] and --cfg var=value to configure arbitrary cfgs when invoked. This is how rust_analyzer has its own cfg. Although, to make linters happy, one has to add the following to cargo.toml:

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(cbindgen)'] }

Essentially, wherever cbindgen is invoking rustc, you just have to add an additional --cfg cbindgen argument.