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