1 #[cfg(test)]
2 #[path = "test.rs"]
3 mod test;
4
5 use super::{Opt, Output};
6 use crate::cfg::{self, CfgValue};
7 use crate::gen::include::Include;
8 use crate::syntax::IncludeKind;
9 use clap::builder::{ArgAction, ValueParser};
10 use clap::{Arg, Command};
11 use std::collections::{BTreeMap as Map, BTreeSet as Set};
12 use std::path::PathBuf;
13 use std::process;
14 use std::sync::{Arc, Mutex, PoisonError};
15 use syn::parse::Parser;
16
17 const 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
23 const TEMPLATE: &str = "\
24 {bin} {version}
25 David Tolnay <dtolnay@gmail.com>
26 https://github.com/dtolnay/cxx
27
28 {usage-heading}
29 {usage}
30
31 {all-args}\
32 ";
33
app() -> Command34 fn 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
54 const INPUT: &str = "input";
55 const CFG: &str = "cfg";
56 const CXX_IMPL_ANNOTATIONS: &str = "cxx-impl-annotations";
57 const HELP: &str = "help";
58 const HEADER: &str = "header";
59 const INCLUDE: &str = "include";
60 const OUTPUT: &str = "output";
61 const VERSION: &str = "version";
62
from_args() -> Opt63 pub(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
arg_input() -> Arg122 fn 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
arg_cfg() -> Arg129 fn arg_cfg() -> Arg {
130 const HELP: &str = "\
131 Compilation configuration matching what will be used to build
132 the 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
arg_cxx_impl_annotations() -> Arg156 fn arg_cxx_impl_annotations() -> Arg {
157 const HELP: &str = "\
158 Optional annotation for implementations of C++ function wrappers
159 that may be exposed to Rust. You may for example need to provide
160 __declspec(dllexport) or __attribute__((visibility(\"default\")))
161 if Rust code from one shared object or executable depends on
162 these 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
arg_header() -> Arg171 fn arg_header() -> Arg {
172 const HELP: &str = "\
173 Emit header with declarations only. Optional if using `-o` with
174 a path ending in `.h`.";
175 Arg::new(HEADER).long(HEADER).num_args(0).help(HELP)
176 }
177
arg_help() -> Arg178 fn arg_help() -> Arg {
179 Arg::new(HELP)
180 .long(HELP)
181 .help("Print help information.")
182 .num_args(0)
183 }
184
arg_include() -> Arg185 fn arg_include() -> Arg {
186 const HELP: &str = "\
187 Any additional headers to #include. The cxxbridge tool does not
188 parse or even require the given paths to exist; they simply go
189 into 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
arg_output() -> Arg199 fn arg_output() -> Arg {
200 const HELP: &str = "\
201 Path of file to write as output. Output goes to stdout if -o is
202 not 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
arg_version() -> Arg212 fn arg_version() -> Arg {
213 Arg::new(VERSION)
214 .long(VERSION)
215 .help("Print version information.")
216 .action(ArgAction::Version)
217 }
218