diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eb78a08 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,335 @@ +/* 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::{Arg, ArgMatches, Command}; + +mod bindgen; +mod logging; + +use crate::bindgen::{Bindings, Builder, Cargo, Config, Error, Profile, Style}; + +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. + if let Some(lang) = matches.value_of("lang") { + config.language = match lang.parse() { + Ok(lang) => lang, + Err(reason) => { + error!("{}", reason); + return; + } + } + } + + if matches.is_present("cpp-compat") { + config.cpp_compat = true; + } + + if matches.is_present("only-target-dependencies") { + config.only_target_dependencies = true; + } + + if let Some(style) = matches.value_of("style") { + config.style = match style { + "Both" => Style::Both, + "both" => Style::Both, + "Tag" => Style::Tag, + "tag" => Style::Tag, + "Type" => Style::Type, + "type" => Style::Type, + _ => { + error!("Unknown style specified."); + return; + } + } + } + + if let Some(profile) = matches.value_of("profile") { + config.parse.expand.profile = match Profile::from_str(profile) { + Ok(p) => p, + Err(e) => { + error!("{}", e); + return; + } + } + } + + if matches.is_present("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.value_of("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.value_of("lockfile"), + matches.value_of("crate"), + true, + matches.is_present("clean"), + matches.is_present("only-target-dependencies"), + matches.value_of("metadata").map(Path::new), + )?; + + // Load any config specified or search in the binding crate directory + let mut config = match matches.value_of("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') + .multiple_occurrences(true) + .help("Enable verbose logging"), + ) + .arg( + Arg::new("verify") + .long("verify") + .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") + .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") + .possible_values(["c++", "C++", "c", "C", "cython", "Cython"]), + ) + .arg( + Arg::new("cpp-compat") + .long("cpp-compat") + .help("Whether to add C++ compatibility to generated C bindings") + ) + .arg( + Arg::new("only-target-dependencies") + .long("only-target-dependencies") + .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") + .possible_values(["Both", "both", "Tag", "tag", "Type", "type"]), + ) + .arg( + Arg::new("d") + .short('d') + .long("parse-dependencies") + .help("Whether to parse dependencies when generating bindings"), + ) + .arg( + Arg::new("clean") + .long("clean") + .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) + .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") + .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.") + .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>" + ) + .required(false), + ) + .arg( + Arg::new("profile") + .long("profile") + .value_name("PROFILE") + .help( + "Specify the profile to use when expanding macros. \ + Has no effect otherwise." + ) + .possible_values(["Debug", "debug", "Release", "release"]), + ) + .arg( + Arg::new("quiet") + .short('q') + .long("quiet") + .help("Report errors only (overrides verbosity options).") + .required(false), + ) + .arg( + Arg::new("depfile") + .value_name("PATH") + .long("depfile") + .takes_value(true) + .min_values(1) + .max_values(1) + .required(false) + .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.is_present("out") && matches.is_present("verify") { + error!( + "Cannot verify bindings against `stdout`, please specify a file to compare against." + ); + std::process::exit(2); + } + + // Initialize logging + if matches.is_present("quiet") { + logging::ErrorLogger::init().unwrap(); + } else { + match matches.occurrences_of("v") { + 0 => logging::WarnLogger::init().unwrap(), + 1 => logging::InfoLogger::init().unwrap(), + _ => logging::TraceLogger::init().unwrap(), + } + } + + // Find the input directory + let input = match matches.value_of("INPUT") { + Some(input) => PathBuf::from(input), + None => 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.value_of("out") { + Some(file) => { + let changed = bindings.write_to_file(file); + + if matches.is_present("verify") && changed { + error!("Bindings changed: {}", file); + std::process::exit(2); + } + if let Some(depfile) = matches.value_of("depfile") { + bindings.generate_depfile(file, depfile) + } + } + _ => { + bindings.write(io::stdout()); + } + } +} |