• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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