Repository: mozilla/cbindgen (language: rust)

Issue:
Added support for integrating the package_version
 The package version information appears in a comment of the header file. The new command line argument for this is --package-version. The TOML config file flag is package_version=true||false.

This new feature is helpful when you are working on a growing Rust library with new functions and structs. The package version entry in the header file appears as
**"/*Package version: {}*/",**

and gives a better overview which header file fits to which version of the library.


Discussion / hints:

Fix patch:
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index 5d56faf9a..271800cb2 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,2 +1,2 @@
 [toolchain]
-channel = "nightly"
+channel = "nightly"
\ No newline at end of file
diff --git a/src/bindgen/bindings.rs b/src/bindgen/bindings.rs
index 410b11f30..80793db4b 100644
--- a/src/bindgen/bindings.rs
+++ b/src/bindgen/bindings.rs
@@ -33,6 +33,7 @@ pub struct Bindings {
     /// Bindings are generated by a recursive call to cbindgen
     /// and shouldn't do anything when written anywhere.
     noop: bool,
+    package_version: String,
 }
 
 #[derive(PartialEq, Eq)]
@@ -53,6 +54,7 @@ impl Bindings {
         functions: Vec<Function>,
         source_files: Vec<path::PathBuf>,
         noop: bool,
+        package_version: String,
     ) -> Bindings {
         Bindings {
             config,
@@ -65,6 +67,7 @@ impl Bindings {
             functions,
             source_files,
             noop,
+            package_version,
         }
     }
 
@@ -209,6 +212,20 @@ impl Bindings {
             return;
         }
 
+        if self.config.package_version {
+            out.new_line_if_not_start();
+            match self.config.language {
+                Language::C | Language::Cxx => {
+                    write!(out, "/* Package version: {} */", self.package_version);
+                }
+                Language::Cython => {
+                    write!(out, "''' Package version: {} '''", self.package_version);
+                }
+            }
+
+            out.new_line();
+        }
+
         if let Some(ref f) = self.config.header {
             out.new_line_if_not_start();
             write!(out, "{}", f);
@@ -228,11 +245,23 @@ impl Bindings {
         }
         if self.config.include_version {
             out.new_line_if_not_start();
-            write!(
-                out,
-                "/* Generated with cbindgen:{} */",
-                crate::bindgen::config::VERSION
-            );
+            match self.config.language {
+                Language::C | Language::Cxx => {
+                    write!(
+                        out,
+                        "/* Generated with cbindgen:{} */",
+                        crate::bindgen::config::VERSION
+                    );
+                }
+                Language::Cython => {
+                    write!(
+                        out,
+                        "''' Generated with cbindgen:{} '''",
+                        crate::bindgen::config::VERSION
+                    );
+                }
+            }
+
             out.new_line();
         }
         if let Some(ref f) = self.config.autogen_warning {
diff --git a/src/bindgen/builder.rs b/src/bindgen/builder.rs
index 4d2eee12b..d47919b98 100644
--- a/src/bindgen/builder.rs
+++ b/src/bindgen/builder.rs
@@ -361,6 +361,7 @@ impl Builder {
                 Default::default(),
                 Default::default(),
                 true,
+                String::new(),
             ));
         }
 
@@ -405,6 +406,7 @@ impl Builder {
             result.typedefs,
             result.functions,
             result.source_files,
+            result.package_version,
         )
         .generate()
     }
diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs
index 9c1a3ccec..dc5fa4635 100644
--- a/src/bindgen/config.rs
+++ b/src/bindgen/config.rs
@@ -919,6 +919,8 @@ pub struct Config {
     /// This option is useful when using cbindgen with tools such as python's cffi which
     /// doesn't understand include directives
     pub no_includes: bool,
+    // Package version: True if the package version should appear as a comment in the .h file
+    pub package_version: bool,
     /// Optional text to output at major sections to deter manual editing
     pub autogen_warning: Option<String>,
     /// Include a comment with the version of cbindgen used to generate the file
@@ -1040,6 +1042,7 @@ impl Default for Config {
             autogen_warning: None,
             include_version: false,
             no_includes: false,
+            package_version: false,
             namespace: None,
             namespaces: None,
             using_namespaces: None,
diff --git a/src/bindgen/ir/constant.rs b/src/bindgen/ir/constant.rs
index d3b9bd443..51e59b2d9 100644
--- a/src/bindgen/ir/constant.rs
+++ b/src/bindgen/ir/constant.rs
@@ -7,7 +7,7 @@ use std::collections::HashMap;
 use std::io::Write;
 
 use syn::ext::IdentExt;
-use syn::{self, UnOp};
+use syn::UnOp;
 
 use crate::bindgen::config::{Config, Language};
 use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
diff --git a/src/bindgen/library.rs b/src/bindgen/library.rs
index cb4cfbd3a..2fd79fed4 100644
--- a/src/bindgen/library.rs
+++ b/src/bindgen/library.rs
@@ -27,6 +27,7 @@ pub struct Library {
     typedefs: ItemMap<Typedef>,
     functions: Vec<Function>,
     source_files: Vec<PathBuf>,
+    package_version: String,
 }
 
 impl Library {
@@ -42,6 +43,7 @@ impl Library {
         typedefs: ItemMap<Typedef>,
         functions: Vec<Function>,
         source_files: Vec<PathBuf>,
+        package_version: String,
     ) -> Library {
         Library {
             config,
@@ -54,6 +56,7 @@ impl Library {
             typedefs,
             functions,
             source_files,
+            package_version,
         }
     }
 
@@ -141,6 +144,7 @@ impl Library {
             functions,
             self.source_files,
             false,
+            self.package_version,
         ))
     }
 
diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs
index b54c7fbbd..96db4025d 100644
--- a/src/bindgen/parser.rs
+++ b/src/bindgen/parser.rs
@@ -81,6 +81,13 @@ pub(crate) fn parse_lib(lib: Cargo, config: &Config) -> ParseResult {
     let binding_crate = context.lib.as_ref().unwrap().binding_crate_ref();
     context.parse_crate(&binding_crate)?;
     context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect();
+    context.out.package_version = context
+        .lib
+        .as_ref()
+        .unwrap()
+        .binding_crate_ref()
+        .version
+        .unwrap();
     Ok(context.out)
 }
 
@@ -409,6 +416,7 @@ pub struct Parse {
     pub typedefs: ItemMap<Typedef>,
     pub functions: Vec<Function>,
     pub source_files: Vec<FilePathBuf>,
+    pub package_version: String,
 }
 
 impl Parse {
@@ -423,6 +431,7 @@ impl Parse {
             typedefs: ItemMap::default(),
             functions: Vec::new(),
             source_files: Vec::new(),
+            package_version: String::new(),
         }
     }
 
@@ -471,6 +480,7 @@ impl Parse {
         self.typedefs.extend_with(&other.typedefs);
         self.functions.extend_from_slice(&other.functions);
         self.source_files.extend_from_slice(&other.source_files);
+        self.package_version = other.package_version.clone();
     }
 
     fn load_syn_crate_mod<'a>(
diff --git a/src/main.rs b/src/main.rs
index e9c5ffc35..a6a1852c2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -49,6 +49,10 @@ fn apply_config_overrides(config: &mut Config, matches: &ArgMatches) {
         config.only_target_dependencies = true;
     }
 
+    if matches.get_flag("package-version") {
+        config.package_version = true;
+    }
+
     match matches.try_get_one::<String>("style") {
         Ok(Some(style)) => {
             config.style = bindgen::Style::from_str(style).unwrap();
@@ -163,6 +167,12 @@ fn main() {
                 .help("Specify the language to output bindings in")
                 .value_parser(["c++", "C++", "c", "C", "cython", "Cython"]),
         )
+        .arg(
+            Arg::new("package-version")
+                .long("package-version")
+                .action(ArgAction::SetTrue)
+                .help("Include the package version in the header comment")
+        )
         .arg(
             Arg::new("cpp-compat")
                 .long("cpp-compat")


Test patch:
diff --git a/tests/expectations/package_version.c b/tests/expectations/package_version.c
new file mode 100644
index 000000000..dd8e7185d
--- /dev/null
+++ b/tests/expectations/package_version.c
@@ -0,0 +1,12 @@
+/* Package version: 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct {
+  uint64_t bar;
+} Foo;
+
+void doit(const Foo*);
diff --git a/tests/expectations/package_version.compat.c b/tests/expectations/package_version.compat.c
new file mode 100644
index 000000000..4bb26e1f0
--- /dev/null
+++ b/tests/expectations/package_version.compat.c
@@ -0,0 +1,20 @@
+/* Package version: 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct {
+  uint64_t bar;
+} Foo;
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+void doit(const Foo*);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
diff --git a/tests/expectations/package_version.cpp b/tests/expectations/package_version.cpp
new file mode 100644
index 000000000..c3091eb18
--- /dev/null
+++ b/tests/expectations/package_version.cpp
@@ -0,0 +1,17 @@
+/* Package version: 0.1.0 */
+
+#include <cstdarg>
+#include <cstdint>
+#include <cstdlib>
+#include <ostream>
+#include <new>
+
+struct Foo {
+  uint64_t bar;
+};
+
+extern "C" {
+
+void doit(const Foo*);
+
+} // extern "C"
diff --git a/tests/expectations/package_version.pyx b/tests/expectations/package_version.pyx
new file mode 100644
index 000000000..7c92cadcf
--- /dev/null
+++ b/tests/expectations/package_version.pyx
@@ -0,0 +1,14 @@
+''' Package version: 0.1.0 '''
+
+from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t
+from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t
+cdef extern from *:
+  ctypedef bint bool
+  ctypedef struct va_list
+
+cdef extern from *:
+
+  ctypedef struct Foo:
+    uint64_t bar;
+
+  void doit(const Foo*);
diff --git a/tests/expectations/package_version_both.c b/tests/expectations/package_version_both.c
new file mode 100644
index 000000000..b09c9468c
--- /dev/null
+++ b/tests/expectations/package_version_both.c
@@ -0,0 +1,12 @@
+/* Package version: 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct Foo {
+  uint64_t bar;
+} Foo;
+
+void doit(const struct Foo*);
diff --git a/tests/expectations/package_version_both.compat.c b/tests/expectations/package_version_both.compat.c
new file mode 100644
index 000000000..763c3e4d4
--- /dev/null
+++ b/tests/expectations/package_version_both.compat.c
@@ -0,0 +1,20 @@
+/* Package version: 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef struct Foo {
+  uint64_t bar;
+} Foo;
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+void doit(const struct Foo*);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
diff --git a/tests/expectations/package_version_tag.c b/tests/expectations/package_version_tag.c
new file mode 100644
index 000000000..b46eae6e6
--- /dev/null
+++ b/tests/expectations/package_version_tag.c
@@ -0,0 +1,12 @@
+/* Package version: 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+struct Foo {
+  uint64_t bar;
+};
+
+void doit(const struct Foo*);
diff --git a/tests/expectations/package_version_tag.compat.c b/tests/expectations/package_version_tag.compat.c
new file mode 100644
index 000000000..0d8fb3da4
--- /dev/null
+++ b/tests/expectations/package_version_tag.compat.c
@@ -0,0 +1,20 @@
+/* Package version: 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+struct Foo {
+  uint64_t bar;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+void doit(const struct Foo*);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
diff --git a/tests/expectations/package_version_tag.pyx b/tests/expectations/package_version_tag.pyx
new file mode 100644
index 000000000..8f70779a7
--- /dev/null
+++ b/tests/expectations/package_version_tag.pyx
@@ -0,0 +1,14 @@
+''' Package version: 0.1.0 '''
+
+from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t
+from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t
+cdef extern from *:
+  ctypedef bint bool
+  ctypedef struct va_list
+
+cdef extern from *:
+
+  cdef struct Foo:
+    uint64_t bar;
+
+  void doit(const Foo*);
diff --git a/tests/rust/package_version/Cargo.lock b/tests/rust/package_version/Cargo.lock
new file mode 100644
index 000000000..aa2bb7ff7
--- /dev/null
+++ b/tests/rust/package_version/Cargo.lock
@@ -0,0 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "package_version"
+version = "0.1.0"
diff --git a/tests/rust/package_version/Cargo.toml b/tests/rust/package_version/Cargo.toml
new file mode 100644
index 000000000..58bcf81dc
--- /dev/null
+++ b/tests/rust/package_version/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "package_version"
+version = "0.1.0"
+authors = ["hitbear"]
+
+[features]
+cbindgen = []
\ No newline at end of file
diff --git a/tests/rust/package_version/cbindgen.toml b/tests/rust/package_version/cbindgen.toml
new file mode 100644
index 000000000..1288ec1e3
--- /dev/null
+++ b/tests/rust/package_version/cbindgen.toml
@@ -0,0 +1,1 @@
+package_version = true
\ No newline at end of file
diff --git a/tests/rust/package_version/src/lib.rs b/tests/rust/package_version/src/lib.rs
new file mode 100644
index 000000000..8dcc8c509
--- /dev/null
+++ b/tests/rust/package_version/src/lib.rs
@@ -0,0 +1,7 @@
+#[repr(C)]
+pub struct Foo {
+    bar: u64,
+}
+
+#[no_mangle]
+pub extern "C" fn doit(_: &Foo) {}
diff --git a/tests/tests.rs b/tests/tests.rs
index 3846f326e..995f108de 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -26,6 +26,7 @@ fn run_cbindgen(
     cpp_compat: bool,
     style: Option<Style>,
     generate_depfile: bool,
+    package_version: bool,
 ) -> (Vec<u8>, Option<String>) {
     assert!(
         !(output.is_none() && generate_depfile),
@@ -58,6 +59,10 @@ fn run_cbindgen(
         }
     }
 
+    if package_version {
+        command.arg("--package-version");
+    }
+
     if let Some(style) = style {
         command.arg("--style").arg(style_str(style));
     }
@@ -200,6 +205,7 @@ fn run_compile_test(
     cpp_compat: bool,
     style: Option<Style>,
     cbindgen_outputs: &mut HashSet<Vec<u8>>,
+    package_version: bool,
 ) {
     let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
     let tests_path = Path::new(&crate_dir).join("tests");
@@ -248,6 +254,7 @@ fn run_compile_test(
         cpp_compat,
         style,
         generate_depfile,
+        package_version,
     );
     if generate_depfile {
         let depfile = depfile_contents.expect("No depfile generated");
@@ -329,6 +336,7 @@ fn test_file(name: &'static str, filename: &'static str) {
                 *cpp_compat,
                 Some(*style),
                 &mut cbindgen_outputs,
+                false,
             );
         }
     }
@@ -341,6 +349,7 @@ fn test_file(name: &'static str, filename: &'static str) {
         /* cpp_compat = */ false,
         None,
         &mut HashSet::new(),
+        false,
     );
 
     // `Style::Both` should be identical to `Style::Tag` for Cython.
@@ -354,6 +363,7 @@ fn test_file(name: &'static str, filename: &'static str) {
             /* cpp_compat = */ false,
             Some(*style),
             &mut cbindgen_outputs,
+            false,
         );
     }
 }