From 884ce27582e0421a4ec23f7ae79a7ce45fa25efd Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 31 Aug 2026 23:12:40 +0900 Subject: [PATCH 1/2] Add export.include_all for public types --- CHANGES | 1 + docs.md | 11 +++ src/bindgen/builder.rs | 13 +++- src/bindgen/config.rs | 5 ++ src/bindgen/parser.rs | 16 ++++- template.toml | 1 + tests/expectations-symbols/include_all.c.sym | 2 + tests/expectations/include_all.c | 44 ++++++++++++ tests/expectations/include_all.compat.c | 46 +++++++++++++ tests/expectations/include_all.cpp | 36 ++++++++++ tests/expectations/include_all.pyx | 36 ++++++++++ tests/expectations/include_all_both.c | 44 ++++++++++++ tests/expectations/include_all_both.compat.c | 46 +++++++++++++ tests/expectations/include_all_tag.c | 44 ++++++++++++ tests/expectations/include_all_tag.compat.c | 46 +++++++++++++ tests/expectations/include_all_tag.pyx | 36 ++++++++++ tests/rust/include_all.rs | 71 ++++++++++++++++++++ tests/rust/include_all.toml | 4 ++ 18 files changed, 499 insertions(+), 3 deletions(-) create mode 100644 tests/expectations-symbols/include_all.c.sym create mode 100644 tests/expectations/include_all.c create mode 100644 tests/expectations/include_all.compat.c create mode 100644 tests/expectations/include_all.cpp create mode 100644 tests/expectations/include_all.pyx create mode 100644 tests/expectations/include_all_both.c create mode 100644 tests/expectations/include_all_both.compat.c create mode 100644 tests/expectations/include_all_tag.c create mode 100644 tests/expectations/include_all_tag.compat.c create mode 100644 tests/expectations/include_all_tag.pyx create mode 100644 tests/rust/include_all.rs create mode 100644 tests/rust/include_all.toml diff --git a/CHANGES b/CHANGES index 6fd28678c..a72ebc823 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,6 @@ # unreleased + * Add `export.include_all` option to automatically export all public non-opaque types. * Emit `&str`, `&CStr`, and arrays thereof as C string literals, and byte-string (`b"..."`) constants as `uint8_t[]` arrays, instead of dropping them. diff --git a/docs.md b/docs.md index aa071ac62..901dd330b 100644 --- a/docs.md +++ b/docs.md @@ -601,6 +601,17 @@ usize_is_size_t = true # default: [] include = ["MyOrphanStruct", "MyGreatTypeRename"] +# Whether to include all public, non-opaque types parsed by cbindgen (`#[repr(C)]` +# structs/unions, `#[repr(u*)]`/`#[repr(C)]` enums, `#[repr(transparent)]` types, +# and typedefs), even if they are not used by any exported functions. Visibility +# of parent modules is not considered: a type declared `pub` in a private module +# is included. Opaque items are included only when named by `include` or required +# by another exported item. This applies to every crate selected by the parse +# configuration. +# +# default: false +include_all = false + # A list of items to not include in the generated bindings # default: [] exclude = ["Bad"] diff --git a/src/bindgen/builder.rs b/src/bindgen/builder.rs index 8521b706e..a5e2c70b4 100644 --- a/src/bindgen/builder.rs +++ b/src/bindgen/builder.rs @@ -170,6 +170,12 @@ impl Builder { self } + #[allow(unused)] + pub fn with_include_all(mut self, include_all: bool) -> Builder { + self.config.export.include_all = include_all; + self + } + #[allow(unused)] pub fn exclude_item>(mut self, item_name: S) -> Builder { self.config @@ -401,8 +407,13 @@ impl Builder { result.source_files.extend_from_slice(self.srcs.as_slice()); + let mut config = self.config; + if config.export.include_all { + config.export.include.extend(result.public_types); + } + Library::new( - self.config, + config, result.constants, result.globals, result.enums, diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index acffa3b2f..19cc5c057 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -325,6 +325,11 @@ pub struct ExportConfig { /// A list of additional items not used by exported functions to include in /// the generated bindings pub include: Vec, + /// Whether to include all public, non-opaque types in the generated bindings, + /// even if they are not used by any exported functions. Parent module + /// visibility is not considered. Opaque types are included only when named + /// by `include` or required by another exported item. + pub include_all: bool, /// A list of items to not include in the generated bindings pub exclude: Vec, /// Table of name conversions to apply to item names diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs index 7f004d1ab..dabeb5e77 100644 --- a/src/bindgen/parser.rs +++ b/src/bindgen/parser.rs @@ -420,6 +420,8 @@ pub struct Parse { pub functions: Vec, pub source_files: Vec, pub package_version: String, + /// Public types that were successfully parsed into non-opaque IR items. + pub public_types: Vec, } impl Parse { @@ -435,6 +437,7 @@ impl Parse { functions: Vec::new(), source_files: Vec::new(), package_version: String::new(), + public_types: Vec::new(), } } @@ -484,6 +487,13 @@ impl Parse { self.functions.extend_from_slice(&other.functions); self.source_files.extend_from_slice(&other.source_files); self.package_version.clone_from(&other.package_version); + self.public_types.extend_from_slice(&other.public_types); + } + + fn record_public_type(&mut self, vis: &syn::Visibility, path: &Path) { + if matches!(vis, syn::Visibility::Public(_)) { + self.public_types.push(path.name().to_owned()); + } } fn load_syn_crate_mod<'a>( @@ -916,6 +926,7 @@ impl Parse { match Struct::load(&config.layout, item, mod_cfg) { Ok(st) => { info!("Take {}::{}.", crate_name, item.ident); + self.record_public_type(&item.vis, &st.path); self.structs.try_insert(st); } Err(msg) => { @@ -939,7 +950,7 @@ impl Parse { match Union::load(&config.layout, item, mod_cfg) { Ok(st) => { info!("Take {}::{}.", crate_name, item.ident); - + self.record_public_type(&item.vis, &st.path); self.unions.try_insert(st); } Err(msg) => { @@ -963,6 +974,7 @@ impl Parse { match Enum::load(item, mod_cfg, config) { Ok(en) => { info!("Take {}::{}.", crate_name, item.ident); + self.record_public_type(&item.vis, &en.path); self.enums.try_insert(en); } Err(msg) => { @@ -980,7 +992,7 @@ impl Parse { match Typedef::load(item, mod_cfg) { Ok(st) => { info!("Take {}::{}.", crate_name, item.ident); - + self.record_public_type(&item.vis, &st.path); self.typedefs.try_insert(st); } Err(msg) => { diff --git a/template.toml b/template.toml index 1c416aa10..884760776 100644 --- a/template.toml +++ b/template.toml @@ -59,6 +59,7 @@ usize_is_size_t = true [export] include = [] +include_all = false exclude = [] # prefix = "CAPI_" item_types = [] diff --git a/tests/expectations-symbols/include_all.c.sym b/tests/expectations-symbols/include_all.c.sym new file mode 100644 index 000000000..33e9f352d --- /dev/null +++ b/tests/expectations-symbols/include_all.c.sym @@ -0,0 +1,2 @@ +{ +}; \ No newline at end of file diff --git a/tests/expectations/include_all.c b/tests/expectations/include_all.c new file mode 100644 index 000000000..000bc4e3d --- /dev/null +++ b/tests/expectations/include_all.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +enum UnusedEnum +#if __STDC_VERSION__ >= 202311L + : uint8_t +#endif // __STDC_VERSION__ >= 202311L + { + A, + B, +}; +#if __STDC_VERSION__ >= 202311L +typedef enum UnusedEnum UnusedEnum; +#else +typedef uint8_t UnusedEnum; +#endif // __STDC_VERSION__ >= 202311L + +typedef struct ExplicitOpaque ExplicitOpaque; + +typedef struct OpaqueDependency OpaqueDependency; + +typedef struct { + int32_t x; + float y; +} UnusedStruct; + +typedef uint32_t UnusedTransparent; + +typedef union { + int32_t x; + float y; +} UnusedUnion; + +typedef int32_t UnusedAlias; + +typedef struct { + OpaqueDependency *opaque; +} UsesOpaqueDependency; + +typedef struct { + int32_t x; +} PublicInPrivateModule; diff --git a/tests/expectations/include_all.compat.c b/tests/expectations/include_all.compat.c new file mode 100644 index 000000000..dcc2eec96 --- /dev/null +++ b/tests/expectations/include_all.compat.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +enum UnusedEnum +#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L + : uint8_t +#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L + { + A, + B, +}; +#ifndef __cplusplus +#if __STDC_VERSION__ >= 202311L +typedef enum UnusedEnum UnusedEnum; +#else +typedef uint8_t UnusedEnum; +#endif // __STDC_VERSION__ >= 202311L +#endif // __cplusplus + +typedef struct ExplicitOpaque ExplicitOpaque; + +typedef struct OpaqueDependency OpaqueDependency; + +typedef struct { + int32_t x; + float y; +} UnusedStruct; + +typedef uint32_t UnusedTransparent; + +typedef union { + int32_t x; + float y; +} UnusedUnion; + +typedef int32_t UnusedAlias; + +typedef struct { + OpaqueDependency *opaque; +} UsesOpaqueDependency; + +typedef struct { + int32_t x; +} PublicInPrivateModule; diff --git a/tests/expectations/include_all.cpp b/tests/expectations/include_all.cpp new file mode 100644 index 000000000..b67d3cdad --- /dev/null +++ b/tests/expectations/include_all.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +enum class UnusedEnum : uint8_t { + A, + B, +}; + +struct ExplicitOpaque; + +struct OpaqueDependency; + +struct UnusedStruct { + int32_t x; + float y; +}; + +using UnusedTransparent = uint32_t; + +union UnusedUnion { + int32_t x; + float y; +}; + +using UnusedAlias = int32_t; + +struct UsesOpaqueDependency { + OpaqueDependency *opaque; +}; + +struct PublicInPrivateModule { + int32_t x; +}; diff --git a/tests/expectations/include_all.pyx b/tests/expectations/include_all.pyx new file mode 100644 index 000000000..0669f3439 --- /dev/null +++ b/tests/expectations/include_all.pyx @@ -0,0 +1,36 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + cdef enum: + A, + B, + ctypedef uint8_t UnusedEnum; + + ctypedef struct ExplicitOpaque: + pass + + ctypedef struct OpaqueDependency: + pass + + ctypedef struct UnusedStruct: + int32_t x; + float y; + + ctypedef uint32_t UnusedTransparent; + + ctypedef union UnusedUnion: + int32_t x; + float y; + + ctypedef int32_t UnusedAlias; + + ctypedef struct UsesOpaqueDependency: + OpaqueDependency *opaque; + + ctypedef struct PublicInPrivateModule: + int32_t x; diff --git a/tests/expectations/include_all_both.c b/tests/expectations/include_all_both.c new file mode 100644 index 000000000..534bdf78d --- /dev/null +++ b/tests/expectations/include_all_both.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +enum UnusedEnum +#if __STDC_VERSION__ >= 202311L + : uint8_t +#endif // __STDC_VERSION__ >= 202311L + { + A, + B, +}; +#if __STDC_VERSION__ >= 202311L +typedef enum UnusedEnum UnusedEnum; +#else +typedef uint8_t UnusedEnum; +#endif // __STDC_VERSION__ >= 202311L + +typedef struct ExplicitOpaque ExplicitOpaque; + +typedef struct OpaqueDependency OpaqueDependency; + +typedef struct UnusedStruct { + int32_t x; + float y; +} UnusedStruct; + +typedef uint32_t UnusedTransparent; + +typedef union UnusedUnion { + int32_t x; + float y; +} UnusedUnion; + +typedef int32_t UnusedAlias; + +typedef struct UsesOpaqueDependency { + struct OpaqueDependency *opaque; +} UsesOpaqueDependency; + +typedef struct PublicInPrivateModule { + int32_t x; +} PublicInPrivateModule; diff --git a/tests/expectations/include_all_both.compat.c b/tests/expectations/include_all_both.compat.c new file mode 100644 index 000000000..a83a1ee94 --- /dev/null +++ b/tests/expectations/include_all_both.compat.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +enum UnusedEnum +#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L + : uint8_t +#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L + { + A, + B, +}; +#ifndef __cplusplus +#if __STDC_VERSION__ >= 202311L +typedef enum UnusedEnum UnusedEnum; +#else +typedef uint8_t UnusedEnum; +#endif // __STDC_VERSION__ >= 202311L +#endif // __cplusplus + +typedef struct ExplicitOpaque ExplicitOpaque; + +typedef struct OpaqueDependency OpaqueDependency; + +typedef struct UnusedStruct { + int32_t x; + float y; +} UnusedStruct; + +typedef uint32_t UnusedTransparent; + +typedef union UnusedUnion { + int32_t x; + float y; +} UnusedUnion; + +typedef int32_t UnusedAlias; + +typedef struct UsesOpaqueDependency { + struct OpaqueDependency *opaque; +} UsesOpaqueDependency; + +typedef struct PublicInPrivateModule { + int32_t x; +} PublicInPrivateModule; diff --git a/tests/expectations/include_all_tag.c b/tests/expectations/include_all_tag.c new file mode 100644 index 000000000..3ff1e2e21 --- /dev/null +++ b/tests/expectations/include_all_tag.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +enum UnusedEnum +#if __STDC_VERSION__ >= 202311L + : uint8_t +#endif // __STDC_VERSION__ >= 202311L + { + A, + B, +}; +#if __STDC_VERSION__ >= 202311L +typedef enum UnusedEnum UnusedEnum; +#else +typedef uint8_t UnusedEnum; +#endif // __STDC_VERSION__ >= 202311L + +struct ExplicitOpaque; + +struct OpaqueDependency; + +struct UnusedStruct { + int32_t x; + float y; +}; + +typedef uint32_t UnusedTransparent; + +union UnusedUnion { + int32_t x; + float y; +}; + +typedef int32_t UnusedAlias; + +struct UsesOpaqueDependency { + struct OpaqueDependency *opaque; +}; + +struct PublicInPrivateModule { + int32_t x; +}; diff --git a/tests/expectations/include_all_tag.compat.c b/tests/expectations/include_all_tag.compat.c new file mode 100644 index 000000000..f1d19ed15 --- /dev/null +++ b/tests/expectations/include_all_tag.compat.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +enum UnusedEnum +#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L + : uint8_t +#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L + { + A, + B, +}; +#ifndef __cplusplus +#if __STDC_VERSION__ >= 202311L +typedef enum UnusedEnum UnusedEnum; +#else +typedef uint8_t UnusedEnum; +#endif // __STDC_VERSION__ >= 202311L +#endif // __cplusplus + +struct ExplicitOpaque; + +struct OpaqueDependency; + +struct UnusedStruct { + int32_t x; + float y; +}; + +typedef uint32_t UnusedTransparent; + +union UnusedUnion { + int32_t x; + float y; +}; + +typedef int32_t UnusedAlias; + +struct UsesOpaqueDependency { + struct OpaqueDependency *opaque; +}; + +struct PublicInPrivateModule { + int32_t x; +}; diff --git a/tests/expectations/include_all_tag.pyx b/tests/expectations/include_all_tag.pyx new file mode 100644 index 000000000..81cfdb57b --- /dev/null +++ b/tests/expectations/include_all_tag.pyx @@ -0,0 +1,36 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + cdef enum: + A, + B, + ctypedef uint8_t UnusedEnum; + + cdef struct ExplicitOpaque: + pass + + cdef struct OpaqueDependency: + pass + + cdef struct UnusedStruct: + int32_t x; + float y; + + ctypedef uint32_t UnusedTransparent; + + cdef union UnusedUnion: + int32_t x; + float y; + + ctypedef int32_t UnusedAlias; + + cdef struct UsesOpaqueDependency: + OpaqueDependency *opaque; + + cdef struct PublicInPrivateModule: + int32_t x; diff --git a/tests/rust/include_all.rs b/tests/rust/include_all.rs new file mode 100644 index 000000000..3bb95b93d --- /dev/null +++ b/tests/rust/include_all.rs @@ -0,0 +1,71 @@ +#[repr(C)] +pub struct UnusedStruct { + x: i32, + y: f32, +} + +#[repr(u8)] +pub enum UnusedEnum { + A, + B, +} + +#[repr(transparent)] +pub struct UnusedTransparent(u32); + +#[repr(C)] +pub union UnusedUnion { + x: i32, + y: f32, +} + +pub type UnusedAlias = i32; + +pub struct NotCbindgenable { + x: i32, +} + +pub struct ExplicitOpaque { + x: i32, +} + +struct OpaqueDependency { + x: i32, +} + +#[repr(C)] +pub struct UsesOpaqueDependency { + opaque: *mut OpaqueDependency, +} + +#[repr(C)] +pub struct ExcludedStruct { + x: i32, +} + +#[repr(C)] +struct PrivateStruct { + x: i32, +} + +#[repr(u8)] +enum PrivateEnum { + A, +} + +#[repr(transparent)] +struct PrivateTransparent(u32); + +#[repr(C)] +union PrivateUnion { + x: i32, +} + +type PrivateAlias = i32; + +mod private_module { + #[repr(C)] + pub struct PublicInPrivateModule { + pub x: i32, + } +} diff --git a/tests/rust/include_all.toml b/tests/rust/include_all.toml new file mode 100644 index 000000000..b4e10a8d9 --- /dev/null +++ b/tests/rust/include_all.toml @@ -0,0 +1,4 @@ +[export] +include_all = true +include = ["ExplicitOpaque"] +exclude = ["ExcludedStruct"] From ae63b6e3f7ef591565bedc39650ae87304b51a22 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 31 Aug 2026 23:41:20 +0900 Subject: [PATCH 2/2] Pass export.include_all roots to Library, gate by crate `export.include_all` appended the collected public type names to `config.export.include` after parsing. The roots now travel through a `public_types` field on `Parse` and `Library` instead, and `Library::generate` seeds them next to functions, globals and constants. `Library::add_root` holds the traversal shared with `export.include`. Recording a public type now requires `ParseConfig::should_generate_top_level_item`, the gate that functions, statics and constants already pass, so only the binding crate and `parse.extra_bindings` contribute. Types of other parsed crates are still emitted when an exported item depends on them. Assisted-by: Claude --- CHANGES | 3 +- Cargo.toml | 2 + docs.md | 8 ++- src/bindgen/builder.rs | 8 +-- src/bindgen/config.rs | 3 +- src/bindgen/library.rs | 38 ++++++++++---- src/bindgen/parser.rs | 51 +++++++++++++------ .../include_all_deps.c.sym | 3 ++ tests/expectations/include_all_deps.c | 21 ++++++++ tests/expectations/include_all_deps.compat.c | 29 +++++++++++ tests/expectations/include_all_deps.cpp | 22 ++++++++ tests/expectations/include_all_deps.pyx | 18 +++++++ tests/expectations/include_all_deps_both.c | 21 ++++++++ .../include_all_deps_both.compat.c | 29 +++++++++++ tests/expectations/include_all_deps_tag.c | 21 ++++++++ .../include_all_deps_tag.compat.c | 29 +++++++++++ tests/expectations/include_all_deps_tag.pyx | 18 +++++++ tests/rust/include_all_deps/Cargo.lock | 14 +++++ tests/rust/include_all_deps/Cargo.toml | 8 +++ tests/rust/include_all_deps/cbindgen.toml | 6 +++ tests/rust/include_all_deps/dep/Cargo.toml | 7 +++ tests/rust/include_all_deps/dep/src/lib.rs | 19 +++++++ tests/rust/include_all_deps/src/lib.rs | 12 +++++ 23 files changed, 353 insertions(+), 37 deletions(-) create mode 100644 tests/expectations-symbols/include_all_deps.c.sym create mode 100644 tests/expectations/include_all_deps.c create mode 100644 tests/expectations/include_all_deps.compat.c create mode 100644 tests/expectations/include_all_deps.cpp create mode 100644 tests/expectations/include_all_deps.pyx create mode 100644 tests/expectations/include_all_deps_both.c create mode 100644 tests/expectations/include_all_deps_both.compat.c create mode 100644 tests/expectations/include_all_deps_tag.c create mode 100644 tests/expectations/include_all_deps_tag.compat.c create mode 100644 tests/expectations/include_all_deps_tag.pyx create mode 100644 tests/rust/include_all_deps/Cargo.lock create mode 100644 tests/rust/include_all_deps/Cargo.toml create mode 100644 tests/rust/include_all_deps/cbindgen.toml create mode 100644 tests/rust/include_all_deps/dep/Cargo.toml create mode 100644 tests/rust/include_all_deps/dep/src/lib.rs create mode 100644 tests/rust/include_all_deps/src/lib.rs diff --git a/CHANGES b/CHANGES index a72ebc823..bcdaadec8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ # unreleased - * Add `export.include_all` option to automatically export all public non-opaque types. + * Add `export.include_all` option to automatically export the public non-opaque + types of the binding crate. * Emit `&str`, `&CStr`, and arrays thereof as C string literals, and byte-string (`b"..."`) constants as `uint8_t[]` arrays, instead of dropping them. diff --git a/Cargo.toml b/Cargo.toml index e279f2b07..3bcba0738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,8 @@ exclude = [ "tests/rust/expand_no_default_features", "tests/rust/expand", "tests/rust/external_workspace_child", + "tests/rust/include_all_deps", + "tests/rust/include_all_deps/dep", "tests/rust/literal_target", "tests/rust/mod_2015", "tests/rust/mod_2018", diff --git a/docs.md b/docs.md index 901dd330b..68c61adae 100644 --- a/docs.md +++ b/docs.md @@ -606,8 +606,12 @@ include = ["MyOrphanStruct", "MyGreatTypeRename"] # and typedefs), even if they are not used by any exported functions. Visibility # of parent modules is not considered: a type declared `pub` in a private module # is included. Opaque items are included only when named by `include` or required -# by another exported item. This applies to every crate selected by the parse -# configuration. +# by another exported item. +# +# Only the binding crate and the crates listed in `parse.extra_bindings` are +# considered, the same scope in which `pub` constants and `#[no_mangle]` +# functions are picked up. Types of other parsed crates are still emitted when +# an exported item depends on them. # # default: false include_all = false diff --git a/src/bindgen/builder.rs b/src/bindgen/builder.rs index a5e2c70b4..e7912b15d 100644 --- a/src/bindgen/builder.rs +++ b/src/bindgen/builder.rs @@ -407,13 +407,8 @@ impl Builder { result.source_files.extend_from_slice(self.srcs.as_slice()); - let mut config = self.config; - if config.export.include_all { - config.export.include.extend(result.public_types); - } - Library::new( - config, + self.config, result.constants, result.globals, result.enums, @@ -422,6 +417,7 @@ impl Builder { result.opaque_items, result.typedefs, result.functions, + result.public_types, result.source_files, result.package_version, ) diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index 19cc5c057..5cda6db2f 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -328,7 +328,8 @@ pub struct ExportConfig { /// Whether to include all public, non-opaque types in the generated bindings, /// even if they are not used by any exported functions. Parent module /// visibility is not considered. Opaque types are included only when named - /// by `include` or required by another exported item. + /// by `include` or required by another exported item. Restricted to the + /// binding crate and `parse.extra_bindings`, like functions and constants. pub include_all: bool, /// A list of items to not include in the generated bindings pub exclude: Vec, diff --git a/src/bindgen/library.rs b/src/bindgen/library.rs index 9bccceb59..1a1cba305 100644 --- a/src/bindgen/library.rs +++ b/src/bindgen/library.rs @@ -26,6 +26,10 @@ pub struct Library { opaque_items: ItemMap, typedefs: ItemMap, functions: Vec, + /// Paths of the `pub`, non-opaque types declared by the crates that are + /// allowed to contribute top-level items. Used as dependency roots when + /// `export.include_all` is set. + public_types: Vec, source_files: Vec, package_version: String, } @@ -42,6 +46,7 @@ impl Library { opaque_items: ItemMap, typedefs: ItemMap, functions: Vec, + public_types: Vec, source_files: Vec, package_version: String, ) -> Library { @@ -55,6 +60,7 @@ impl Library { opaque_items, typedefs, functions, + public_types, source_files, package_version, } @@ -90,19 +96,14 @@ impl Library { self.constants.for_all_items(|constant| { constant.add_dependencies(&self, &mut dependencies); }); - for name in &self.config.export.include { - let path = Path::new(name.clone()); - if let Some(items) = self.get_items(&path) { - if dependencies.items.insert(path) { - for item in &items { - item.deref().add_dependencies(&self, &mut dependencies); - } - for item in items { - dependencies.order.push(item); - } - } + if self.config.export.include_all { + for path in &self.public_types { + self.add_root(&mut dependencies, path.clone()); } } + for name in &self.config.export.include { + self.add_root(&mut dependencies, Path::new(name.clone())); + } dependencies.sort(); @@ -149,6 +150,21 @@ impl Library { )) } + /// Adds `path`, and everything it depends on, to `dependencies`. Paths that + /// don't resolve to a generated item are silently ignored. + fn add_root(&self, dependencies: &mut Dependencies, path: Path) { + let Some(items) = self.get_items(&path) else { + return; + }; + if !dependencies.items.insert(path) { + return; + } + for item in &items { + item.deref().add_dependencies(self, dependencies); + } + dependencies.order.extend(items); + } + pub fn get_items(&self, p: &Path) -> Option> { macro_rules! find { ($field:ident, $kind:ident) => { diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs index dabeb5e77..26ea1c041 100644 --- a/src/bindgen/parser.rs +++ b/src/bindgen/parser.rs @@ -420,8 +420,10 @@ pub struct Parse { pub functions: Vec, pub source_files: Vec, pub package_version: String, - /// Public types that were successfully parsed into non-opaque IR items. - pub public_types: Vec, + /// Paths of the `pub`, non-opaque types declared by the crates that are + /// allowed to contribute top-level items. Used as dependency roots when + /// `export.include_all` is set. + pub public_types: Vec, } impl Parse { @@ -490,9 +492,9 @@ impl Parse { self.public_types.extend_from_slice(&other.public_types); } - fn record_public_type(&mut self, vis: &syn::Visibility, path: &Path) { - if matches!(vis, syn::Visibility::Public(_)) { - self.public_types.push(path.name().to_owned()); + fn record_public_type(&mut self, record: bool, vis: &syn::Visibility, path: &Path) { + if record && matches!(vis, syn::Visibility::Public(_)) { + self.public_types.push(path.clone()); } } @@ -507,6 +509,13 @@ impl Parse { let mut impls_with_assoc_consts = Vec::new(); let mut nested_modules = Vec::new(); + // Types are parsed from every crate, since exported items may reference + // them, but only the crates that contribute top-level items may seed the + // root set of `export.include_all`. + let record_public_types = config + .parse + .should_generate_top_level_item(crate_name, binding_crate_name); + for item in items { if item.should_skip_parsing() { continue; @@ -531,16 +540,16 @@ impl Parse { self.load_syn_static(config, binding_crate_name, crate_name, mod_cfg, item); } syn::Item::Struct(ref item) => { - self.load_syn_struct(config, crate_name, mod_cfg, item); + self.load_syn_struct(config, record_public_types, crate_name, mod_cfg, item); } syn::Item::Union(ref item) => { - self.load_syn_union(config, crate_name, mod_cfg, item); + self.load_syn_union(config, record_public_types, crate_name, mod_cfg, item); } syn::Item::Enum(ref item) => { - self.load_syn_enum(config, crate_name, mod_cfg, item); + self.load_syn_enum(config, record_public_types, crate_name, mod_cfg, item); } syn::Item::Type(ref item) => { - self.load_syn_ty(crate_name, mod_cfg, item); + self.load_syn_ty(record_public_types, crate_name, mod_cfg, item); } syn::Item::Impl(ref item_impl) => { let has_assoc_const = item_impl @@ -572,7 +581,7 @@ impl Parse { } } syn::Item::Macro(ref item) => { - self.load_builtin_macro(config, crate_name, mod_cfg, item); + self.load_builtin_macro(config, record_public_types, crate_name, mod_cfg, item); } syn::Item::Mod(ref item) => { nested_modules.push(item); @@ -919,6 +928,7 @@ impl Parse { fn load_syn_struct( &mut self, config: &Config, + record_public_types: bool, crate_name: &str, mod_cfg: Option<&Cfg>, item: &syn::ItemStruct, @@ -926,7 +936,7 @@ impl Parse { match Struct::load(&config.layout, item, mod_cfg) { Ok(st) => { info!("Take {}::{}.", crate_name, item.ident); - self.record_public_type(&item.vis, &st.path); + self.record_public_type(record_public_types, &item.vis, &st.path); self.structs.try_insert(st); } Err(msg) => { @@ -943,6 +953,7 @@ impl Parse { fn load_syn_union( &mut self, config: &Config, + record_public_types: bool, crate_name: &str, mod_cfg: Option<&Cfg>, item: &syn::ItemUnion, @@ -950,7 +961,7 @@ impl Parse { match Union::load(&config.layout, item, mod_cfg) { Ok(st) => { info!("Take {}::{}.", crate_name, item.ident); - self.record_public_type(&item.vis, &st.path); + self.record_public_type(record_public_types, &item.vis, &st.path); self.unions.try_insert(st); } Err(msg) => { @@ -967,6 +978,7 @@ impl Parse { fn load_syn_enum( &mut self, config: &Config, + record_public_types: bool, crate_name: &str, mod_cfg: Option<&Cfg>, item: &syn::ItemEnum, @@ -974,7 +986,7 @@ impl Parse { match Enum::load(item, mod_cfg, config) { Ok(en) => { info!("Take {}::{}.", crate_name, item.ident); - self.record_public_type(&item.vis, &en.path); + self.record_public_type(record_public_types, &item.vis, &en.path); self.enums.try_insert(en); } Err(msg) => { @@ -988,11 +1000,17 @@ impl Parse { } /// Loads a `type` declaration - fn load_syn_ty(&mut self, crate_name: &str, mod_cfg: Option<&Cfg>, item: &syn::ItemType) { + fn load_syn_ty( + &mut self, + record_public_types: bool, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemType, + ) { match Typedef::load(item, mod_cfg) { Ok(st) => { info!("Take {}::{}.", crate_name, item.ident); - self.record_public_type(&item.vis, &st.path); + self.record_public_type(record_public_types, &item.vis, &st.path); self.typedefs.try_insert(st); } Err(msg) => { @@ -1008,6 +1026,7 @@ impl Parse { fn load_builtin_macro( &mut self, config: &Config, + record_public_types: bool, crate_name: &str, mod_cfg: Option<&Cfg>, item: &syn::ItemMacro, @@ -1040,7 +1059,7 @@ impl Parse { let (struct_, impl_) = bitflags.expand(out_of_line_transparent); if let Some(struct_) = struct_ { - self.load_syn_struct(config, crate_name, mod_cfg, &struct_); + self.load_syn_struct(config, record_public_types, crate_name, mod_cfg, &struct_); } if let syn::Type::Path(ref path) = *impl_.self_ty { if let Some(type_name) = path.path.get_ident() { diff --git a/tests/expectations-symbols/include_all_deps.c.sym b/tests/expectations-symbols/include_all_deps.c.sym new file mode 100644 index 000000000..3ac657824 --- /dev/null +++ b/tests/expectations-symbols/include_all_deps.c.sym @@ -0,0 +1,3 @@ +{ +get_x; +}; \ No newline at end of file diff --git a/tests/expectations/include_all_deps.c b/tests/expectations/include_all_deps.c new file mode 100644 index 000000000..cff97788c --- /dev/null +++ b/tests/expectations/include_all_deps.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +/** + * Reachable from an exported function, so it is emitted even though its crate + * does not contribute top-level items. + */ +typedef struct { + uint32_t x; +} UsedDepStruct; + +/** + * Not used by any exported item, but declared by the binding crate. + */ +typedef struct { + uint32_t z; +} UnusedLocalStruct; + +uint32_t get_x(const UsedDepStruct *used); diff --git a/tests/expectations/include_all_deps.compat.c b/tests/expectations/include_all_deps.compat.c new file mode 100644 index 000000000..126ad26c7 --- /dev/null +++ b/tests/expectations/include_all_deps.compat.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +/** + * Reachable from an exported function, so it is emitted even though its crate + * does not contribute top-level items. + */ +typedef struct { + uint32_t x; +} UsedDepStruct; + +/** + * Not used by any exported item, but declared by the binding crate. + */ +typedef struct { + uint32_t z; +} UnusedLocalStruct; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +uint32_t get_x(const UsedDepStruct *used); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/include_all_deps.cpp b/tests/expectations/include_all_deps.cpp new file mode 100644 index 000000000..b276bdfd3 --- /dev/null +++ b/tests/expectations/include_all_deps.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +/// Reachable from an exported function, so it is emitted even though its crate +/// does not contribute top-level items. +struct UsedDepStruct { + uint32_t x; +}; + +/// Not used by any exported item, but declared by the binding crate. +struct UnusedLocalStruct { + uint32_t z; +}; + +extern "C" { + +uint32_t get_x(const UsedDepStruct *used); + +} // extern "C" diff --git a/tests/expectations/include_all_deps.pyx b/tests/expectations/include_all_deps.pyx new file mode 100644 index 000000000..7ccd77dbf --- /dev/null +++ b/tests/expectations/include_all_deps.pyx @@ -0,0 +1,18 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + # Reachable from an exported function, so it is emitted even though its crate + # does not contribute top-level items. + ctypedef struct UsedDepStruct: + uint32_t x; + + # Not used by any exported item, but declared by the binding crate. + ctypedef struct UnusedLocalStruct: + uint32_t z; + + uint32_t get_x(const UsedDepStruct *used); diff --git a/tests/expectations/include_all_deps_both.c b/tests/expectations/include_all_deps_both.c new file mode 100644 index 000000000..29d309f4f --- /dev/null +++ b/tests/expectations/include_all_deps_both.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +/** + * Reachable from an exported function, so it is emitted even though its crate + * does not contribute top-level items. + */ +typedef struct UsedDepStruct { + uint32_t x; +} UsedDepStruct; + +/** + * Not used by any exported item, but declared by the binding crate. + */ +typedef struct UnusedLocalStruct { + uint32_t z; +} UnusedLocalStruct; + +uint32_t get_x(const struct UsedDepStruct *used); diff --git a/tests/expectations/include_all_deps_both.compat.c b/tests/expectations/include_all_deps_both.compat.c new file mode 100644 index 000000000..37d1f1deb --- /dev/null +++ b/tests/expectations/include_all_deps_both.compat.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +/** + * Reachable from an exported function, so it is emitted even though its crate + * does not contribute top-level items. + */ +typedef struct UsedDepStruct { + uint32_t x; +} UsedDepStruct; + +/** + * Not used by any exported item, but declared by the binding crate. + */ +typedef struct UnusedLocalStruct { + uint32_t z; +} UnusedLocalStruct; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +uint32_t get_x(const struct UsedDepStruct *used); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/include_all_deps_tag.c b/tests/expectations/include_all_deps_tag.c new file mode 100644 index 000000000..71947987a --- /dev/null +++ b/tests/expectations/include_all_deps_tag.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +/** + * Reachable from an exported function, so it is emitted even though its crate + * does not contribute top-level items. + */ +struct UsedDepStruct { + uint32_t x; +}; + +/** + * Not used by any exported item, but declared by the binding crate. + */ +struct UnusedLocalStruct { + uint32_t z; +}; + +uint32_t get_x(const struct UsedDepStruct *used); diff --git a/tests/expectations/include_all_deps_tag.compat.c b/tests/expectations/include_all_deps_tag.compat.c new file mode 100644 index 000000000..63f8eec18 --- /dev/null +++ b/tests/expectations/include_all_deps_tag.compat.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +/** + * Reachable from an exported function, so it is emitted even though its crate + * does not contribute top-level items. + */ +struct UsedDepStruct { + uint32_t x; +}; + +/** + * Not used by any exported item, but declared by the binding crate. + */ +struct UnusedLocalStruct { + uint32_t z; +}; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +uint32_t get_x(const struct UsedDepStruct *used); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/include_all_deps_tag.pyx b/tests/expectations/include_all_deps_tag.pyx new file mode 100644 index 000000000..0e7c3e845 --- /dev/null +++ b/tests/expectations/include_all_deps_tag.pyx @@ -0,0 +1,18 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + # Reachable from an exported function, so it is emitted even though its crate + # does not contribute top-level items. + cdef struct UsedDepStruct: + uint32_t x; + + # Not used by any exported item, but declared by the binding crate. + cdef struct UnusedLocalStruct: + uint32_t z; + + uint32_t get_x(const UsedDepStruct *used); diff --git a/tests/rust/include_all_deps/Cargo.lock b/tests/rust/include_all_deps/Cargo.lock new file mode 100644 index 000000000..d4d7981ad --- /dev/null +++ b/tests/rust/include_all_deps/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "include-all-deps" +version = "0.1.0" +dependencies = [ + "include-all-deps-dep", +] + +[[package]] +name = "include-all-deps-dep" +version = "0.1.0" diff --git a/tests/rust/include_all_deps/Cargo.toml b/tests/rust/include_all_deps/Cargo.toml new file mode 100644 index 000000000..ed71cc8f2 --- /dev/null +++ b/tests/rust/include_all_deps/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "include-all-deps" +version = "0.1.0" +authors = ["cbindgen"] +edition = "2018" + +[dependencies] +include-all-deps-dep = { path = "dep" } diff --git a/tests/rust/include_all_deps/cbindgen.toml b/tests/rust/include_all_deps/cbindgen.toml new file mode 100644 index 000000000..4959ed944 --- /dev/null +++ b/tests/rust/include_all_deps/cbindgen.toml @@ -0,0 +1,6 @@ +[parse] +parse_deps = true +include = ["include-all-deps-dep"] + +[export] +include_all = true diff --git a/tests/rust/include_all_deps/dep/Cargo.toml b/tests/rust/include_all_deps/dep/Cargo.toml new file mode 100644 index 000000000..eed5a3308 --- /dev/null +++ b/tests/rust/include_all_deps/dep/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "include-all-deps-dep" +version = "0.1.0" +authors = ["cbindgen"] +edition = "2018" + +[dependencies] diff --git a/tests/rust/include_all_deps/dep/src/lib.rs b/tests/rust/include_all_deps/dep/src/lib.rs new file mode 100644 index 000000000..4214c995a --- /dev/null +++ b/tests/rust/include_all_deps/dep/src/lib.rs @@ -0,0 +1,19 @@ +/// Reachable from an exported function, so it is emitted even though its crate +/// does not contribute top-level items. +#[repr(C)] +pub struct UsedDepStruct { + pub x: u32, +} + +/// Not reachable, and its crate is not listed in `parse.extra_bindings`, so +/// `export.include_all` must not pick it up. +#[repr(C)] +pub struct UnusedDepStruct { + pub y: u32, +} + +/// Same gate as the types above: never emitted from a non-binding crate. +#[no_mangle] +pub extern "C" fn dep_only_fn() -> u32 { + 0 +} diff --git a/tests/rust/include_all_deps/src/lib.rs b/tests/rust/include_all_deps/src/lib.rs new file mode 100644 index 000000000..065139b1d --- /dev/null +++ b/tests/rust/include_all_deps/src/lib.rs @@ -0,0 +1,12 @@ +use include_all_deps_dep::UsedDepStruct; + +/// Not used by any exported item, but declared by the binding crate. +#[repr(C)] +pub struct UnusedLocalStruct { + pub z: u32, +} + +#[no_mangle] +pub unsafe extern "C" fn get_x(used: *const UsedDepStruct) -> u32 { + (*used).x +}