1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use std::env;
6 use std::io;
7 use std::path::{Path, PathBuf};
8 use std::str::FromStr;
9 
10 extern crate clap;
11 #[macro_use]
12 extern crate log;
13 extern crate proc_macro2;
14 #[macro_use]
15 extern crate serde;
16 extern crate serde_json;
17 #[macro_use]
18 extern crate quote;
19 #[macro_use]
20 extern crate syn;
21 extern crate toml;
22 
23 use clap::{App, Arg, ArgMatches};
24 
25 mod bindgen;
26 mod logging;
27 
28 use crate::bindgen::{Bindings, Builder, Cargo, Config, Error, Profile, Style};
29 
apply_config_overrides<'a>(config: &mut Config, matches: &ArgMatches<'a>)30 fn apply_config_overrides<'a>(config: &mut Config, matches: &ArgMatches<'a>) {
31     // We allow specifying a language to override the config default. This is
32     // used by compile-tests.
33     if let Some(lang) = matches.value_of("lang") {
34         config.language = match lang.parse() {
35             Ok(lang) => lang,
36             Err(reason) => {
37                 error!("{}", reason);
38                 return;
39             }
40         }
41     }
42 
43     if matches.is_present("cpp-compat") {
44         config.cpp_compat = true;
45     }
46 
47     if matches.is_present("only-target-dependencies") {
48         config.only_target_dependencies = true;
49     }
50 
51     if let Some(style) = matches.value_of("style") {
52         config.style = match style {
53             "Both" => Style::Both,
54             "both" => Style::Both,
55             "Tag" => Style::Tag,
56             "tag" => Style::Tag,
57             "Type" => Style::Type,
58             "type" => Style::Type,
59             _ => {
60                 error!("Unknown style specified.");
61                 return;
62             }
63         }
64     }
65 
66     if let Some(profile) = matches.value_of("profile") {
67         config.parse.expand.profile = match Profile::from_str(profile) {
68             Ok(p) => p,
69             Err(e) => {
70                 error!("{}", e);
71                 return;
72             }
73         }
74     }
75 
76     if matches.is_present("d") {
77         config.parse.parse_deps = true;
78     }
79 }
80 
load_bindings<'a>(input: &Path, matches: &ArgMatches<'a>) -> Result<Bindings, Error>81 fn load_bindings<'a>(input: &Path, matches: &ArgMatches<'a>) -> Result<Bindings, Error> {
82     // If a file is specified then we load it as a single source
83     if !input.is_dir() {
84         // Load any config specified or search in the input directory
85         let mut config = match matches.value_of("config") {
86             Some(c) => Config::from_file(c).unwrap(),
87             None => Config::from_root_or_default(input),
88         };
89 
90         apply_config_overrides(&mut config, matches);
91 
92         return Builder::new()
93             .with_config(config)
94             .with_src(input)
95             .generate();
96     }
97 
98     // We have to load a whole crate, so we use cargo to gather metadata
99     let lib = Cargo::load(
100         input,
101         matches.value_of("lockfile"),
102         matches.value_of("crate"),
103         true,
104         matches.is_present("clean"),
105         matches.is_present("only-target-dependencies"),
106         matches.value_of("metadata").map(Path::new),
107     )?;
108 
109     // Load any config specified or search in the binding crate directory
110     let mut config = match matches.value_of("config") {
111         Some(c) => Config::from_file(c).unwrap(),
112         None => {
113             let binding_crate_dir = lib.find_crate_dir(&lib.binding_crate_ref());
114 
115             if let Some(binding_crate_dir) = binding_crate_dir {
116                 Config::from_root_or_default(&binding_crate_dir)
117             } else {
118                 // This shouldn't happen
119                 Config::from_root_or_default(input)
120             }
121         }
122     };
123 
124     apply_config_overrides(&mut config, matches);
125 
126     Builder::new()
127         .with_config(config)
128         .with_cargo(lib)
129         .generate()
130 }
131 
main()132 fn main() {
133     let matches = App::new("cbindgen")
134         .version(bindgen::VERSION)
135         .about("Generate C bindings for a Rust library")
136         .arg(
137             Arg::with_name("v")
138                 .short("v")
139                 .multiple(true)
140                 .help("Enable verbose logging"),
141         )
142         .arg(
143             Arg::with_name("verify")
144                 .long("verify")
145                 .help("Generate bindings and compare it to the existing bindings file and error if they are different"),
146         )
147         .arg(
148             Arg::with_name("config")
149                 .short("c")
150                 .long("config")
151                 .value_name("PATH")
152                 .help("Specify path to a `cbindgen.toml` config to use"),
153         )
154         .arg(
155             Arg::with_name("lang")
156                 .short("l")
157                 .long("lang")
158                 .value_name("LANGUAGE")
159                 .help("Specify the language to output bindings in")
160                 .possible_values(&["c++", "C++", "c", "C", "cython", "Cython"]),
161         )
162         .arg(
163             Arg::with_name("cpp-compat")
164                 .long("cpp-compat")
165                 .help("Whether to add C++ compatibility to generated C bindings")
166         )
167         .arg(
168             Arg::with_name("only-target-dependencies")
169                 .long("only-target-dependencies")
170                 .help("Only fetch dependencies needed by the target platform. \
171                     The target platform defaults to the host platform; set TARGET to override.")
172         )
173         .arg(
174             Arg::with_name("style")
175                 .short("s")
176                 .long("style")
177                 .value_name("STYLE")
178                 .help("Specify the declaration style to use for bindings")
179                 .possible_values(&["Both", "both", "Tag", "tag", "Type", "type"]),
180         )
181         .arg(
182             Arg::with_name("d")
183                 .short("d")
184                 .long("parse-dependencies")
185                 .help("Whether to parse dependencies when generating bindings"),
186         )
187         .arg(
188             Arg::with_name("clean")
189                 .long("clean")
190                 .help(
191                     "Whether to use a new temporary directory for expanding macros. \
192                     Affects performance, but might be required in certain build processes.")
193                 .required(false)
194         )
195         .arg(
196             Arg::with_name("INPUT")
197                 .help(
198                     "A crate directory or source file to generate bindings for. \
199                     In general this is the folder where the Cargo.toml file of \
200                     source Rust library resides.")
201                 .required(false)
202                 .index(1),
203         )
204         .arg(
205             Arg::with_name("crate")
206                 .long("crate")
207                 .value_name("CRATE_NAME")
208                 .help(
209                     "If generating bindings for a crate, \
210                      the specific crate to generate bindings for",
211                 )
212                 .required(false),
213         )
214         .arg(
215             Arg::with_name("out")
216                 .short("o")
217                 .long("output")
218                 .value_name("PATH")
219                 .help("The file to output the bindings to")
220                 .required(false),
221         )
222         .arg(
223             Arg::with_name("lockfile")
224                 .long("lockfile")
225                 .value_name("PATH")
226                 .help(
227                     "Specify the path to the Cargo.lock file explicitly. If this \
228                     is not specified, the Cargo.lock file is searched for in the \
229                     same folder as the Cargo.toml file. This option is useful for \
230                     projects that use workspaces.")
231                 .required(false),
232         )
233         .arg(
234             Arg::with_name("metadata")
235                 .long("metadata")
236                 .value_name("PATH")
237                 .help(
238                     "Specify the path to the output of a `cargo metadata` \
239                      command that allows to get dependency information. \
240                      This is useful because cargo metadata may be the longest \
241                      part of cbindgen runtime, and you may want to share it \
242                      across cbindgen invocations. By default cbindgen will run \
243                      `cargo metadata --all-features --format-version 1 \
244                       --manifest-path <path/to/crate/Cargo.toml>"
245                 )
246                 .required(false),
247         )
248         .arg(
249             Arg::with_name("profile")
250                 .long("profile")
251                 .value_name("PROFILE")
252                 .help(
253                     "Specify the profile to use when expanding macros. \
254                      Has no effect otherwise."
255                 )
256                 .possible_values(&["Debug", "debug", "Release", "release"]),
257         )
258         .arg(
259             Arg::with_name("quiet")
260                 .short("q")
261                 .long("quiet")
262                 .help("Report errors only (overrides verbosity options).")
263                 .required(false),
264         )
265         .get_matches();
266 
267     if !matches.is_present("out") && matches.is_present("verify") {
268         error!(
269             "Cannot verify bindings against `stdout`, please specify a file to compare against."
270         );
271         std::process::exit(2);
272     }
273 
274     // Initialize logging
275     if matches.is_present("quiet") {
276         logging::ErrorLogger::init().unwrap();
277     } else {
278         match matches.occurrences_of("v") {
279             0 => logging::WarnLogger::init().unwrap(),
280             1 => logging::InfoLogger::init().unwrap(),
281             _ => logging::TraceLogger::init().unwrap(),
282         }
283     }
284 
285     // Find the input directory
286     let input = match matches.value_of("INPUT") {
287         Some(input) => PathBuf::from(input),
288         None => env::current_dir().unwrap(),
289     };
290 
291     let bindings = match load_bindings(&input, &matches) {
292         Ok(bindings) => bindings,
293         Err(msg) => {
294             error!("{}", msg);
295             error!("Couldn't generate bindings for {}.", input.display());
296             std::process::exit(1);
297         }
298     };
299 
300     // Write the bindings file
301     match matches.value_of("out") {
302         Some(file) => {
303             let changed = bindings.write_to_file(file);
304 
305             if matches.is_present("verify") && changed {
306                 error!("Bindings changed: {}", file);
307                 std::process::exit(2);
308             }
309         }
310         _ => {
311             bindings.write(io::stdout());
312         }
313     }
314 }
315