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