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 Cython bindings #590

Merged
merged 1 commit into from
Nov 17, 2020
Merged
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
10 changes: 10 additions & 0 deletions .github/workflows/cbindgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ jobs:
toolchain: nightly
override: true

- name: Install Python
uses: actions/setup-python@v2
with:
python-version: '3.8.*'

- name: Install Cython
run: |
python -m pip install --upgrade pip wheel
pip install Cython==0.29.*

- name: Build
run: |
cargo build --verbose
Expand Down
3 changes: 3 additions & 0 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Don't worry about requesting code review, as there is nothing formally setup for

There is continuous integration setup for `cbindgen` using [travis](https://travis-ci.org/). It automatically runs `cargo test` which runs `cbindgen` against a series of rust files from `tests/rust/` and checks that the output compiles using `gcc` or `g++`.

In addition to a C/C++ compiler `cargo test` requires Python and Cython
(`python -m pip install Cython`) for checking Cython bindings generated from tests (`.pyx` files).

Please run `cargo test` before filing a pull request to be sure that all tests pass. This will also update the test expectations.

Rustfmt is also enforced by travis. To format your code install `rustfmt-preview` using `rustup component add rustfmt-preview` and then `cargo fmt`. Travis runs with rust nightly, so use `rustup run nightly -- cargo fmt` to guarantee consistent results.
Expand Down
6 changes: 4 additions & 2 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ Then all you need to do is run it:
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.
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.

See `cbindgen --help` for more options.

Expand Down Expand Up @@ -372,7 +374,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++"
# possible values: "C", "C++", "Cython"
#
# default: "C++"
language = "C"
Expand Down
112 changes: 76 additions & 36 deletions src/bindgen/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,14 @@ impl Bindings {
write!(out, "{}", f);
out.new_line();
}
if let Some(ref f) = self.config.include_guard {
if let Some(f) = self.config.include_guard() {
out.new_line_if_not_start();
write!(out, "#ifndef {}", f);
out.new_line();
write!(out, "#define {}", f);
out.new_line();
}
if self.config.pragma_once {
if self.config.pragma_once && self.config.language != Language::Cython {
out.new_line_if_not_start();
write!(out, "#pragma once");
out.new_line();
Expand All @@ -154,8 +154,8 @@ impl Bindings {
}

if self.config.no_includes
&& self.config.sys_includes.is_empty()
&& self.config.includes.is_empty()
&& self.config.sys_includes().is_empty()
&& self.config.includes().is_empty()
&& self.config.after_includes.is_none()
{
return;
Expand All @@ -164,50 +164,70 @@ impl Bindings {
out.new_line_if_not_start();

if !self.config.no_includes {
if self.config.language == Language::C {
out.write("#include <stdarg.h>");
out.new_line();
out.write("#include <stdbool.h>");
out.new_line();
if self.config.usize_is_size_t {
out.write("#include <stddef.h>");
match self.config.language {
Language::C => {
out.write("#include <stdarg.h>");
out.new_line();
out.write("#include <stdbool.h>");
out.new_line();
if self.config.usize_is_size_t {
out.write("#include <stddef.h>");
out.new_line();
}
out.write("#include <stdint.h>");
out.new_line();
out.write("#include <stdlib.h>");
out.new_line();
}
out.write("#include <stdint.h>");
out.new_line();
out.write("#include <stdlib.h>");
out.new_line();
} else {
out.write("#include <cstdarg>");
out.new_line();
if self.config.usize_is_size_t {
out.write("#include <cstddef>");
Language::Cxx => {
out.write("#include <cstdarg>");
out.new_line();
if self.config.usize_is_size_t {
out.write("#include <cstddef>");
out.new_line();
}
out.write("#include <cstdint>");
out.new_line();
out.write("#include <cstdlib>");
out.new_line();
out.write("#include <ostream>");
out.new_line();
out.write("#include <new>");
out.new_line();
if self.config.enumeration.cast_assert_name.is_none()
&& (self.config.enumeration.derive_mut_casts
|| self.config.enumeration.derive_const_casts)
{
out.write("#include <cassert>");
out.new_line();
}
}
out.write("#include <cstdint>");
out.new_line();
out.write("#include <cstdlib>");
out.new_line();
out.write("#include <ostream>");
out.new_line();
out.write("#include <new>");
out.new_line();
if self.config.enumeration.cast_assert_name.is_none()
&& (self.config.enumeration.derive_mut_casts
|| self.config.enumeration.derive_const_casts)
{
out.write("#include <cassert>");
Language::Cython => {
out.write(
"from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t",
);
out.new_line();
out.write(
"from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t",
);
out.new_line();
out.write("cdef extern from *");
out.open_brace();
out.write("ctypedef bint bool");
out.new_line();
out.write("ctypedef struct va_list");
out.new_line();
out.close_brace(false);
}
}
}

for include in &self.config.sys_includes {
for include in self.config.sys_includes() {
write!(out, "#include <{}>", include);
out.new_line();
}

for include in &self.config.includes {
for include in self.config.includes() {
write!(out, "#include \"{}\"", include);
out.new_line();
}
Expand Down Expand Up @@ -320,9 +340,18 @@ impl Bindings {
}
}

if self.config.language == Language::Cython
&& self.globals.is_empty()
&& self.constants.is_empty()
&& self.items.is_empty()
&& self.functions.is_empty()
{
out.write("pass");
}

self.close_namespaces(&mut out);

if let Some(ref f) = self.config.include_guard {
if let Some(f) = self.config.include_guard() {
out.new_line_if_not_start();
if self.config.language == Language::C {
write!(out, "#endif /* {} */", f);
Expand Down Expand Up @@ -357,6 +386,17 @@ impl Bindings {
}

fn open_close_namespaces<F: Write>(&self, op: NamespaceOperation, out: &mut SourceWriter<F>) {
if self.config.language == Language::Cython {
if op == NamespaceOperation::Open {
out.new_line();
out.write("cdef extern from *");
out.open_brace();
} else {
out.close_brace(false);
}
return;
}

let mut namespaces = self.all_namespaces();
if namespaces.is_empty() {
return;
Expand Down
8 changes: 5 additions & 3 deletions src/bindgen/cdecl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ impl CDecl {
write!(out, "{} ", self.type_qualifers);
}

if let Some(ref ctype) = self.type_ctype {
write!(out, "{} ", ctype.to_str());
if config.language != Language::Cython {
if let Some(ref ctype) = self.type_ctype {
write!(out, "{} ", ctype.to_str());
}
}

write!(out, "{}", self.type_name);
Expand Down Expand Up @@ -214,7 +216,7 @@ impl CDecl {
if is_const {
out.write("const ");
}
if !is_nullable && !is_ref {
if !is_nullable && !is_ref && config.language != Language::Cython {
if let Some(attr) = &config.pointer.non_null_attribute {
write!(out, "{} ", attr);
}
Expand Down
45 changes: 45 additions & 0 deletions src/bindgen/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub enum Language {
Cxx,
C,
Cython,
}

impl FromStr for Language {
Expand All @@ -39,13 +40,24 @@ impl FromStr for Language {
"C++" => Ok(Language::Cxx),
"c" => Ok(Language::C),
"C" => Ok(Language::C),
"cython" => Ok(Language::Cython),
"Cython" => Ok(Language::Cython),
_ => Err(format!("Unrecognized Language: '{}'.", s)),
}
}
}

deserialize_enum_str!(Language);

impl Language {
pub(crate) fn typedef(self) -> &'static str {
match self {
Language::Cxx | Language::C => "typedef",
Language::Cython => "ctypedef",
}
}
}

/// Controls what type of line endings are used in the generated code.
#[derive(Debug, Clone, Copy)]
pub enum LineEndingStyle {
Expand Down Expand Up @@ -200,6 +212,15 @@ impl Style {
Style::Tag => false,
}
}

// https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#styles-of-struct-union-and-enum-declaration
pub fn cython_def(self) -> &'static str {
if self.generate_tag() {
"cdef "
} else {
"ctypedef "
}
}
}

impl FromStr for Style {
Expand Down Expand Up @@ -945,6 +966,30 @@ impl Config {
self.language == Language::C && self.cpp_compat
}

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

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

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

pub fn from_file<P: AsRef<StdPath>>(file_name: P) -> Result<Config, String> {
let config_text = fs::read_to_string(file_name.as_ref()).map_err(|_| {
format!(
Expand Down
5 changes: 5 additions & 0 deletions src/bindgen/ir/annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::str::FromStr;

use crate::bindgen::config::{Config, Language};
use crate::bindgen::utilities::SynAttributeHelpers;

// A system for specifying properties on items. Annotations are
Expand Down Expand Up @@ -48,6 +49,10 @@ impl AnnotationSet {
self.annotations.is_empty() && !self.must_use
}

pub(crate) fn must_use(&self, config: &Config) -> bool {
self.must_use && config.language != Language::Cython
}

pub fn load(attrs: &[syn::Attribute]) -> Result<AnnotationSet, String> {
let lines = attrs.get_comment_lines();
let lines: Vec<&str> = lines
Expand Down
Loading