| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| use std::env; |
| use std::io; |
| use std::path::{Path, PathBuf}; |
| use std::str::FromStr; |
| |
| extern crate clap; |
| #[macro_use] |
| extern crate log; |
| extern crate proc_macro2; |
| #[macro_use] |
| extern crate serde; |
| extern crate serde_json; |
| #[macro_use] |
| extern crate quote; |
| #[macro_use] |
| extern crate syn; |
| extern crate toml; |
| |
| use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; |
| |
| mod bindgen; |
| mod logging; |
| |
| use bindgen::{Bindings, Builder, Cargo, Config, Error}; |
| |
| fn apply_config_overrides(config: &mut Config, matches: &ArgMatches) { |
| // We allow specifying a language to override the config default. This is |
| // used by compile-tests. |
| match matches.try_get_one::<String>("lang") { |
| Ok(Some(lang)) => { |
| config.language = bindgen::Language::from_str(lang).unwrap(); |
| } |
| Err(reason) => { |
| error!("{}", reason); |
| return; |
| } |
| _ => (), |
| } |
| |
| if matches.get_flag("cpp-compat") { |
| config.cpp_compat = true; |
| } |
| |
| if matches.get_flag("only-target-dependencies") { |
| 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(); |
| } |
| Err(_) => { |
| error!("Unknown style specified."); |
| return; |
| } |
| _ => (), |
| } |
| |
| match matches.try_get_one::<String>("profile") { |
| Ok(Some(profile)) => { |
| config.parse.expand.profile = bindgen::Profile::from_str(profile).unwrap(); |
| } |
| Err(e) => { |
| error!("{}", e); |
| return; |
| } |
| _ => (), |
| } |
| |
| if matches.get_flag("d") { |
| config.parse.parse_deps = true; |
| } |
| } |
| |
| fn load_bindings(input: &Path, matches: &ArgMatches) -> Result<Bindings, Error> { |
| // If a file is specified then we load it as a single source |
| if !input.is_dir() { |
| // Load any config specified or search in the input directory |
| let mut config = match matches.get_one::<PathBuf>("config") { |
| Some(c) => Config::from_file(c).unwrap(), |
| None => Config::from_root_or_default( |
| input |
| .parent() |
| .expect("All files should have a parent directory"), |
| ), |
| }; |
| |
| apply_config_overrides(&mut config, matches); |
| |
| return Builder::new() |
| .with_config(config) |
| .with_src(input) |
| .generate(); |
| } |
| |
| // We have to load a whole crate, so we use cargo to gather metadata |
| let lib = Cargo::load( |
| input, |
| matches.get_one::<PathBuf>("lockfile").map(|s| s.as_path()), |
| matches.get_one::<String>("crate").map(|s| s.as_str()), |
| true, |
| matches.get_flag("clean"), |
| matches.get_flag("only-target-dependencies"), |
| matches.get_one::<PathBuf>("metadata").map(|p| p.as_path()), |
| )?; |
| |
| // Load any config specified or search in the binding crate directory |
| let mut config = match matches.get_one::<PathBuf>("config") { |
| Some(c) => Config::from_file(c).unwrap(), |
| None => { |
| let binding_crate_dir = lib.find_crate_dir(&lib.binding_crate_ref()); |
| |
| if let Some(binding_crate_dir) = binding_crate_dir { |
| Config::from_root_or_default(binding_crate_dir) |
| } else { |
| // This shouldn't happen |
| Config::from_root_or_default(input) |
| } |
| } |
| }; |
| |
| apply_config_overrides(&mut config, matches); |
| |
| Builder::new() |
| .with_config(config) |
| .with_cargo(lib) |
| .generate() |
| } |
| |
| fn main() { |
| let matches = Command::new("cbindgen") |
| .version(bindgen::VERSION) |
| .about("Generate C bindings for a Rust library") |
| .arg( |
| Arg::new("v") |
| .short('v') |
| .action(ArgAction::Count) |
| .help("Enable verbose logging"), |
| ) |
| .arg( |
| Arg::new("verify") |
| .long("verify") |
| .action(ArgAction::SetTrue) |
| .help("Generate bindings and compare it to the existing bindings file and error if they are different"), |
| ) |
| .arg( |
| Arg::new("config") |
| .short('c') |
| .long("config") |
| .value_name("PATH") |
| .value_parser(value_parser!(PathBuf)) |
| .help("Specify path to a `cbindgen.toml` config to use"), |
| ) |
| .arg( |
| Arg::new("lang") |
| .short('l') |
| .long("lang") |
| .value_name("LANGUAGE") |
| .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") |
| .action(ArgAction::SetTrue) |
| .help("Whether to add C++ compatibility to generated C bindings") |
| ) |
| .arg( |
| Arg::new("only-target-dependencies") |
| .long("only-target-dependencies") |
| .action(ArgAction::SetTrue) |
| .help("Only fetch dependencies needed by the target platform. \ |
| The target platform defaults to the host platform; set TARGET to override.") |
| ) |
| .arg( |
| Arg::new("style") |
| .short('s') |
| .long("style") |
| .value_name("STYLE") |
| .help("Specify the declaration style to use for bindings") |
| .value_parser(["Both", "both", "Tag", "tag", "Type", "type"]), |
| ) |
| .arg( |
| Arg::new("d") |
| .short('d') |
| .long("parse-dependencies") |
| .action(ArgAction::SetTrue) |
| .help("Whether to parse dependencies when generating bindings"), |
| ) |
| .arg( |
| Arg::new("clean") |
| .long("clean") |
| .action(ArgAction::SetTrue) |
| .help( |
| "Whether to use a new temporary directory for expanding macros. \ |
| Affects performance, but might be required in certain build processes.") |
| .required(false) |
| ) |
| .arg( |
| Arg::new("INPUT") |
| .help( |
| "A crate directory or source file to generate bindings for. \ |
| In general this is the folder where the Cargo.toml file of \ |
| source Rust library resides.") |
| .required(false) |
| .value_parser(value_parser!(PathBuf)) |
| .index(1), |
| ) |
| .arg( |
| Arg::new("crate") |
| .long("crate") |
| .value_name("CRATE_NAME") |
| .help( |
| "If generating bindings for a crate, \ |
| the specific crate to generate bindings for", |
| ) |
| .required(false), |
| ) |
| .arg( |
| Arg::new("out") |
| .short('o') |
| .long("output") |
| .value_name("PATH") |
| .help("The file to output the bindings to") |
| .value_parser(value_parser!(PathBuf)) |
| .required(false), |
| ) |
| .arg( |
| Arg::new("lockfile") |
| .long("lockfile") |
| .value_name("PATH") |
| .help( |
| "Specify the path to the Cargo.lock file explicitly. If this \ |
| is not specified, the Cargo.lock file is searched for in the \ |
| same folder as the Cargo.toml file. This option is useful for \ |
| projects that use workspaces.") |
| .value_parser(value_parser!(PathBuf)) |
| .required(false), |
| ) |
| .arg( |
| Arg::new("metadata") |
| .long("metadata") |
| .value_name("PATH") |
| .help( |
| "Specify the path to the output of a `cargo metadata` \ |
| command that allows to get dependency information. \ |
| This is useful because cargo metadata may be the longest \ |
| part of cbindgen runtime, and you may want to share it \ |
| across cbindgen invocations. By default cbindgen will run \ |
| `cargo metadata --all-features --format-version 1 \ |
| --manifest-path <path/to/crate/Cargo.toml>" |
| ) |
| .value_parser(value_parser!(PathBuf)) |
| .required(false), |
| ) |
| .arg( |
| Arg::new("profile") |
| .long("profile") |
| .value_name("PROFILE") |
| .help( |
| "Specify the profile to use when expanding macros. \ |
| Has no effect otherwise." |
| ) |
| .value_parser(["Debug", "debug", "Release", "release"]), |
| ) |
| .arg( |
| Arg::new("quiet") |
| .short('q') |
| .long("quiet") |
| .action(ArgAction::SetTrue) |
| .help("Report errors only (overrides verbosity options).") |
| .required(false), |
| ) |
| .arg( |
| Arg::new("depfile") |
| .value_name("PATH") |
| .long("depfile") |
| .num_args(1) |
| .required(false) |
| .value_parser(value_parser!(PathBuf)) |
| .help("Generate a depfile at the given Path listing the source files \ |
| cbindgen traversed when generating the bindings. Useful when \ |
| integrating cbindgen into 3rd party build-systems. \ |
| This option is ignored if `--out` is missing." |
| ) |
| ) |
| .get_matches(); |
| |
| if matches.get_flag("verify") && !matches.contains_id("out") { |
| error!( |
| "Cannot verify bindings against `stdout`, please specify a file to compare against." |
| ); |
| std::process::exit(2); |
| } |
| |
| // Initialize logging |
| if matches.get_flag("quiet") { |
| logging::ErrorLogger::init().unwrap(); |
| } else { |
| match matches.get_count("v") { |
| 0 => logging::WarnLogger::init().unwrap(), |
| 1 => logging::InfoLogger::init().unwrap(), |
| _ => logging::TraceLogger::init().unwrap(), |
| } |
| } |
| |
| // Find the input directory |
| let input: PathBuf = matches |
| .get_one("INPUT") |
| .cloned() |
| .unwrap_or_else(|| env::current_dir().unwrap()); |
| |
| let bindings = match load_bindings(&input, &matches) { |
| Ok(bindings) => bindings, |
| Err(msg) => { |
| error!("{}", msg); |
| error!("Couldn't generate bindings for {}.", input.display()); |
| std::process::exit(1); |
| } |
| }; |
| |
| // Write the bindings file |
| match matches.get_one::<PathBuf>("out") { |
| Some(file) => { |
| let changed = bindings.write_to_file(file); |
| |
| if matches.get_flag("verify") && changed { |
| error!("Bindings changed: {}", file.display()); |
| std::process::exit(2); |
| } |
| if let Some(depfile) = matches.get_one("depfile") { |
| bindings.generate_depfile(file, depfile) |
| } |
| } |
| _ => { |
| bindings.write(io::stdout()); |
| } |
| } |
| } |