Skip to content
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
7 changes: 7 additions & 0 deletions .plzconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ PleaseRustTool = //tools/please_rust:bootstrap
; names cannot collide with a consumer's third_party/rust ones
CriterionDep = //third_party/crates:criterion
BindgenTool = ///third_party/crates/bindgen_cli//:bindgen_bin
CbindgenTool = ///third_party/crates/cbindgen//:cbindgen_bin
Rustc = //third_party/rust:toolchain_rustc|rustc
; Exercise the pipelined shape in this repo; consumers default to off
PipelinedCompilation = true
Expand Down Expand Up @@ -129,6 +130,12 @@ DefaultValue = ///third_party/crates/bindgen_cli//:bindgen_bin
Help = Build label of the bindgen binary used by rust_bindgen. Built from crates via rust_repo (bindgen-cli).
Inherit = true

[PluginConfig "cbindgen_tool"]
ConfigKey = CbindgenTool
DefaultValue = ///third_party/crates/cbindgen//:cbindgen_bin
Help = Build label of the cbindgen binary used by rust_cbindgen. Built from crates via rust_repo (cbindgen), the same way bindgen is.
Inherit = true

[PluginConfig "libclang_path"]
ConfigKey = LibclangPath
DefaultValue =
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,26 @@ rust_library(
)
```

The reverse, a C header generated from Rust so C can call into a
`staticlib` or `cdylib`, comes from `rust_cbindgen`. cbindgen is declared the
same way (`lock --add cbindgen`), and parses the source rather than compiling
it, so the rule needs no toolchain:
```python
rust_cbindgen(
name = "ffi_header", # generates ffi_header.h
root = "src/lib.rs", # .hpp with lang = "c++"
)

c_binary(
name = "uses_rust",
srcs = ["main.c"],
hdrs = [":ffi_header"],
deps = [":rust_ffi"],
)
```
A signature that changes on the Rust side then stops the C compiling, rather
than compiling and crashing.

Protobuf and gRPC codegen live in
[rust-proto-rules](https://github.com/becomeliminal/rust-proto-rules), a
separate plugin that pins these rules by tag and plugs into the proto
Expand Down
50 changes: 50 additions & 0 deletions build_defs/rust.build_defs
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,56 @@ def rust_bindgen(name:str, header:str, srcs:list=[], clang_args:list=[], bindgen
)


def rust_cbindgen(name:str, root:str, srcs:list=[], lang:str="c", config:str="", crate_name:str="",
cbindgen_flags:list=[], visibility:list=None):
"""Generates a C or C++ header from Rust, the reverse of rust_bindgen.

C code calling into a `staticlib` or `cdylib` needs declarations for what
it is calling. Hand-writing them means nothing checks them against the
Rust definitions, and a signature that changes on one side goes on
compiling on the other until it crashes.

The output is a single header, usable as a `hdrs` entry of a c_library or
included directly by a c_binary's sources.

Args:
name (str): Name of the rule; the output is <name>.h, or .hpp for C++.
root (str): The crate's root module, the same file a rust_library
would take as its root.
srcs (list): The rest of the crate's sources, when the root declares
modules. cbindgen follows `mod` from the root, so a
crate of several files needs them staged.
lang (str): Output language: 'c', 'c++' or 'cython'.
config (str): A cbindgen.toml, for anything the flags do not cover.
crate_name (str): Crate name recorded in the header's include guard.
Defaults to the rule name.
cbindgen_flags (list): Extra flags passed to cbindgen.
visibility (list): Visibility declaration.
"""
ext = "hpp" if lang == "c++" else ("pyx" if lang == "cython" else "h")
flags = " ".join(cbindgen_flags)
cfg = " --config $SRCS_CONFIG" if config else ""
crate_flag = f" --crate {crate_name}" if crate_name else ""
return build_rule(
name = name,
srcs = {
"root": [root],
"mods": srcs,
"config": [config] if config else [],
},
outs = [f"{name}.{ext}"],
# cbindgen parses the source rather than compiling it, so it needs no
# toolchain and no dependencies: a crate's public extern "C" surface
# is decided by its own text.
cmd = f"$TOOLS_CBINDGEN --lang {lang}{cfg}{crate_flag} -o $OUT {flags} $SRCS_ROOT",
tools = {
"cbindgen": [CONFIG.RUST.CBINDGEN_TOOL],
},
visibility = visibility,
labels = ["rust", "codegen"],
)


def rust_crate_download(name:str, crate:str, version:str, hashes:list=None, labels:list=[], visibility:list=None):
"""Downloads a crate from crates.io.

Expand Down
3 changes: 1 addition & 2 deletions docs/COMPARISON.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ rather than by release:
- **Missing capability:**
[#23](https://github.com/becomeliminal/rust-rules/issues/23) cross-compiling C,
[#24](https://github.com/becomeliminal/rust-rules/issues/24) channels,
[#25](https://github.com/becomeliminal/rust-rules/issues/25) cbindgen,
[#26](https://github.com/becomeliminal/rust-rules/issues/26) wasm-bindgen,
[#27](https://github.com/becomeliminal/rust-rules/issues/27) multi-platform locks,
[#28](https://github.com/becomeliminal/rust-rules/issues/28) bench profile,
Expand Down Expand Up @@ -120,7 +119,7 @@ rather than by release:
| **Documentation**<br>rustdoc HTML | **yes**. rust_doc | **yes**. rust_doc | **yes**. cargo doc |
| **Coverage**<br>Line coverage from instrumented tests | **yes**. -C instrument-coverage into plz cover | **yes**. Supported | **partial**. External tooling, llvm-cov |
| **C header bindings**<br>bindgen | **yes**. rust_bindgen, tool built from declared crates | **yes**. rust_bindgen | **partial**. build.rs calling bindgen |
| **Rust to C headers**<br>cbindgen | **no**. Open | **yes**. Supported | **partial**. build.rs calling cbindgen |
| **Rust to C headers**<br>cbindgen | **yes**. rust_cbindgen, tool built from declared crates | **no**. No cbindgen rule or extension | **partial**. cbindgen called from build.rs |
| **wasm-bindgen**<br>JS bindings | **no**. Groundwork only | **yes**. Supported | **partial**. External tool |
| **Publish to crates.io**<br>cargo publish | **no**. Deliberate non-goal | **no**. Not its job | **yes**. Native |

Expand Down
10 changes: 10 additions & 0 deletions test/cc_interop/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ rust_library(
edition = "2021",
)

# The header the C side includes is generated from the Rust source rather
# than hand-written, so a signature that changes on one side stops the other
# compiling instead of crashing at run time.
rust_cbindgen(
name = "ffi_header",
root = "rust_ffi_lib.rs",
crate_name = "ffi",
)

c_binary(
name = "uses_rust",
srcs = ["main.c"],
hdrs = [":ffi_header"],
deps = [":rust_ffi"],
)
4 changes: 3 additions & 1 deletion test/cc_interop/main.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <stdio.h>
int ffi_add(int a, int b);
// Generated by rust_cbindgen from rust_ffi_lib.rs. Declaring ffi_add by hand
// here is what this is replacing: nothing checked it against the Rust.
#include "ffi_header.h"
int main(void) {
printf("rust says %d\n", ffi_add(40, 2));
return 0;
Expand Down
Loading
Loading