1#[cfg(test)] 2#[path = "test.rs"] 3mod test; 4 5use super::{Opt, Output}; 6use crate::cfg::{self, CfgValue}; 7use crate::gen::include::Include; 8use crate::syntax::IncludeKind; 9use clap::builder::{ArgAction, ValueParser}; 10use clap::{Arg, Command}; 11use std::collections::{BTreeMap as Map, BTreeSet as Set}; 12use std::path::PathBuf; 13use std::process; 14use std::sync::{Arc, Mutex, PoisonError}; 15use syn::parse::Parser; 16 17const USAGE: &str = "\ 18 cxxbridge <input>.rs Emit .cc file for bridge to stdout 19 cxxbridge <input>.rs --header Emit .h file for bridge to stdout 20 cxxbridge --header Emit \"rust/cxx.h\" header to stdout\ 21"; 22 23const TEMPLATE: &str = "\ 24{bin} {version} 25David Tolnay <dtolnay@gmail.com> 26https://github.com/dtolnay/cxx 27 28{usage-heading} 29 {usage} 30 31{all-args}\ 32"; 33 34fn app() -> Command { 35 let mut app = Command::new("cxxbridge") 36 .override_usage(USAGE) 37 .help_template(TEMPLATE) 38 .next_line_help(true) 39 .disable_help_flag(true) 40 .disable_version_flag(true) 41 .arg(arg_input()) 42 .arg(arg_cfg()) 43 .arg(arg_cxx_impl_annotations()) 44 .arg(arg_header()) 45 .arg(arg_help()) 46 .arg(arg_include()) 47 .arg(arg_output()); 48 if let Some(version) = option_env!("CARGO_PKG_VERSION") { 49 app = app.arg(arg_version()).version(version); 50 } 51 app 52} 53 54const INPUT: &str = "input"; 55const CFG: &str = "cfg"; 56const CXX_IMPL_ANNOTATIONS: &str = "cxx-impl-annotations"; 57const HELP: &str = "help"; 58const HEADER: &str = "header"; 59const INCLUDE: &str = "include"; 60const OUTPUT: &str = "output"; 61const VERSION: &str = "version"; 62 63pub(super) fn from_args() -> Opt { 64 let matches = app().get_matches(); 65 66 if matches.get_flag(HELP) { 67 let _ = app().print_long_help(); 68 process::exit(0); 69 } 70 71 let input = matches.get_one::<PathBuf>(INPUT).cloned(); 72 let cxx_impl_annotations = matches 73 .get_one::<String>(CXX_IMPL_ANNOTATIONS) 74 .map(String::clone); 75 let header = matches.get_flag(HEADER); 76 let include = matches 77 .get_many::<String>(INCLUDE) 78 .unwrap_or_default() 79 .map(|include| { 80 if include.starts_with('<') && include.ends_with('>') { 81 Include { 82 path: include[1..include.len() - 1].to_owned(), 83 kind: IncludeKind::Bracketed, 84 } 85 } else { 86 Include { 87 path: include.to_owned(), 88 kind: IncludeKind::Quoted, 89 } 90 } 91 }) 92 .collect(); 93 94 let mut outputs = Vec::new(); 95 for path in matches.get_many::<PathBuf>(OUTPUT).unwrap_or_default() { 96 outputs.push(if path.as_os_str() == "-" { 97 Output::Stdout 98 } else { 99 Output::File(path.clone()) 100 }); 101 } 102 if outputs.is_empty() { 103 outputs.push(Output::Stdout); 104 } 105 106 let mut cfg = Map::new(); 107 for arg in matches.get_many::<String>(CFG).unwrap_or_default() { 108 let (name, value) = cfg::parse.parse_str(arg).unwrap(); 109 cfg.entry(name).or_insert_with(Set::new).insert(value); 110 } 111 112 Opt { 113 input, 114 header, 115 cxx_impl_annotations, 116 include, 117 outputs, 118 cfg, 119 } 120} 121 122fn arg_input() -> Arg { 123 Arg::new(INPUT) 124 .help("Input Rust source file containing #[cxx::bridge].") 125 .required_unless_present_any(&[HEADER, HELP]) 126 .value_parser(ValueParser::path_buf()) 127} 128 129fn arg_cfg() -> Arg { 130 const HELP: &str = "\ 131Compilation configuration matching what will be used to build 132the Rust side of the bridge."; 133 let bool_cfgs = Arc::new(Mutex::new(Map::<String, bool>::new())); 134 Arg::new(CFG) 135 .long(CFG) 136 .num_args(1) 137 .value_name("name=\"value\" | name[=true] | name=false") 138 .action(ArgAction::Append) 139 .value_parser(move |arg: &str| match cfg::parse.parse_str(arg) { 140 Ok((_, CfgValue::Str(_))) => Ok(arg.to_owned()), 141 Ok((name, CfgValue::Bool(value))) => { 142 let mut bool_cfgs = bool_cfgs.lock().unwrap_or_else(PoisonError::into_inner); 143 if let Some(&prev) = bool_cfgs.get(&name) { 144 if prev != value { 145 return Err(format!("cannot have both {0}=false and {0}=true", name)); 146 } 147 } 148 bool_cfgs.insert(name, value); 149 Ok(arg.to_owned()) 150 } 151 Err(_) => Err("expected name=\"value\", name=true, or name=false".to_owned()), 152 }) 153 .help(HELP) 154} 155 156fn arg_cxx_impl_annotations() -> Arg { 157 const HELP: &str = "\ 158Optional annotation for implementations of C++ function wrappers 159that may be exposed to Rust. You may for example need to provide 160__declspec(dllexport) or __attribute__((visibility(\"default\"))) 161if Rust code from one shared object or executable depends on 162these C++ functions in another."; 163 Arg::new(CXX_IMPL_ANNOTATIONS) 164 .long(CXX_IMPL_ANNOTATIONS) 165 .num_args(1) 166 .value_name("annotation") 167 .value_parser(ValueParser::string()) 168 .help(HELP) 169} 170 171fn arg_header() -> Arg { 172 const HELP: &str = "\ 173Emit header with declarations only. Optional if using `-o` with 174a path ending in `.h`."; 175 Arg::new(HEADER).long(HEADER).num_args(0).help(HELP) 176} 177 178fn arg_help() -> Arg { 179 Arg::new(HELP) 180 .long(HELP) 181 .help("Print help information.") 182 .num_args(0) 183} 184 185fn arg_include() -> Arg { 186 const HELP: &str = "\ 187Any additional headers to #include. The cxxbridge tool does not 188parse or even require the given paths to exist; they simply go 189into the generated C++ code as #include lines."; 190 Arg::new(INCLUDE) 191 .long(INCLUDE) 192 .short('i') 193 .num_args(1) 194 .action(ArgAction::Append) 195 .value_parser(ValueParser::string()) 196 .help(HELP) 197} 198 199fn arg_output() -> Arg { 200 const HELP: &str = "\ 201Path of file to write as output. Output goes to stdout if -o is 202not specified."; 203 Arg::new(OUTPUT) 204 .long(OUTPUT) 205 .short('o') 206 .num_args(1) 207 .action(ArgAction::Append) 208 .value_parser(ValueParser::path_buf()) 209 .help(HELP) 210} 211 212fn arg_version() -> Arg { 213 Arg::new(VERSION) 214 .long(VERSION) 215 .help("Print version information.") 216 .action(ArgAction::Version) 217} 218