/* 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 { // 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 " ) .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()); } } }