Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generation of Zig bindings #732

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .github/workflows/cbindgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ jobs:
python -m pip install --upgrade pip wheel
pip install Cython==3.0.2

- name: Install Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: master

- name: Build
run: |
cargo +stable build --verbose
Expand Down
6 changes: 3 additions & 3 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ cbindgen --config cbindgen.toml --crate my_rust_library --output my_header.h
```

This produces a header file for C++. For C, add the `--lang c` switch. \
`cbindgen` also supports generation of [Cython](https://cython.org) bindings,
use `--lang cython` for that.
`cbindgen` also supports generation of [Cython](https://cython.org) and [Zig](https://ziglang.org) bindings,
use `--lang cython` or `--lang zig` for that.

See `cbindgen --help` for more options.

Expand Down Expand Up @@ -419,7 +419,7 @@ Note that many options defined here only apply for one of C or C++. Usually it's
```toml
# The language to output bindings in
#
# possible values: "C", "C++", "Cython"
# possible values: "C", "C++", "Cython", "Zig"
#
# default: "C++"
language = "C"
Expand Down
5 changes: 4 additions & 1 deletion src/bindgen/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::bindgen::ir::{
Constant, Function, ItemContainer, ItemMap, Path as BindgenPath, Static, Struct, Type, Typedef,
};
use crate::bindgen::language_backend::{
CLikeLanguageBackend, CythonLanguageBackend, LanguageBackend,
CLikeLanguageBackend, CythonLanguageBackend, ZigLanguageBackend, LanguageBackend,
};
use crate::bindgen::writer::SourceWriter;

Expand Down Expand Up @@ -210,6 +210,9 @@ impl Bindings {
Language::Cython => {
self.write_with_backend(file, &mut CythonLanguageBackend::new(&self.config))
}
Language::Zig => {
self.write_with_backend(file, &mut ZigLanguageBackend::new(&self.config))
}
}
}

Expand Down
98 changes: 87 additions & 11 deletions src/bindgen/cdecl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ impl CDecl {
"error generating cdecl for {:?}",
t
);
"const".clone_into(&mut self.type_qualifers);
if config.language != Language::Zig {
"const".clone_into(&mut self.type_qualifers);
}
}

assert!(
Expand All @@ -139,15 +141,22 @@ impl CDecl {
"error generating cdecl for {:?}",
t
);
"const".clone_into(&mut self.type_qualifers);
if config.language != Language::Zig {
"const".clone_into(&mut self.type_qualifers);
}
}

assert!(
self.type_name.is_empty(),
"error generating cdecl for {:?}",
t
);
self.type_name = p.to_repr_c(config).to_string();

if config.language == Language::Zig {
self.type_name = p.to_repr_zig().to_string();
} else {
self.type_name = p.to_repr_c(config).to_string();
}
}
Type::Ptr {
ref ty,
Expand Down Expand Up @@ -210,7 +219,9 @@ impl CDecl {
}
}

write!(out, "{}", self.type_name);
if config.language != Language::Zig {
write!(out, "{}", self.type_name);
}

if !self.type_generic_args.is_empty() {
out.write("<");
Expand All @@ -228,11 +239,16 @@ impl CDecl {

// When we have an identifier, put a space between the type and the declarators
if ident.is_some() {
out.write(" ");
if config.language == Language::Zig {
out.write("");
} else {
out.write(" ");
}
}

// Write the left part of declarators before the identifier
let mut iter_rev = self.declarators.iter().rev().peekable();
let mut is_functors = false;
kassane marked this conversation as resolved.
Show resolved Hide resolved

#[allow(clippy::while_let_on_iterator)]
while let Some(declarator) = iter_rev.next() {
Expand All @@ -244,9 +260,26 @@ impl CDecl {
is_nullable,
is_ref,
} => {
out.write(if is_ref { "&" } else { "*" });
if config.language != Language::Zig {
out.write(if is_ref { "&" } else { "*" });
} else {
if !self.type_qualifers.is_empty() {
write!(out, "{}", self.type_qualifers);
} else {
if ident.is_none() && config.language == Language::Zig {
out.write("_");
}
if config.language != Language::Zig {
out.write("_");
}
}
}
if is_const {
out.write("const ");
if config.language == Language::Zig {
write!(out, "{} ", config.style.zig_def());
} else {
out.write("const ");
}
}
if !is_nullable && !is_ref && config.language != Language::Cython {
if let Some(attr) = &config.pointer.non_null_attribute {
Expand All @@ -260,16 +293,28 @@ impl CDecl {
}
}
CDeclarator::Func { .. } => {
if next_is_pointer {
if next_is_pointer && config.language != Language::Zig {
out.write("(");
}
if !next_is_pointer && config.language == Language::Zig {
out.write("extern fn ");
}
is_functors = true;
}
}
}

// Write the identifier
if let Some(ident) = ident {
write!(out, "{}", ident);
if config.language == Language::Zig && self.declarators.is_empty() {
if ident.is_empty() {
write!(out, "{}", self.type_name);
} else {
write!(out, "{}: {}", ident, self.type_name);
}
} else {
write!(out, "{}", ident);
}
}

// Write the right part of declarators after the identifier
Expand All @@ -281,12 +326,34 @@ impl CDecl {
match *declarator {
CDeclarator::Ptr { .. } => {
last_was_pointer = true;

if config.language == Language::Zig {
if self.type_name.contains("u8") || self.type_name.contains("const u8") {
write!(out, ": ?[*:0]const {}", self.type_name);
} else if is_functors {
out.write(": ?fn");
} else {
write!(out, ": ?*{}", self.type_name);
}
}
}
CDeclarator::Array(ref constant) => {
if last_was_pointer {
out.write(")");
}
write!(out, "[{}]", constant);
if config.language == Language::Zig {
if constant.is_empty() {
write!(out, "{}: [*]{}", self.type_qualifers, self.type_name);
} else {
write!(
out,
"{}: [{}]{}",
self.type_qualifers, constant, self.type_name
);
}
} else {
write!(out, "[{}]", constant);
}

last_was_pointer = false;
}
Expand All @@ -295,9 +362,10 @@ impl CDecl {
ref layout,
never_return,
} => {
if last_was_pointer {
if last_was_pointer && config.language != Language::Zig {
out.write(")");
}
is_functors = false;

out.write("(");
if args.is_empty() && config.language == Language::C {
Expand Down Expand Up @@ -364,6 +432,14 @@ impl CDecl {
}
}

if config.language == Language::Zig {
if !last_was_pointer && self.type_name.contains("anyopaque") {
out.write(" callconv(.C) void")
} else {
write!(out, " {}", self.type_name);
}
}

last_was_pointer = true;
}
}
Expand Down
35 changes: 32 additions & 3 deletions src/bindgen/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub enum Language {
Cxx,
C,
Cython,
Zig,
}

impl FromStr for Language {
Expand All @@ -42,6 +43,8 @@ impl FromStr for Language {
"C" => Ok(Language::C),
"cython" => Ok(Language::Cython),
"Cython" => Ok(Language::Cython),
"zig" => Ok(Language::Zig),
"Zig" => Ok(Language::Zig),
_ => Err(format!("Unrecognized Language: '{}'.", s)),
}
}
Expand All @@ -54,6 +57,7 @@ impl Language {
match self {
Language::Cxx | Language::C => "typedef",
Language::Cython => "ctypedef",
Language::Zig => "pub const",
}
}
}
Expand Down Expand Up @@ -167,6 +171,7 @@ pub enum DocumentationStyle {
Doxy,
Cxx,
Auto,
Zig,
}

impl FromStr for DocumentationStyle {
Expand All @@ -180,6 +185,7 @@ impl FromStr for DocumentationStyle {
"c++" => Ok(DocumentationStyle::Cxx),
"doxy" => Ok(DocumentationStyle::Doxy),
"auto" => Ok(DocumentationStyle::Auto),
"zig" => Ok(DocumentationStyle::Zig),
_ => Err(format!("Unrecognized documentation style: '{}'.", s)),
}
}
Expand Down Expand Up @@ -240,6 +246,14 @@ impl Style {
"ctypedef "
}
}

pub fn zig_def(self) -> &'static str {
if self.generate_tag() {
"pub const "
} else {
"pub extern"
}
}
}

impl FromStr for Style {
Expand Down Expand Up @@ -711,6 +725,8 @@ pub struct ConstantConfig {
pub allow_static_const: bool,
/// Whether a generated constant should be constexpr in C++ mode.
pub allow_constexpr: bool,
/// Whether a generated compile-time should be comptime in Zig mode.
pub allow_comptime: bool,
/// Sort key for constants
pub sort_by: Option<SortKey>,
}
Expand All @@ -720,6 +736,7 @@ impl Default for ConstantConfig {
ConstantConfig {
allow_static_const: true,
allow_constexpr: true,
allow_comptime: true,
sort_by: None,
}
}
Expand Down Expand Up @@ -894,6 +911,15 @@ pub struct CythonConfig {
pub cimports: BTreeMap<String, Vec<String>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct ZigConfig {
pub header: Option<String>,
pub cimports: BTreeMap<String, Vec<String>>,
}

/// A collection of settings to customize the generated bindings.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -1018,6 +1044,8 @@ pub struct Config {
pub only_target_dependencies: bool,
/// Configuration options specific to Cython.
pub cython: CythonConfig,
/// Configuration options specific to Zig.
pub zig: ZigConfig,
#[doc(hidden)]
#[serde(skip)]
/// Internal field for tracking from which file the config was loaded.
Expand Down Expand Up @@ -1070,6 +1098,7 @@ impl Default for Config {
pointer: PtrConfig::default(),
only_target_dependencies: false,
cython: CythonConfig::default(),
zig: ZigConfig::default(),
config_path: None,
}
}
Expand All @@ -1081,23 +1110,23 @@ impl Config {
}

pub(crate) fn include_guard(&self) -> Option<&str> {
if self.language == Language::Cython {
if self.language == Language::Cython || self.language == Language::Zig {
None
} else {
self.include_guard.as_deref()
}
}

pub(crate) fn includes(&self) -> &[String] {
if self.language == Language::Cython {
if self.language == Language::Cython || self.language == Language::Zig {
&[]
} else {
&self.includes
}
}

pub(crate) fn sys_includes(&self) -> &[String] {
if self.language == Language::Cython {
if self.language == Language::Cython || self.language == Language::Zig {
&[]
} else {
&self.sys_includes
Expand Down
6 changes: 6 additions & 0 deletions src/bindgen/ir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,12 @@ impl Constant {
write!(out, " {} # = ", name);
language_backend.write_literal(out, value);
}
Language::Zig => {
out.write(config.style.zig_def());
write!(out, "{} = ", name);
language_backend.write_literal(out, value);
out.write(";");
}
}

condition.write_after(config, out);
Expand Down
Loading