rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.4k stars 691 forks source link

Support for one set of bindings depending on another (conflicting `__BindgenBitfieldUnit`) #2835

Closed ian-h-chamberlain closed 3 months ago

ian-h-chamberlain commented 4 months ago

Hi! This is kind of a feature request for a (maybe unusual) use case where bindings are generated in multiple different crates. In our case we have citro3d-sys which depends on some types in ctru-sys.

The idea is to reuse existing types from ctru-sys wherever possible, instead of regenerating a duplicate definition. For the most part, this works great with allowlist_type / blocklist_type, so we can use something like this:

use ctru_sys::*;
use libc::*;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

There is one exception where this doesn't work — bindgen-generated types for bitfields. The various _bindgen_ty_* seem to avoid overlap (whether this is by design due to deterministic naming, or just coincidence?), but both sets generate pub struct __BindgenBitfieldUnit<Storage> as well, which unfortunately does overlap.

Input C/C++ Header

Minimal reproduction with two crates a and b:

a.h:

typedef struct A {
  unsigned char x;
  unsigned b1 : 1;
  unsigned b2 : 1;
  unsigned baz;
} A;

b.h:

#include "../a/a.h"

typedef struct B {
  A a;
} B;

typedef struct C {
  unsigned char x;
  unsigned b1 : 1;
  unsigned b2 : 1;
  unsigned baz;
} C;

Bindgen Invocation

a/build.rs:

    bindgen::Builder::default()
        .header("a.h")
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file(PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("bindings.rs"))
        .expect("Couldn't write bindings!");

b/build.rs:

    bindgen::Builder::default()
        .header("b.h")
        .blocklist_type("A")
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file(PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("bindings.rs"))
        .expect("Couldn't write bindings!");

Actual Results

With b/lib.rs:

// Makes it clear when there are duplicates:
#![deny(ambiguous_glob_reexports)]

pub use a::*;

mod bindings {
    use a::*;
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

pub use bindings::*;
$ cargo check --workspace
   Compiling b v0.1.0 (/Users/ianchamberlain/Documents/Development/rs-sandbox/b)
error: ambiguous glob re-exports
  --> b/src/lib.rs:3:9
   |
3  | pub use a::*;
   |         ^^^^ the name `__BindgenBitfieldUnit` in the type namespace is first re-exported here
...
11 | pub use bindings::*;
   |         ----------- but the name `__BindgenBitfieldUnit` in the type namespace is also re-exported here
   |
note: the lint level is defined here
  --> b/src/lib.rs:1:9
   |
1  | #![deny(ambiguous_glob_reexports)]
   |         ^^^^^^^^^^^^^^^^^^^^^^^^

error: could not compile `b` (lib) due to 1 previous error

Expected Results

I think the simplest option to address this in our case would be a flag to skip generating the __BindgenBitfieldUnit type — would a PR be accepted to add such a flag? I have mostly worked out the implementation already, but maybe there's a better/simpler way to support this kind of use case?

If it would be accepted, I can open a PR adding a skip_bindgen_bitfield_unit flag, which in this case could be used to avoid the generated conflict by reusing a's version of the struct instead of generating a new one in b.

Thank you!

emilio commented 4 months ago

I would prefer, I think, making blocklist_type("__BindgenBitfieldUnit") work and documenting it I believe

emilio commented 4 months ago

(same for the other built-in types of course)

ian-h-chamberlain commented 4 months ago

Ah, that would make sense! I think I can adjust my first attempt to handle that without too much trouble, at least for the bitfield unit type. I'll look at doing the same for other generated types too.

Do you have a suggestion for where exactly to document this (e.g. on blocklist_type itself, or perhaps in the book's "bitfields" page? The latter might make sense for the bitfield unit specifically, but maybe not for anonymous types like _bindgen_ty_1