xref: /third_party/rust/crates/cxx/gen/cmd/src/app.rs (revision 33d722a9)
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