7 releases
0.5.0 | Jun 19, 2023 |
---|---|
0.4.1 | Feb 4, 2023 |
0.3.4 | Jan 17, 2023 |
0.3.2 | Aug 30, 2022 |
#2047 in Rust patterns
299 downloads per month
Used in 2 crates
12KB
152 lines
This crate supports generating a C header for a library crate, directly in the library itself.
Typically the crate would be annotated with ffizz_header
macros to define the header content.
Then, the header is generated by calling generate
.
Generating Headers
What follows is a simple, effective way to generate the header file, using the excellent cargo-xtask.
With this in place, simply run cargo xtask codegen
to generate the header file.
The file can either be checked in (in which case CI should verify that it is up-to-date), or generated as part of the release / packaging process.
Setup
In your library's top level, add a call to
#[cfg(debug_assertions)] // only include this in debug builds
/// Generate the header
pub fn generate_header() -> String {
ffizz_header::generate()
}
Set up an xtask project as described in the project's documentation.
Add your library as a dependency of the xtask crate.
In xtask/src/main.rs
, for a mysupercool-lib
crate:
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let workspace_dir = manifest_dir.parent().unwrap();
// assume the mysupercool-lib crate is in `lib/`..
let lib_crate_dir = workspace_dir.join("tests").join("lib");
let mut file = File::create(lib_crate_dir.join("mysupercoollib.h")).unwrap();
write!(&mut file, "{}", ::mysupercool_lib::generate_header()).unwrap();
}
You may wish to improve on this implementation, with proper command-line parsing and error handling.
Caveats
This method does not support producing multiple header files for a single workspace. Rust refuses to link them, due to duplicate symbols. If your workspace contains multiple libraries, another option is to build a binary for each one, that generates the header file for only that library.
Defining Headers
Typically, a library exporting a header will define its topmatter and corresponding footer in src/lib.rs
, using snippet
.
ffizz_header::snippet! {
#[ffizz(name="topmatter", order=1)]
/// ```c
/// #ifndef INFPREC_H
/// #define INFPREC_H
///
/// #include <stdint.h> // ..and any other required headesr
/// ```
}
ffizz_header::snippet! {
#[ffizz(name="bottomatter", order=10000)]
/// ```c
/// #endif /* INFPREC_H */
/// ```
}
The topmatter might also include forward declarations of types or macros.
The remaining declarations will be for types and exported functions, using item
.
It can be helpful to define a range of order
values for each source file, to keep related declarations together in the generated header.
#[ffizz_header::item]
#[ffizz(order = 900)]
/// ***** infprec_t *****
///
/// An infinite-precision integer.
/// ```c
/// typedef struct infprec_t infprec_t
/// ```
pub struct InfPrec { /* .. */ }
# type infprec_t = ();
#[ffizz_header::item]
#[ffizz(order = 901)]
/// Add two infinite-precision numbers.
#[no_mangle]
pub unsafe extern "C" fn infprec_add(a: infprec_t, b: infprec_t) -> infprec_t { todo!() }
extern "C"
For headers intended for use in C and C++, it may be helpful to define an EXTERN_C macro:
/// #ifdef __cplusplus
/// #define EXTERN_C extern "C"
/// #else
/// #define EXTERN_C
/// #endif // __cplusplus
this can later be used in declarations like
EXTERN_C infprec_t infprec_add(infprec_t a, infprec_t b);
Dependencies
~2MB
~45K SLoC