blob: c233ad77a968cfee159196e3f51e7913393d42e0 [file] [log] [blame]
David Tolnay39efd732020-08-30 13:21:51 -07001#[cfg(test)]
2#[path = "test.rs"]
3mod test;
4
David Tolnay9ef74ce2020-09-13 23:24:02 -04005use super::{Opt, Output};
David Tolnay700cd0c2020-10-28 12:40:27 -07006use crate::gen::include::Include;
7use crate::syntax::IncludeKind;
David Tolnay39fa3662020-07-30 20:23:18 -07008use clap::AppSettings;
9use std::ffi::{OsStr, OsString};
10use std::path::PathBuf;
11
12type App = clap::App<'static, 'static>;
13type Arg = clap::Arg<'static, 'static>;
14
15const USAGE: &str = "\
16 cxxbridge <input>.rs Emit .cc file for bridge to stdout
17 cxxbridge <input>.rs --header Emit .h file for bridge to stdout
David Tolnaye32c5022020-09-13 23:03:05 -040018 cxxbridge --header Emit \"rust/cxx.h\" header to stdout\
David Tolnay39fa3662020-07-30 20:23:18 -070019";
20
21const TEMPLATE: &str = "\
22{bin} {version}
23David Tolnay <dtolnay@gmail.com>
24https://github.com/dtolnay/cxx
25
26USAGE:
27 {usage}
28
29ARGS:
30{positionals}
31OPTIONS:
32{unified}\
33";
34
35fn app() -> App {
36 let mut app = App::new("cxxbridge")
37 .usage(USAGE)
38 .template(TEMPLATE)
39 .setting(AppSettings::NextLineHelp)
40 .arg(arg_input())
41 .arg(arg_cxx_impl_annotations())
42 .arg(arg_header())
43 .arg(arg_include())
David Tolnay9ef74ce2020-09-13 23:24:02 -040044 .arg(arg_output())
David Tolnay39fa3662020-07-30 20:23:18 -070045 .help_message("Print help information.")
46 .version_message("Print version information.");
47 if let Some(version) = option_env!("CARGO_PKG_VERSION") {
48 app = app.version(version);
49 }
50 app
51}
52
53const INPUT: &str = "input";
54const CXX_IMPL_ANNOTATIONS: &str = "cxx-impl-annotations";
55const HEADER: &str = "header";
56const INCLUDE: &str = "include";
David Tolnay9ef74ce2020-09-13 23:24:02 -040057const OUTPUT: &str = "output";
David Tolnay39fa3662020-07-30 20:23:18 -070058
59pub(super) fn from_args() -> Opt {
60 let matches = app().get_matches();
David Tolnay64276142020-09-24 10:43:02 -040061
62 let input = matches.value_of_os(INPUT).map(PathBuf::from);
63 let cxx_impl_annotations = matches.value_of(CXX_IMPL_ANNOTATIONS).map(str::to_owned);
64 let header = matches.is_present(HEADER);
65 let include = matches
66 .values_of(INCLUDE)
David Tolnay13213b72020-09-24 10:50:07 -040067 .unwrap_or_default()
David Tolnay700cd0c2020-10-28 12:40:27 -070068 .map(|include| {
69 if include.starts_with('<') && include.ends_with('>') {
70 Include {
71 path: include[1..include.len() - 1].to_owned(),
72 kind: IncludeKind::Bracketed,
73 }
74 } else {
75 Include {
76 path: include.to_owned(),
77 kind: IncludeKind::Quoted,
78 }
79 }
80 })
David Tolnay13213b72020-09-24 10:50:07 -040081 .collect();
David Tolnayf0277562020-09-24 10:46:14 -040082
83 let mut outputs = Vec::new();
84 for path in matches.values_of_os(OUTPUT).unwrap_or_default() {
85 outputs.push(if path == "-" {
86 Output::Stdout
87 } else {
88 Output::File(PathBuf::from(path))
89 });
90 }
91 if outputs.is_empty() {
92 outputs.push(Output::Stdout);
93 }
David Tolnay64276142020-09-24 10:43:02 -040094
David Tolnay39fa3662020-07-30 20:23:18 -070095 Opt {
David Tolnay64276142020-09-24 10:43:02 -040096 input,
David Tolnay64276142020-09-24 10:43:02 -040097 header,
David Tolnay3dafaed2021-02-25 20:42:28 -080098 cxx_impl_annotations,
David Tolnay64276142020-09-24 10:43:02 -040099 include,
David Tolnayf0277562020-09-24 10:46:14 -0400100 outputs,
David Tolnay39fa3662020-07-30 20:23:18 -0700101 }
102}
103
104fn validate_utf8(arg: &OsStr) -> Result<(), OsString> {
105 if arg.to_str().is_some() {
106 Ok(())
107 } else {
108 Err(OsString::from("invalid utf-8 sequence"))
109 }
110}
111
112fn arg_input() -> Arg {
113 Arg::with_name(INPUT)
114 .help("Input Rust source file containing #[cxx::bridge].")
115 .required_unless(HEADER)
116}
117
118fn arg_cxx_impl_annotations() -> Arg {
119 const HELP: &str = "\
120Optional annotation for implementations of C++ function wrappers
121that may be exposed to Rust. You may for example need to provide
122__declspec(dllexport) or __attribute__((visibility(\"default\")))
123if Rust code from one shared object or executable depends on
124these C++ functions in another.
125 ";
126 Arg::with_name(CXX_IMPL_ANNOTATIONS)
127 .long(CXX_IMPL_ANNOTATIONS)
128 .takes_value(true)
129 .value_name("annotation")
130 .validator_os(validate_utf8)
131 .help(HELP)
132}
133
134fn arg_header() -> Arg {
David Tolnaye2e47a32020-09-24 10:23:27 -0400135 const HELP: &str = "\
136Emit header with declarations only. Optional if using `-o` with
137a path ending in `.h`.
138 ";
139 Arg::with_name(HEADER).long(HEADER).help(HELP)
David Tolnay39fa3662020-07-30 20:23:18 -0700140}
141
142fn arg_include() -> Arg {
143 const HELP: &str = "\
144Any additional headers to #include. The cxxbridge tool does not
145parse or even require the given paths to exist; they simply go
146into the generated C++ code as #include lines.
147 ";
148 Arg::with_name(INCLUDE)
149 .long(INCLUDE)
150 .short("i")
151 .takes_value(true)
152 .multiple(true)
David Tolnay762f0412020-10-03 13:00:04 -0700153 .number_of_values(1)
David Tolnay39fa3662020-07-30 20:23:18 -0700154 .validator_os(validate_utf8)
155 .help(HELP)
156}
David Tolnay9ef74ce2020-09-13 23:24:02 -0400157
158fn arg_output() -> Arg {
159 const HELP: &str = "\
160Path of file to write as output. Output goes to stdout if -o is
161not specified.
162 ";
163 Arg::with_name(OUTPUT)
164 .long(OUTPUT)
165 .short("o")
166 .takes_value(true)
David Tolnayf0277562020-09-24 10:46:14 -0400167 .multiple(true)
David Tolnay762f0412020-10-03 13:00:04 -0700168 .number_of_values(1)
David Tolnay9ef74ce2020-09-13 23:24:02 -0400169 .validator_os(validate_utf8)
170 .help(HELP)
171}