blob: bd3bcfd5835399aacb569eed548f9662fc0745fd [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 Tolnay39fa3662020-07-30 20:23:18 -07006use clap::AppSettings;
7use std::ffi::{OsStr, OsString};
8use std::path::PathBuf;
9
10type App = clap::App<'static, 'static>;
11type Arg = clap::Arg<'static, 'static>;
12
13const USAGE: &str = "\
14 cxxbridge <input>.rs Emit .cc file for bridge to stdout
15 cxxbridge <input>.rs --header Emit .h file for bridge to stdout
David Tolnaye32c5022020-09-13 23:03:05 -040016 cxxbridge --header Emit \"rust/cxx.h\" header to stdout\
David Tolnay39fa3662020-07-30 20:23:18 -070017";
18
19const TEMPLATE: &str = "\
20{bin} {version}
21David Tolnay <dtolnay@gmail.com>
22https://github.com/dtolnay/cxx
23
24USAGE:
25 {usage}
26
27ARGS:
28{positionals}
29OPTIONS:
30{unified}\
31";
32
33fn app() -> App {
34 let mut app = App::new("cxxbridge")
35 .usage(USAGE)
36 .template(TEMPLATE)
37 .setting(AppSettings::NextLineHelp)
38 .arg(arg_input())
39 .arg(arg_cxx_impl_annotations())
40 .arg(arg_header())
41 .arg(arg_include())
David Tolnay9ef74ce2020-09-13 23:24:02 -040042 .arg(arg_output())
David Tolnay39fa3662020-07-30 20:23:18 -070043 .help_message("Print help information.")
44 .version_message("Print version information.");
45 if let Some(version) = option_env!("CARGO_PKG_VERSION") {
46 app = app.version(version);
47 }
48 app
49}
50
51const INPUT: &str = "input";
52const CXX_IMPL_ANNOTATIONS: &str = "cxx-impl-annotations";
53const HEADER: &str = "header";
54const INCLUDE: &str = "include";
David Tolnay9ef74ce2020-09-13 23:24:02 -040055const OUTPUT: &str = "output";
David Tolnay39fa3662020-07-30 20:23:18 -070056
57pub(super) fn from_args() -> Opt {
58 let matches = app().get_matches();
David Tolnay64276142020-09-24 10:43:02 -040059
60 let input = matches.value_of_os(INPUT).map(PathBuf::from);
61 let cxx_impl_annotations = matches.value_of(CXX_IMPL_ANNOTATIONS).map(str::to_owned);
62 let header = matches.is_present(HEADER);
63 let include = matches
64 .values_of(INCLUDE)
David Tolnay13213b72020-09-24 10:50:07 -040065 .unwrap_or_default()
66 .map(str::to_owned)
67 .collect();
David Tolnayf0277562020-09-24 10:46:14 -040068
69 let mut outputs = Vec::new();
70 for path in matches.values_of_os(OUTPUT).unwrap_or_default() {
71 outputs.push(if path == "-" {
72 Output::Stdout
73 } else {
74 Output::File(PathBuf::from(path))
75 });
76 }
77 if outputs.is_empty() {
78 outputs.push(Output::Stdout);
79 }
David Tolnay64276142020-09-24 10:43:02 -040080
David Tolnay39fa3662020-07-30 20:23:18 -070081 Opt {
David Tolnay64276142020-09-24 10:43:02 -040082 input,
83 cxx_impl_annotations,
84 header,
85 include,
David Tolnayf0277562020-09-24 10:46:14 -040086 outputs,
David Tolnay39fa3662020-07-30 20:23:18 -070087 }
88}
89
90fn validate_utf8(arg: &OsStr) -> Result<(), OsString> {
91 if arg.to_str().is_some() {
92 Ok(())
93 } else {
94 Err(OsString::from("invalid utf-8 sequence"))
95 }
96}
97
98fn arg_input() -> Arg {
99 Arg::with_name(INPUT)
100 .help("Input Rust source file containing #[cxx::bridge].")
101 .required_unless(HEADER)
102}
103
104fn arg_cxx_impl_annotations() -> Arg {
105 const HELP: &str = "\
106Optional annotation for implementations of C++ function wrappers
107that may be exposed to Rust. You may for example need to provide
108__declspec(dllexport) or __attribute__((visibility(\"default\")))
109if Rust code from one shared object or executable depends on
110these C++ functions in another.
111 ";
112 Arg::with_name(CXX_IMPL_ANNOTATIONS)
113 .long(CXX_IMPL_ANNOTATIONS)
114 .takes_value(true)
115 .value_name("annotation")
116 .validator_os(validate_utf8)
117 .help(HELP)
118}
119
120fn arg_header() -> Arg {
David Tolnaye2e47a32020-09-24 10:23:27 -0400121 const HELP: &str = "\
122Emit header with declarations only. Optional if using `-o` with
123a path ending in `.h`.
124 ";
125 Arg::with_name(HEADER).long(HEADER).help(HELP)
David Tolnay39fa3662020-07-30 20:23:18 -0700126}
127
128fn arg_include() -> Arg {
129 const HELP: &str = "\
130Any additional headers to #include. The cxxbridge tool does not
131parse or even require the given paths to exist; they simply go
132into the generated C++ code as #include lines.
133 ";
134 Arg::with_name(INCLUDE)
135 .long(INCLUDE)
136 .short("i")
137 .takes_value(true)
138 .multiple(true)
David Tolnay762f0412020-10-03 13:00:04 -0700139 .number_of_values(1)
David Tolnay39fa3662020-07-30 20:23:18 -0700140 .validator_os(validate_utf8)
141 .help(HELP)
142}
David Tolnay9ef74ce2020-09-13 23:24:02 -0400143
144fn arg_output() -> Arg {
145 const HELP: &str = "\
146Path of file to write as output. Output goes to stdout if -o is
147not specified.
148 ";
149 Arg::with_name(OUTPUT)
150 .long(OUTPUT)
151 .short("o")
152 .takes_value(true)
David Tolnayf0277562020-09-24 10:46:14 -0400153 .multiple(true)
David Tolnay762f0412020-10-03 13:00:04 -0700154 .number_of_values(1)
David Tolnay9ef74ce2020-09-13 23:24:02 -0400155 .validator_os(validate_utf8)
156 .help(HELP)
157}