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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ path = "src/lib.rs"

[workspace]
exclude = [
"cbindgen-macro",
"tests/depfile/single_crate_config",
"tests/depfile/single_crate_default_config",
"tests/depfile/single_crate",
Expand Down
47 changes: 47 additions & 0 deletions cbindgen-macro/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions cbindgen-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "cbindgen-macro"
version = "0.1.0"
edition = "2021"
description = "Procedural macro attributes for cbindgen"
license = "MPL-2.0"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1"
syn = { version = "2.0", features = ["full"] }
59 changes: 59 additions & 0 deletions cbindgen-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Procedural macro attributes for cbindgen.
//!
//! This crate provides the `#[cbindgen_macro::namespace()]` attribute that can be used
//! to specify C++ namespaces for individual functions in generated headers.
//!
//! # Example
//!
//! ```rust,ignore
//! use cbindgen_macro::namespace;
//!
//! #[namespace("ffi::bar")]
//! #[no_mangle]
//! pub extern "C" fn foo() {}
//! ```
//!
//! The attribute itself is a no-op at compile time - it simply passes through
//! the item unchanged. However, cbindgen parses this attribute from the source
//! code to determine the C++ namespace for the function in the generated header.

use proc_macro::TokenStream;

/// Specifies a C++ namespace for a function in cbindgen-generated headers.
///
/// This attribute is a no-op at compile time but is parsed by cbindgen to
/// determine where to place the function declaration in the generated C++ header.
///
/// # Example
///
/// ```rust,ignore
/// #[cbindgen_macro::namespace("ffi::bar")]
/// #[no_mangle]
/// pub extern "C" fn foo() {}
/// ```
///
/// This will generate the following C++ code:
///
/// ```cpp
/// extern "C" {
///
/// namespace ffi {
/// namespace bar {
///
/// void foo();
///
/// } // namespace bar
/// } // namespace ffi
///
/// } // extern "C"
/// ```
#[proc_macro_attribute]
pub fn namespace(_attr: TokenStream, item: TokenStream) -> TokenStream {
// This is a no-op - we just pass through the item unchanged.
// cbindgen parses the attribute directly from the source code.
item
}
43 changes: 43 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,49 @@ arg: *const T --> const T arg[]
arg: *mut T --> T arg[]
```

### Per-Item Namespace Attribute (C++ only)

You can use the `#[cbindgen_macro::namespace("...")]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces.

First, add the `cbindgen-macro` crate to your dependencies:

```toml
[dependencies]
cbindgen-macro = { path = "path/to/cbindgen/cbindgen-macro" }
```

Then use the attribute on your functions:

```rust
#[cbindgen_macro::namespace("ffi::bar")]
#[no_mangle]
pub extern "C" fn foo(a: *const c_char) {}
```

This will generate the following C++ code:

```cpp
extern "C" {

namespace ffi {
namespace bar {

void foo(const char *a);

} // namespace bar
} // namespace ffi

} // extern "C"
```

**Key points:**

* Use `::` as the namespace separator (e.g., `"ffi::bar"` becomes nested namespaces `namespace ffi { namespace bar { ... } }`)
* This attribute only affects C++ output; it is ignored for C and Cython output
* Functions with the same namespace path will be grouped together in the generated header
* If both a global `namespace` in `cbindgen.toml` and a per-item namespace attribute are specified, the per-item namespace is nested inside the global namespace
* Functions without this attribute will use the global namespace (if configured) or no namespace

## Generating Swift Bindings

In addition to parsing function names in C/C++ header files, the Swift compiler can make use of the `swift_name` attribute on functions to generate more idiomatic names for imported functions and methods.
Expand Down
9 changes: 8 additions & 1 deletion src/bindgen/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::bindgen::library::Library;
use crate::bindgen::monomorph::Monomorphs;
use crate::bindgen::rename::{IdentifierType, RenameRule};
use crate::bindgen::reserved;
use crate::bindgen::utilities::IterHelpers;
use crate::bindgen::utilities::{IterHelpers, SynAttributeHelpers};

#[derive(Debug, Clone)]
pub struct FunctionArgument {
Expand All @@ -36,6 +36,9 @@ pub struct Function {
pub annotations: AnnotationSet,
pub documentation: Documentation,
pub never_return: bool,
/// Per-item C++ namespace path from `#[cbindgen::namespace = "..."]` attribute.
/// For example, `["ffi", "bar"]` for `#[cbindgen::namespace = "ffi::bar"]`.
pub namespace: Option<Vec<String>>,
}

impl Function {
Expand Down Expand Up @@ -65,6 +68,9 @@ impl Function {
ret.replace_self_with(self_path);
}

// Parse the #[cbindgen::namespace = "..."] attribute
let namespace = attrs.get_cbindgen_namespace();

Ok(Function {
path,
self_type_path: self_type_path.cloned(),
Expand All @@ -75,6 +81,7 @@ impl Function {
annotations: AnnotationSet::load(attrs)?,
documentation: Documentation::load(attrs),
never_return,
namespace,
})
}

Expand Down
82 changes: 79 additions & 3 deletions src/bindgen/language_backend/clike.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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,
Field, Function, GenericParams, Item, 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::BTreeMap;
use std::io::Write;

pub struct CLikeLanguageBackend<'a> {
Expand Down Expand Up @@ -116,6 +117,81 @@ impl<'a> CLikeLanguageBackend<'a> {
self.config.language == Language::C && self.config.style.generate_typedef()
}

/// Writes functions, grouping those with per-item namespace attributes into
/// nested namespace blocks (for C++ output only).
fn write_functions_with_namespaces<W: Write>(
&mut self,
out: &mut SourceWriter<W>,
b: &Bindings,
) where
Self: LanguageBackend,
{
// Only apply per-item namespace grouping for C++ output
if b.config.language != Language::Cxx {
self.write_functions_default(out, b);
return;
}

// Group functions by their per-item namespace
// Functions without a namespace go to the `None` key
let mut grouped: BTreeMap<Option<Vec<String>>, Vec<&Function>> = BTreeMap::new();

for function in &b.functions {
if !function.annotations.should_export() {
continue;
}
grouped
.entry(function.namespace.clone())
.or_default()
.push(function);
}

// Write functions without per-item namespace first (they use global namespace)
if let Some(functions) = grouped.remove(&None) {
for function in functions {
out.new_line_if_not_start();
self.write_function(&b.config, out, function);
out.new_line();
}
}

// Write functions with per-item namespaces
for (namespace, functions) in grouped {
if let Some(ns_parts) = namespace {
if ns_parts.is_empty() {
// Empty namespace means no namespace wrapping
for function in functions {
out.new_line_if_not_start();
self.write_function(&b.config, out, function);
out.new_line();
}
} else {
// Open nested namespaces
out.new_line_if_not_start();
for ns in &ns_parts {
out.new_line();
write!(out, "namespace {ns} {{");
}
out.new_line();

// Write functions inside the namespace
for function in functions {
out.new_line_if_not_start();
self.write_function(&b.config, out, function);
out.new_line();
}

// Close nested namespaces in reverse order
for ns in ns_parts.iter().rev() {
out.new_line();
write!(out, "}} // namespace {ns}");
}
out.new_line();
}
}
}
}

fn write_derived_cpp_ops<W: Write>(&mut self, out: &mut SourceWriter<W>, s: &Struct) {
let mut wrote_start_newline = false;

Expand Down Expand Up @@ -990,7 +1066,7 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
// Override default method to close various blocks containing both globals and functions
// these blocks are opened in [`write_globals`] that is also overridden
if !b.functions.is_empty() || !b.globals.is_empty() {
self.write_functions_default(out, b);
self.write_functions_with_namespaces(out, b);

if b.config.cpp_compatible_c() {
out.new_line();
Expand Down
36 changes: 36 additions & 0 deletions src/bindgen/utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,42 @@ pub trait SynAttributeHelpers {
.next()
}

/// Looks up the `#[cbindgen::namespace("...")]` or `#[cbindgen_macro::namespace("...")]`
/// attribute and parses it into a vector of namespace segments.
/// Supports `::` as the separator.
/// For example, `"ffi::bar"` becomes `["ffi", "bar"]`.
fn get_cbindgen_namespace(&self) -> Option<Vec<String>> {
for attr in self.attrs() {
// Handle #[cbindgen_macro::namespace("...")] or #[cbindgen::namespace("...")] syntax
if let syn::Meta::List(syn::MetaList { path, tokens, .. }) = &attr.meta {
if path.segments.len() == 2 {
let first = &path.segments[0];
let second = &path.segments[1];
let first_ident = first.ident.to_string();
if (first_ident == "cbindgen" || first_ident == "cbindgen_macro")
&& second.ident == "namespace"
{
// Parse the tokens inside the parentheses as a string literal
let tokens_str = tokens.to_string();
// Remove surrounding quotes if present
let value = tokens_str.trim_matches('"').to_string();
if value.is_empty() {
return Some(Vec::new());
}
// Split by "::" separator
let segments: Vec<String> = value
.split("::")
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
return Some(segments);
}
}
}
}
None
}

fn get_comment_lines(&self) -> Vec<String> {
let mut comment = Vec::new();

Expand Down
7 changes: 7 additions & 0 deletions tests/expectations-symbols/namespace_attr.c.sym
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
global_function;
ffi_function;
nested_function;
another_nested_function;
other_namespace_function;
};
5 changes: 5 additions & 0 deletions tests/expectations-symbols/namespace_attr_precedence.c.sym
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
uses_global_namespace;
uses_item_namespace;
also_uses_global_namespace;
};
Loading