From a3b05e074b4d65ab40e7c6b4cffbe56a5eeb0f97 Mon Sep 17 00:00:00 2001 From: Jack Marsh Date: Mon, 24 Aug 2026 14:13:15 +0100 Subject: [PATCH 1/4] Give the bare crate label to a root, not to whatever is newest A crate declared at two versions gets one bare label and one versioned one, and the bare label went to the newest. So a new major arriving as an indirect dependency of something unrelated takes the label, and every first-party rule naming //third_party/crates: moves a major version with nobody saying so. Adding cbindgen, which wants toml 0.9, did exactly that: please_rust asks for toml 0.8, the bare toml label became 0.9.12, and the tool linked against it. The symptom was one unrelated test failing to parse a lockfile, pointing nowhere near the cause. A root is what somebody declared and what rules depend on by name, so it keeps the bare label. Newest still wins between two roots, and when nothing is a root. --- tools/please_rust/src/sync.rs | 54 +++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/tools/please_rust/src/sync.rs b/tools/please_rust/src/sync.rs index 5ce7835..75d7116 100644 --- a/tools/please_rust/src/sync.rs +++ b/tools/please_rust/src/sync.rs @@ -883,8 +883,9 @@ fn import_cargo_lock(path: &Path, decls: &mut Vec) -> Result<()> { Ok(()) } -/// Newest declared version of a crate gets the plain normalized name; older -/// duplicates get `crate_norm-x.y.z`. Returns old->new subrepo renames. +/// A root declaration of a crate gets the plain normalized name, newest +/// first; everything else gets `crate_norm-x.y.z`. Returns old->new subrepo +/// renames. fn normalize_names(decls: &mut [Decl]) -> Result> { let mut by_crate: BTreeMap> = BTreeMap::new(); for (i, d) in decls.iter().enumerate() { @@ -900,7 +901,15 @@ fn normalize_names(decls: &mut [Decl]) -> Result> { .with_context(|| format!("Bad version {} for {}", decls[i].version, crate_name))?; versions.push((v, i)); } - versions.sort_by(|a, b| b.0.cmp(&a.0)); + // The bare name goes to a root before it goes to whatever is newest. + // A root is what somebody declared and what first-party rules name: + // //third_party/crates:toml. Handing the bare name to the newest + // version instead means a new major arriving as an indirect + // dependency of something unrelated takes the label, and every rule + // depending on it moves a major version without anyone saying so. + // Adding cbindgen, which wants toml 0.9, silently moved please_rust + // from the toml 0.8 it asks for. + versions.sort_by(|a, b| decls[b.1].root.cmp(&decls[a.1].root).then(b.0.cmp(&a.0))); for (rank, (_, i)) in versions.iter().enumerate() { let new_name = if rank == 0 { norm.clone() @@ -2062,6 +2071,45 @@ pub fn target_applies(target_cfg: &str, triple: &str) -> bool { mod tests { use super::*; + /// The bare label is what first-party rules name, so it has to keep + /// meaning the same crate. Adding cbindgen, which wants toml 0.9, handed + /// the bare toml label to that indirect entry and moved please_rust off + /// the toml 0.8 it asks for. The symptom was an unrelated test failing to + /// parse a lockfile. + #[test] + fn the_bare_name_goes_to_a_root_not_to_whatever_is_newest() { + let decl = |crate_name: &str, version: &str, root: bool| { + let mut d = parse( + "rust_repo(\n name = \"x\",\n crate = \"x\",\n version = \"1.0.0\",\n)\n", + ) + .remove(0); + d.crate_name = crate_name.to_string(); + d.version = version.to_string(); + d.root = root; + d.name = None; + d + }; + + // A root and a newer indirect: the root keeps the name it is + // depended on by. + let mut decls = vec![decl("toml", "0.8.23", true), decl("toml", "0.9.12", false)]; + normalize_names(&mut decls).unwrap(); + assert_eq!(decls[0].subrepo(), "toml"); + assert_eq!(decls[1].subrepo(), "toml-0.9.12"); + + // Two roots: the newest wins, as before. + let mut decls = vec![decl("serde", "1.0.0", true), decl("serde", "2.0.0", true)]; + normalize_names(&mut decls).unwrap(); + assert_eq!(decls[1].subrepo(), "serde"); + assert_eq!(decls[0].subrepo(), "serde-1.0.0"); + + // Nothing is a root: the newest wins, as before. + let mut decls = vec![decl("log", "0.4.1", false), decl("log", "0.4.9", false)]; + normalize_names(&mut decls).unwrap(); + assert_eq!(decls[1].subrepo(), "log"); + assert_eq!(decls[0].subrepo(), "log-0.4.1"); + } + fn parse(text: &str) -> Vec { let lines: Vec = text.lines().map(|s| s.to_string()).collect(); parse_build(&lines).unwrap() From 8337c060fc41d1cf9a659f4d6ca3742927ebd87e Mon Sep 17 00:00:00 2001 From: Jack Marsh Date: Mon, 24 Aug 2026 14:13:15 +0100 Subject: [PATCH 2/4] Generate a C header from Rust with rust_cbindgen rust_bindgen makes Rust bindings from a C header. The reverse had no rule, so C calling into a staticlib hand-wrote the declarations and nothing checked them against the Rust definitions. The same shape as rust_bindgen: cbindgen is a published crate, declared by rust_repo and aliased through CbindgenTool, so the supply chain stays in the graph. cbindgen parses the source rather than compiling it, so the rule needs no toolchain and no dependencies. test/cc_interop generates its header now rather than carrying one, and main.c includes it instead of declaring ffi_add by hand. Adding a parameter on the Rust side turns into main.c:6:30: error: too few arguments to function 'ffi_add' where before it compiled and crashed at run time. --- .plzconfig | 7 ++ build_defs/rust.build_defs | 50 +++++++++++++ test/cc_interop/BUILD | 10 +++ test/cc_interop/main.c | 4 +- third_party/crates/BUILD | 144 ++++++++++++++++++++++++++++++------- 5 files changed, 187 insertions(+), 28 deletions(-) diff --git a/.plzconfig b/.plzconfig index 353276b..903415c 100644 --- a/.plzconfig +++ b/.plzconfig @@ -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 @@ -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 = diff --git a/build_defs/rust.build_defs b/build_defs/rust.build_defs index 066e759..1ab47e9 100644 --- a/build_defs/rust.build_defs +++ b/build_defs/rust.build_defs @@ -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 .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. diff --git a/test/cc_interop/BUILD b/test/cc_interop/BUILD index e50928d..9bca75f 100644 --- a/test/cc_interop/BUILD +++ b/test/cc_interop/BUILD @@ -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"], ) diff --git a/test/cc_interop/main.c b/test/cc_interop/main.c index a50784e..e55c2b0 100644 --- a/test/cc_interop/main.c +++ b/test/cc_interop/main.c @@ -1,5 +1,7 @@ #include -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; diff --git a/third_party/crates/BUILD b/third_party/crates/BUILD index b280f44..4e812a6 100644 --- a/third_party/crates/BUILD +++ b/third_party/crates/BUILD @@ -141,7 +141,7 @@ rust_repo( ) rust_repo( - name = "itertools", + name = "itertools-0.12.1", crate = "itertools", version = "0.12.1", indirect = True, @@ -266,6 +266,7 @@ rust_repo( crate = "linux-raw-sys", version = "0.4.14", indirect = True, + platforms = ["linux"], ) @@ -347,7 +348,7 @@ rust_repo( ) rust_repo( - name = "heck", + name = "heck-0.5.0", crate = "heck", version = "0.5.0", hashes = ["2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"], @@ -362,9 +363,10 @@ rust_repo( ) rust_repo( - name = "log-0.4.21", + name = "log", crate = "log", - version = "0.4.21", + version = "0.4.33", + hashes = ["0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"], indirect = True, ) @@ -723,17 +725,19 @@ rust_repo( ) rust_repo( - name = "libsecp256k1_gen_ecmult", + name = "libsecp256k1_gen_ecmult-0.3.0", crate = "libsecp256k1-gen-ecmult", version = "0.3.0", indirect = True, + platforms = [], ) rust_repo( - name = "libsecp256k1_gen_genmult", + name = "libsecp256k1_gen_genmult-0.3.0", crate = "libsecp256k1-gen-genmult", version = "0.3.0", indirect = True, + platforms = [], ) rust_repo( @@ -1081,7 +1085,7 @@ rust_repo( ) rust_repo( - name = "serde_spanned", + name = "serde_spanned-0.6.9", crate = "serde_spanned", version = "0.6.9", hashes = ["bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"], @@ -1137,7 +1141,7 @@ rust_repo( ) rust_repo( - name = "toml_datetime", + name = "toml_datetime-0.6.11", crate = "toml_datetime", version = "0.6.11", hashes = ["22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"], @@ -1180,7 +1184,7 @@ rust_repo( rust_repo( - name = "winnow", + name = "winnow-0.7.14", crate = "winnow", version = "0.7.14", hashes = ["5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"], @@ -1221,7 +1225,7 @@ rust_resolve( "either|either|1.12.0||false|true", "lock_api|lock_api|0.4.12||false|true", "parking_lot|parking_lot|0.12.3||false|true", - "itertools|itertools|0.12.1||false|true", + "itertools-0.12.1|itertools|0.12.1||false|true", "slab|slab|0.4.9||false|true", "pin_utils|pin-utils|0.1.0||false|true", "fnv|fnv|1.0.7||false|true", @@ -1250,9 +1254,9 @@ rust_resolve( "want|want|0.3.1||false|true", "petgraph|petgraph|0.6.5||false|true", "futures_channel|futures-channel|0.3.30||false|true", - "heck|heck|0.5.0||false|true", + "heck-0.5.0|heck|0.5.0||false|true", "httpdate|httpdate|1.0.3||false|true", - "log-0.4.21|log|0.4.21||false|true", + "log|log|0.4.33||false|true", "multimap|multimap|0.10.0||false|true", "tower_layer|tower-layer|0.3.2||false|true", "regex_automata|regex-automata|0.4.7||false|true", @@ -1302,8 +1306,8 @@ rust_resolve( "generic_array|generic-array|0.14.7||false|true", "version_check|version_check|0.9.5||false|true", "typenum|typenum|1.17.0||false|true", - "libsecp256k1_gen_ecmult|libsecp256k1-gen-ecmult|0.3.0||false|true", - "libsecp256k1_gen_genmult|libsecp256k1-gen-genmult|0.3.0||false|true", + "libsecp256k1_gen_ecmult-0.3.0|libsecp256k1-gen-ecmult|0.3.0||false|true", + "libsecp256k1_gen_genmult-0.3.0|libsecp256k1-gen-genmult|0.3.0||false|true", "blake3|blake3|1.5.4|default,std|true|true", "arrayvec|arrayvec|0.7.6||false|true", "constant_time_eq|constant_time_eq|0.3.1||false|true", @@ -1348,19 +1352,19 @@ rust_resolve( "serde_core|serde_core|1.0.228||false|true", "serde_derive|serde_derive|1.0.228||false|true", "serde_json|serde_json|1.0.147|default|true|true", - "serde_spanned|serde_spanned|0.6.9||false|true", + "serde_spanned-0.6.9|serde_spanned|0.6.9||false|true", "smallvec|smallvec|1.15.2||false|true", "strsim|strsim|0.11.1||false|true", "syn-2.0.111|syn|2.0.111|full,parsing,default|true|true", "thiserror-1.0.69|thiserror|1.0.69|default|true|true", "thiserror_impl-1.0.69|thiserror-impl|1.0.69||false|true", "toml|toml|0.8.23|default|true|true", - "toml_datetime|toml_datetime|0.6.11||false|true", + "toml_datetime-0.6.11|toml_datetime|0.6.11||false|true", "toml_edit|toml_edit|0.22.27||false|true", "toml_write|toml_write|0.1.2||false|true", "unicode_ident|unicode-ident|1.0.22||false|true", "utf8parse|utf8parse|0.2.2||false|true", - "winnow|winnow|0.7.14||false|true", + "winnow-0.7.14|winnow|0.7.14||false|true", "zmij|zmij|0.1.9||false|true", "bindgen|bindgen|0.71.1||false|true", "bindgen_cli|bindgen-cli|0.71.1||true|true", @@ -1379,7 +1383,7 @@ rust_resolve( "libloading|libloading|0.8.9||true|true", "annotate_snippets|annotate-snippets|0.11.5||true|true", "unicode_width|unicode-width|0.2.2||false|true", - "log|log|0.4.33||false|true", + "log-0.4.33|log|0.4.33||false|true", "priority_queue|priority-queue|2.7.0||false|true", "pubgrub|pubgrub|0.4.0||true|true", "syn|syn|3.0.3||true|true", @@ -1388,14 +1392,23 @@ rust_resolve( "version_ranges|version-ranges|0.1.3||false|true", "base64-0.12.3|base64|0.12.3||true|true", "getrandom-0.1.16|getrandom|0.1.16||false|true", - "heck-0.4.1|heck|0.4.1||true|true", - "itertools-0.10.5|itertools|0.10.5||true|true", - "libsecp256k1_gen_ecmult-0.2.1|libsecp256k1-gen-ecmult|0.2.1||true|true", - "libsecp256k1_gen_genmult-0.2.1|libsecp256k1-gen-genmult|0.2.1||true|true", + "heck|heck|0.4.1||true|true", + "itertools|itertools|0.10.5||true|true", + "libsecp256k1_gen_ecmult|libsecp256k1-gen-ecmult|0.2.1||true|true", + "libsecp256k1_gen_genmult|libsecp256k1-gen-genmult|0.2.1||true|true", "rand-0.7.3|rand|0.7.3||true|true", "rand_chacha-0.2.2|rand_chacha|0.2.2||false|true", "rand_core-0.5.1|rand_core|0.5.1||false|true", "errno|errno|0.3.14||true|true", + "allocator_api2|allocator-api2|0.2.21||false|true", + "cbindgen|cbindgen|0.29.4||true|true", + "foldhash|foldhash|0.2.0||false|true", + "serde_spanned|serde_spanned|1.1.1||false|true", + "toml-0.9.12+spec-1.1.0|toml|0.9.12+spec-1.1.0||false|true", + "toml_datetime|toml_datetime|0.7.5+spec-1.1.0||false|true", + "toml_parser|toml_parser|1.1.3+spec-1.1.0||false|true", + "toml_writer|toml_writer|1.1.2+spec-1.1.0||false|true", + "winnow|winnow|1.0.4||false|true", ], ) @@ -1501,6 +1514,7 @@ rust_repo( version = "0.1.14", hashes = ["7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"], indirect = True, + platforms = [], ) # Added by please_rust sync @@ -1537,11 +1551,12 @@ rust_repo( # Added by please_rust sync rust_repo( - name = "log", + name = "log-0.4.33", crate = "log", version = "0.4.33", hashes = ["0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"], indirect = True, + platforms = [], ) rust_repo( @@ -1606,28 +1621,28 @@ rust_repo( ) rust_repo( - name = "heck-0.4.1", + name = "heck", crate = "heck", version = "0.4.1", hashes = ["95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"], ) rust_repo( - name = "itertools-0.10.5", + name = "itertools", crate = "itertools", version = "0.10.5", hashes = ["b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"], ) rust_repo( - name = "libsecp256k1_gen_ecmult-0.2.1", + name = "libsecp256k1_gen_ecmult", crate = "libsecp256k1-gen-ecmult", version = "0.2.1", hashes = ["ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3"], ) rust_repo( - name = "libsecp256k1_gen_genmult-0.2.1", + name = "libsecp256k1_gen_genmult", crate = "libsecp256k1-gen-genmult", version = "0.2.1", hashes = ["67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d"], @@ -1663,3 +1678,78 @@ rust_repo( version = "0.3.14", hashes = ["39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"], ) + +# Added by please_rust sync +rust_repo( + name = "allocator_api2", + crate = "allocator-api2", + version = "0.2.21", + hashes = ["683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"], + indirect = True, + platforms = [], +) + +rust_repo( + name = "cbindgen", + crate = "cbindgen", + version = "0.29.4", + hashes = ["2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20"], +) + +rust_repo( + name = "foldhash", + crate = "foldhash", + version = "0.2.0", + hashes = ["77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"], + indirect = True, + platforms = [], +) + +rust_repo( + name = "serde_spanned", + crate = "serde_spanned", + version = "1.1.1", + hashes = ["6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"], + indirect = True, +) + +rust_repo( + name = "toml-0.9.12+spec-1.1.0", + crate = "toml", + version = "0.9.12+spec-1.1.0", + hashes = ["cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"], + indirect = True, +) + +rust_repo( + name = "toml_datetime", + crate = "toml_datetime", + version = "0.7.5+spec-1.1.0", + hashes = ["92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"], + indirect = True, +) + +rust_repo( + name = "toml_parser", + crate = "toml_parser", + version = "1.1.3+spec-1.1.0", + hashes = ["1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"], + indirect = True, +) + +rust_repo( + name = "toml_writer", + crate = "toml_writer", + version = "1.1.2+spec-1.1.0", + hashes = ["7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"], + indirect = True, + platforms = [], +) + +rust_repo( + name = "winnow", + crate = "winnow", + version = "1.0.4", + hashes = ["23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"], + indirect = True, +) From 0e1ec3949218722e9a159bb5cb040f41cfa5cbc4 Mon Sep 17 00:00:00 2001 From: Jack Marsh Date: Mon, 24 Aug 2026 14:14:06 +0100 Subject: [PATCH 3/4] Document rust_cbindgen --- README.md | 20 ++++++++++++++++++++ docs/COMPARISON.md | 3 +-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1467698..653fa5f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md index e69c033..f71a54e 100644 --- a/docs/COMPARISON.md +++ b/docs/COMPARISON.md @@ -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, @@ -120,7 +119,7 @@ rather than by release: | **Documentation**
rustdoc HTML | **yes**. rust_doc | **yes**. rust_doc | **yes**. cargo doc | | **Coverage**
Line coverage from instrumented tests | **yes**. -C instrument-coverage into plz cover | **yes**. Supported | **partial**. External tooling, llvm-cov | | **C header bindings**
bindgen | **yes**. rust_bindgen, tool built from declared crates | **yes**. rust_bindgen | **partial**. build.rs calling bindgen | -| **Rust to C headers**
cbindgen | **no**. Open | **yes**. Supported | **partial**. build.rs calling cbindgen | +| **Rust to C headers**
cbindgen | **yes**. rust_cbindgen, tool built from declared crates | **yes**. Supported | **partial**. build.rs calling cbindgen | | **wasm-bindgen**
JS bindings | **no**. Groundwork only | **yes**. Supported | **partial**. External tool | | **Publish to crates.io**
cargo publish | **no**. Deliberate non-goal | **no**. Not its job | **yes**. Native | From d41156ffb85bdcbfd68fbad1f122ae687a2bcb92 Mon Sep 17 00:00:00 2001 From: Jack Marsh Date: Mon, 24 Aug 2026 14:26:47 +0100 Subject: [PATCH 4/4] Correct the cbindgen row: rules_rust has none The table claimed rules_rust supported Rust to C headers. It does not. Its extensions are bindgen, mdbook, prost, pyo3 and wasm_bindgen, and a code search over the repository returns no hits for cbindgen at all, against 23 for rust_bindgen and 41 for wasm_bindgen, so the search works and the absence is real. Cargo's row is right as it stands: cbindgen is called from build.rs as a library, generated by the crate being built rather than as an artifact another rule can depend on. --- docs/COMPARISON.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md index f71a54e..6aeb0ef 100644 --- a/docs/COMPARISON.md +++ b/docs/COMPARISON.md @@ -119,7 +119,7 @@ rather than by release: | **Documentation**
rustdoc HTML | **yes**. rust_doc | **yes**. rust_doc | **yes**. cargo doc | | **Coverage**
Line coverage from instrumented tests | **yes**. -C instrument-coverage into plz cover | **yes**. Supported | **partial**. External tooling, llvm-cov | | **C header bindings**
bindgen | **yes**. rust_bindgen, tool built from declared crates | **yes**. rust_bindgen | **partial**. build.rs calling bindgen | -| **Rust to C headers**
cbindgen | **yes**. rust_cbindgen, tool built from declared crates | **yes**. Supported | **partial**. build.rs calling cbindgen | +| **Rust to C headers**
cbindgen | **yes**. rust_cbindgen, tool built from declared crates | **no**. No cbindgen rule or extension | **partial**. cbindgen called from build.rs | | **wasm-bindgen**
JS bindings | **no**. Groundwork only | **yes**. Supported | **partial**. External tool | | **Publish to crates.io**
cargo publish | **no**. Deliberate non-goal | **no**. Not its job | **yes**. Native |