1 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4 // option. This file may not be copied, modified, or distributed
5 // except according to those terms.
6
7 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
8 #![warn(clippy::pedantic)]
9
10 use bindgen::Builder;
11 use serde_derive::Deserialize;
12 use std::collections::HashMap;
13 use std::env;
14 use std::fs;
15 use std::path::{Path, PathBuf};
16 use std::process::Command;
17
18 const BINDINGS_DIR: &str = "bindings";
19 const BINDINGS_CONFIG: &str = "bindings.toml";
20
21 // This is the format of a single section of the configuration file.
22 #[derive(Deserialize)]
23 struct Bindings {
24 /// types that are explicitly included
25 #[serde(default)]
26 types: Vec<String>,
27 /// functions that are explicitly included
28 #[serde(default)]
29 functions: Vec<String>,
30 /// variables (and `#define`s) that are explicitly included
31 #[serde(default)]
32 variables: Vec<String>,
33 /// types that should be explicitly marked as opaque
34 #[serde(default)]
35 opaque: Vec<String>,
36 /// enumerations that are turned into a module (without this, the enum is
37 /// mapped using the default, which means that the individual values are
38 /// formed with an underscore as <enum_type>_<enum_value_name>).
39 #[serde(default)]
40 enums: Vec<String>,
41
42 /// Any item that is specifically excluded; if none of the types, functions,
43 /// or variables fields are specified, everything defined will be mapped,
44 /// so this can be used to limit that.
45 #[serde(default)]
46 exclude: Vec<String>,
47
48 /// Whether the file is to be interpreted as C++
49 #[serde(default)]
50 cplusplus: bool,
51 }
52
is_debug() -> bool53 fn is_debug() -> bool {
54 env::var("DEBUG")
55 .map(|d| d.parse::<bool>().unwrap_or(false))
56 .unwrap_or(false)
57 }
58
59 // bindgen needs access to libclang.
60 // On windows, this doesn't just work, you have to set LIBCLANG_PATH.
61 // Rather than download the 400Mb+ files, like gecko does, let's just reuse their work.
setup_clang()62 fn setup_clang() {
63 if env::consts::OS != "windows" {
64 return;
65 }
66 println!("rerun-if-env-changed=LIBCLANG_PATH");
67 println!("rerun-if-env-changed=MOZBUILD_STATE_PATH");
68 if env::var("LIBCLANG_PATH").is_ok() {
69 return;
70 }
71 let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") {
72 PathBuf::from(dir.trim())
73 } else {
74 eprintln!("warning: Building without a gecko setup is not likely to work.");
75 eprintln!(" A working libclang is needed to build neqo.");
76 eprintln!(" Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set.");
77 eprintln!();
78 eprintln!(" We recommend checking out https://github.com/mozilla/gecko-dev");
79 eprintln!(" Then run `./mach bootstrap` which will retrieve clang.");
80 eprintln!(" Make sure to export MOZBUILD_STATE_PATH when building.");
81 return;
82 };
83 let libclang_dir = mozbuild_root.join("clang").join("lib");
84 if libclang_dir.is_dir() {
85 env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
86 println!("rustc-env:LIBCLANG_PATH={}", libclang_dir.to_str().unwrap());
87 } else {
88 println!("warning: LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko");
89 }
90 }
91
nss_dir() -> PathBuf92 fn nss_dir() -> PathBuf {
93 let dir = if let Ok(dir) = env::var("NSS_DIR") {
94 PathBuf::from(dir.trim())
95 } else {
96 let out_dir = env::var("OUT_DIR").unwrap();
97 let dir = Path::new(&out_dir).join("nss");
98 if !dir.exists() {
99 Command::new("hg")
100 .args(&[
101 "clone",
102 "https://hg.mozilla.org/projects/nss",
103 dir.to_str().unwrap(),
104 ])
105 .status()
106 .expect("can't clone nss");
107 }
108 let nspr_dir = Path::new(&out_dir).join("nspr");
109 if !nspr_dir.exists() {
110 Command::new("hg")
111 .args(&[
112 "clone",
113 "https://hg.mozilla.org/projects/nspr",
114 nspr_dir.to_str().unwrap(),
115 ])
116 .status()
117 .expect("can't clone nspr");
118 }
119 dir
120 };
121 assert!(dir.is_dir());
122 // Note that this returns a relative path because UNC
123 // paths on windows cause certain tools to explode.
124 dir
125 }
126
get_bash() -> PathBuf127 fn get_bash() -> PathBuf {
128 // When running under MOZILLABUILD, we need to make sure not to invoke
129 // another instance of bash that might be sitting around (like WSL).
130 match env::var("MOZILLABUILD") {
131 Ok(d) => PathBuf::from(d).join("msys").join("bin").join("bash.exe"),
132 Err(_) => PathBuf::from("bash"),
133 }
134 }
135
build_nss(dir: PathBuf)136 fn build_nss(dir: PathBuf) {
137 let mut build_nss = vec![
138 String::from("./build.sh"),
139 String::from("-Ddisable_tests=1"),
140 ];
141 if is_debug() {
142 build_nss.push(String::from("--static"));
143 } else {
144 build_nss.push(String::from("-o"));
145 }
146 if let Ok(d) = env::var("NSS_JOBS") {
147 build_nss.push(String::from("-j"));
148 build_nss.push(d);
149 }
150 let status = Command::new(get_bash())
151 .args(build_nss)
152 .current_dir(dir)
153 .status()
154 .expect("couldn't start NSS build");
155 assert!(status.success(), "NSS build failed");
156 }
157
dynamic_link()158 fn dynamic_link() {
159 let libs = if env::consts::OS == "windows" {
160 &["nssutil3.dll", "nss3.dll", "ssl3.dll"]
161 } else {
162 &["nssutil3", "nss3", "ssl3"]
163 };
164 dynamic_link_both(libs);
165 }
166
dynamic_link_both(extra_libs: &[&str])167 fn dynamic_link_both(extra_libs: &[&str]) {
168 let nspr_libs = if env::consts::OS == "windows" {
169 &["libplds4", "libplc4", "libnspr4"]
170 } else {
171 &["plds4", "plc4", "nspr4"]
172 };
173 for lib in nspr_libs.iter().chain(extra_libs) {
174 println!("cargo:rustc-link-lib=dylib={}", lib);
175 }
176 }
177
static_link()178 fn static_link() {
179 let mut static_libs = vec![
180 "certdb",
181 "certhi",
182 "cryptohi",
183 "freebl",
184 "nss_static",
185 "nssb",
186 "nssdev",
187 "nsspki",
188 "nssutil",
189 "pk11wrap",
190 "pkcs12",
191 "pkcs7",
192 "smime",
193 "softokn_static",
194 "ssl",
195 ];
196 if env::consts::OS != "macos" {
197 static_libs.push("sqlite");
198 }
199 for lib in static_libs {
200 println!("cargo:rustc-link-lib=static={}", lib);
201 }
202
203 // Dynamic libs that aren't transitively included by NSS libs.
204 let mut other_libs = Vec::new();
205 if env::consts::OS != "windows" {
206 other_libs.extend_from_slice(&["pthread", "dl", "c", "z"]);
207 }
208 if env::consts::OS == "macos" {
209 other_libs.push("sqlite3");
210 }
211 dynamic_link_both(&other_libs);
212 }
213
get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf>214 fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
215 let nsprinclude = nsstarget.join("include").join("nspr");
216 let nssinclude = nssdist.join("public").join("nss");
217 let includes = vec![nsprinclude, nssinclude];
218 for i in &includes {
219 println!("cargo:include={}", i.to_str().unwrap());
220 }
221 includes
222 }
223
build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool)224 fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool) {
225 let suffix = if bindings.cplusplus { ".hpp" } else { ".h" };
226 let header_path = PathBuf::from(BINDINGS_DIR).join(String::from(base) + suffix);
227 let header = header_path.to_str().unwrap();
228 let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(String::from(base) + ".rs");
229
230 println!("cargo:rerun-if-changed={}", header);
231
232 let mut builder = Builder::default().header(header);
233 builder = builder.generate_comments(false);
234 builder = builder.size_t_is_usize(true);
235
236 builder = builder.clang_arg("-v");
237
238 if !gecko {
239 builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT");
240 if env::consts::OS == "windows" {
241 builder = builder.clang_arg("-DWIN");
242 } else if env::consts::OS == "macos" {
243 builder = builder.clang_arg("-DDARWIN");
244 } else if env::consts::OS == "linux" {
245 builder = builder.clang_arg("-DLINUX");
246 } else if env::consts::OS == "android" {
247 builder = builder.clang_arg("-DLINUX");
248 builder = builder.clang_arg("-DANDROID");
249 }
250 if bindings.cplusplus {
251 builder = builder.clang_args(&["-x", "c++", "-std=c++11"]);
252 }
253 }
254
255 builder = builder.clang_args(flags);
256
257 // Apply the configuration.
258 for v in &bindings.types {
259 builder = builder.whitelist_type(v);
260 }
261 for v in &bindings.functions {
262 builder = builder.whitelist_function(v);
263 }
264 for v in &bindings.variables {
265 builder = builder.whitelist_var(v);
266 }
267 for v in &bindings.exclude {
268 builder = builder.blacklist_item(v);
269 }
270 for v in &bindings.opaque {
271 builder = builder.opaque_type(v);
272 }
273 for v in &bindings.enums {
274 builder = builder.constified_enum_module(v);
275 }
276
277 let bindings = builder.generate().expect("unable to generate bindings");
278 bindings
279 .write_to_file(out)
280 .expect("couldn't write bindings");
281 }
282
setup_standalone() -> Vec<String>283 fn setup_standalone() -> Vec<String> {
284 setup_clang();
285
286 println!("cargo:rerun-if-env-changed=NSS_DIR");
287 let nss = nss_dir();
288 build_nss(nss.clone());
289
290 // $NSS_DIR/../dist/
291 let nssdist = nss.parent().unwrap().join("dist");
292 println!("cargo:rerun-if-env-changed=NSS_TARGET");
293 let nsstarget = env::var("NSS_TARGET")
294 .unwrap_or_else(|_| fs::read_to_string(nssdist.join("latest")).unwrap());
295 let nsstarget = nssdist.join(nsstarget.trim());
296
297 let includes = get_includes(&nsstarget, &nssdist);
298
299 let nsslibdir = nsstarget.join("lib");
300 println!(
301 "cargo:rustc-link-search=native={}",
302 nsslibdir.to_str().unwrap()
303 );
304 if is_debug() {
305 static_link();
306 } else {
307 dynamic_link();
308 }
309
310 let mut flags: Vec<String> = Vec::new();
311 for i in includes {
312 flags.push(String::from("-I") + i.to_str().unwrap());
313 }
314
315 flags
316 }
317
setup_for_gecko() -> Vec<String>318 fn setup_for_gecko() -> Vec<String> {
319 let mut flags: Vec<String> = Vec::new();
320
321 let fold_libs = env::var("MOZ_FOLD_LIBS").unwrap_or_default() == "1";
322 let libs = if fold_libs {
323 vec!["nss3"]
324 } else {
325 vec!["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"]
326 };
327
328 for lib in &libs {
329 println!("cargo:rustc-link-lib=dylib={}", lib);
330 }
331
332 if let Some(path) = env::var_os("MOZ_TOPOBJDIR").map(PathBuf::from) {
333 if fold_libs {
334 println!(
335 "cargo:rustc-link-search=native={}",
336 path.join("security").to_str().unwrap()
337 );
338 } else {
339 println!(
340 "cargo:rustc-link-search=native={}",
341 path.join("dist").join("bin").to_str().unwrap()
342 );
343 let nsslib_path = path.join("security").join("nss").join("lib");
344 println!(
345 "cargo:rustc-link-search=native={}",
346 nsslib_path.join("nss").join("nss_nss3").to_str().unwrap()
347 );
348 println!(
349 "cargo:rustc-link-search=native={}",
350 nsslib_path.join("ssl").join("ssl_ssl3").to_str().unwrap()
351 );
352 println!(
353 "cargo:rustc-link-search=native={}",
354 path.join("config")
355 .join("external")
356 .join("nspr")
357 .join("pr")
358 .to_str()
359 .unwrap()
360 );
361 }
362
363 let flags_path = path.join("netwerk/socket/neqo/extra-bindgen-flags");
364
365 println!("cargo:rerun-if-changed={}", flags_path.to_str().unwrap());
366 flags = fs::read_to_string(flags_path)
367 .expect("Failed to read extra-bindgen-flags file")
368 .split_whitespace()
369 .map(std::borrow::ToOwned::to_owned)
370 .collect();
371
372 flags.push(String::from("-include"));
373 flags.push(
374 path.join("dist")
375 .join("include")
376 .join("mozilla-config.h")
377 .to_str()
378 .unwrap()
379 .to_string(),
380 );
381 } else {
382 println!("cargo:warning=MOZ_TOPOBJDIR should be set by default, otherwise the build is not guaranteed to finish.");
383 }
384 flags
385 }
386
main()387 fn main() {
388 let flags = if cfg!(feature = "gecko") {
389 setup_for_gecko()
390 } else {
391 setup_standalone()
392 };
393
394 let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG);
395 println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap());
396 let config = fs::read_to_string(config_file).expect("unable to read binding configuration");
397 let config: HashMap<String, Bindings> = ::toml::from_str(&config).unwrap();
398
399 for (k, v) in &config {
400 build_bindings(k, v, &flags[..], cfg!(feature = "gecko"));
401 }
402 }
403