Source code

Revision control

Other Tools

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 https://mozilla.org/MPL/2.0/. */
4
5
use super::PYTHON;
6
use bindgen::{Builder, CodegenConfig};
7
use regex::Regex;
8
use std::cmp;
9
use std::collections::HashSet;
10
use std::env;
11
use std::fs::{self, File};
12
use std::io::{Read, Write};
13
use std::path::{Path, PathBuf};
14
use std::process::{exit, Command};
15
use std::slice;
16
use std::sync::Mutex;
17
use std::time::SystemTime;
18
use toml;
19
use toml::value::Table;
20
21
lazy_static! {
22
static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko");
23
}
24
25
const STRUCTS_FILE: &'static str = "structs.rs";
26
27
fn read_config(path: &PathBuf) -> Table {
28
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
29
update_last_modified(&path);
30
31
let mut contents = String::new();
32
File::open(path)
33
.expect("Failed to open config file")
34
.read_to_string(&mut contents)
35
.expect("Failed to read config file");
36
match toml::from_str::<Table>(&contents) {
37
Ok(result) => result,
38
Err(e) => panic!("Failed to parse config file: {}", e),
39
}
40
}
41
42
lazy_static! {
43
static ref CONFIG: Table = {
44
// Load Gecko's binding generator config from the source tree.
45
let path = PathBuf::from(env::var_os("MOZ_SRC").unwrap())
46
.join("layout/style/ServoBindings.toml");
47
read_config(&path)
48
};
49
static ref BUILD_CONFIG: Table = {
50
// Load build-specific config overrides.
51
let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap())
52
.join("layout/style/bindgen.toml");
53
read_config(&path)
54
};
55
static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap();
56
static ref DISTDIR_PATH: PathBuf = {
57
let path = PathBuf::from(env::var_os("MOZ_DIST").unwrap());
58
if !path.is_absolute() || !path.is_dir() {
59
panic!("MOZ_DIST must be an absolute directory, was: {}", path.display());
60
}
61
path
62
};
63
static ref SEARCH_PATHS: Vec<PathBuf> = vec![
64
DISTDIR_PATH.join("include"),
65
DISTDIR_PATH.join("include/nspr"),
66
];
67
static ref ADDED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
68
static ref LAST_MODIFIED: Mutex<SystemTime> =
69
Mutex::new(get_modified_time(&env::current_exe().unwrap())
70
.expect("Failed to get modified time of executable"));
71
}
72
73
fn get_modified_time(file: &Path) -> Option<SystemTime> {
74
file.metadata().and_then(|m| m.modified()).ok()
75
}
76
77
fn update_last_modified(file: &Path) {
78
let modified = get_modified_time(file).expect("Couldn't get file modification time");
79
let mut last_modified = LAST_MODIFIED.lock().unwrap();
80
*last_modified = cmp::max(modified, *last_modified);
81
}
82
83
fn search_include(name: &str) -> Option<PathBuf> {
84
for path in SEARCH_PATHS.iter() {
85
let file = path.join(name);
86
if file.is_file() {
87
update_last_modified(&file);
88
return Some(file);
89
}
90
}
91
None
92
}
93
94
fn add_headers_recursively(path: PathBuf, added_paths: &mut HashSet<PathBuf>) {
95
if added_paths.contains(&path) {
96
return;
97
}
98
let mut file = File::open(&path).unwrap();
99
let mut content = String::new();
100
file.read_to_string(&mut content).unwrap();
101
added_paths.insert(path);
102
// Find all includes and add them recursively
103
for cap in INCLUDE_RE.captures_iter(&content) {
104
if let Some(path) = search_include(cap.get(1).unwrap().as_str()) {
105
add_headers_recursively(path, added_paths);
106
}
107
}
108
}
109
110
fn add_include(name: &str) -> String {
111
let mut added_paths = ADDED_PATHS.lock().unwrap();
112
let file = match search_include(name) {
113
Some(file) => file,
114
None => panic!("Include not found: {}", name),
115
};
116
let result = String::from(file.to_str().unwrap());
117
add_headers_recursively(file, &mut *added_paths);
118
result
119
}
120
121
trait BuilderExt {
122
fn get_initial_builder() -> Builder;
123
fn include<T: Into<String>>(self, file: T) -> Builder;
124
}
125
126
impl BuilderExt for Builder {
127
fn get_initial_builder() -> Builder {
128
use bindgen::RustTarget;
129
130
// Disable rust unions, because we replace some types inside of
131
// them.
132
let mut builder = Builder::default()
133
.rust_target(RustTarget::Stable_1_25)
134
.disable_untagged_union();
135
136
let rustfmt_path = env::var_os("RUSTFMT")
137
// This can be replaced with
138
// > .filter(|p| !p.is_empty()).map(PathBuf::from)
139
// once we can use 1.27+.
140
.and_then(|p| {
141
if p.is_empty() {
142
None
143
} else {
144
Some(PathBuf::from(p))
145
}
146
});
147
if let Some(path) = rustfmt_path {
148
builder = builder.with_rustfmt(path);
149
}
150
151
for dir in SEARCH_PATHS.iter() {
152
builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap());
153
}
154
155
builder = builder.include(add_include("mozilla-config.h"));
156
157
if env::var("CARGO_FEATURE_GECKO_DEBUG").is_ok() {
158
builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1");
159
}
160
161
let build_config = BUILD_CONFIG["build"]
162
.as_table()
163
.expect("Malformed config file");
164
let extra_bindgen_flags = build_config["args"].as_array().unwrap().as_slice();
165
for item in extra_bindgen_flags.iter() {
166
builder = builder.clang_arg(item.as_str().expect("Expect string in list"));
167
}
168
169
builder
170
}
171
fn include<T: Into<String>>(self, file: T) -> Builder {
172
self.clang_arg("-include").clang_arg(file)
173
}
174
}
175
176
struct Fixup {
177
pat: String,
178
rep: String,
179
}
180
181
fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) {
182
let out_file = OUTDIR_PATH.join(file);
183
if let Some(modified) = get_modified_time(&out_file) {
184
// Don't generate the file if nothing it depends on was modified.
185
let last_modified = LAST_MODIFIED.lock().unwrap();
186
if *last_modified <= modified {
187
return;
188
}
189
}
190
let command_line_opts = builder.command_line_flags();
191
let result = builder.generate();
192
let mut result = match result {
193
Ok(bindings) => bindings.to_string(),
194
Err(_) => {
195
panic!(
196
"Failed to generate bindings, flags: {:?}",
197
command_line_opts
198
);
199
},
200
};
201
202
for fixup in fixups.iter() {
203
result = Regex::new(&fixup.pat)
204
.unwrap()
205
.replace_all(&result, &*fixup.rep)
206
.into_owned()
207
.into();
208
}
209
let bytes = result.into_bytes();
210
File::create(&out_file)
211
.unwrap()
212
.write_all(&bytes)
213
.expect("Unable to write output");
214
}
215
216
struct BuilderWithConfig<'a> {
217
builder: Builder,
218
config: &'a Table,
219
used_keys: HashSet<&'static str>,
220
}
221
impl<'a> BuilderWithConfig<'a> {
222
fn new(builder: Builder, config: &'a Table) -> Self {
223
BuilderWithConfig {
224
builder,
225
config,
226
used_keys: HashSet::new(),
227
}
228
}
229
230
fn handle_list<F>(self, key: &'static str, func: F) -> BuilderWithConfig<'a>
231
where
232
F: FnOnce(Builder, slice::Iter<'a, toml::Value>) -> Builder,
233
{
234
let mut builder = self.builder;
235
let config = self.config;
236
let mut used_keys = self.used_keys;
237
if let Some(list) = config.get(key) {
238
used_keys.insert(key);
239
builder = func(builder, list.as_array().unwrap().as_slice().iter());
240
}
241
BuilderWithConfig {
242
builder,
243
config,
244
used_keys,
245
}
246
}
247
fn handle_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
248
where
249
F: FnMut(Builder, &'a toml::Value) -> Builder,
250
{
251
self.handle_list(key, |b, iter| iter.fold(b, |b, item| func(b, item)))
252
}
253
fn handle_str_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
254
where
255
F: FnMut(Builder, &'a str) -> Builder,
256
{
257
self.handle_items(key, |b, item| func(b, item.as_str().unwrap()))
258
}
259
fn handle_table_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
260
where
261
F: FnMut(Builder, &'a Table) -> Builder,
262
{
263
self.handle_items(key, |b, item| func(b, item.as_table().unwrap()))
264
}
265
fn handle_common(self, fixups: &mut Vec<Fixup>) -> BuilderWithConfig<'a> {
266
self.handle_str_items("headers", |b, item| b.header(add_include(item)))
267
.handle_str_items("raw-lines", |b, item| b.raw_line(item))
268
.handle_str_items("hide-types", |b, item| b.blacklist_type(item))
269
.handle_table_items("fixups", |builder, item| {
270
fixups.push(Fixup {
271
pat: item["pat"].as_str().unwrap().into(),
272
rep: item["rep"].as_str().unwrap().into(),
273
});
274
builder
275
})
276
}
277
278
fn get_builder(self) -> Builder {
279
for key in self.config.keys() {
280
if !self.used_keys.contains(key.as_str()) {
281
panic!(format!("Unknown key: {}", key));
282
}
283
}
284
self.builder
285
}
286
}
287
288
fn generate_structs() {
289
let builder = Builder::get_initial_builder()
290
.enable_cxx_namespaces()
291
.with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS);
292
let mut fixups = vec![];
293
let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
294
.handle_common(&mut fixups)
295
.handle_str_items("whitelist-functions", |b, item| b.whitelist_function(item))
296
.handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
297
.handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
298
.handle_str_items("whitelist-vars", |b, item| b.whitelist_var(item))
299
.handle_str_items("whitelist-types", |b, item| b.whitelist_type(item))
300
.handle_str_items("opaque-types", |b, item| b.opaque_type(item))
301
.handle_table_items("cbindgen-types", |b, item| {
302
let gecko = item["gecko"].as_str().unwrap();
303
let servo = item["servo"].as_str().unwrap();
304
b.blacklist_type(format!("mozilla::{}", gecko))
305
.module_raw_line("root::mozilla", format!("pub use {} as {};", servo, gecko))
306
})
307
.handle_table_items("mapped-generic-types", |builder, item| {
308
let generic = item["generic"].as_bool().unwrap();
309
let gecko = item["gecko"].as_str().unwrap();
310
let servo = item["servo"].as_str().unwrap();
311
let gecko_name = gecko.rsplit("::").next().unwrap();
312
let gecko = gecko
313
.split("::")
314
.map(|s| format!("\\s*{}\\s*", s))
315
.collect::<Vec<_>>()
316
.join("::");
317
318
fixups.push(Fixup {
319
pat: format!("\\broot\\s*::\\s*{}\\b", gecko),
320
rep: format!("::gecko_bindings::structs::{}", gecko_name),
321
});
322
builder.blacklist_type(gecko).raw_line(format!(
323
"pub type {0}{2} = {1}{2};",
324
gecko_name,
325
servo,
326
if generic { "<T>" } else { "" }
327
))
328
})
329
.get_builder();
330
write_binding_file(builder, STRUCTS_FILE, &fixups);
331
}
332
333
fn setup_logging() -> bool {
334
struct BuildLogger {
335
file: Option<Mutex<fs::File>>,
336
filter: String,
337
}
338
339
impl log::Log for BuildLogger {
340
fn enabled(&self, meta: &log::Metadata) -> bool {
341
self.file.is_some() && meta.target().contains(&self.filter)
342
}
343
344
fn log(&self, record: &log::Record) {
345
if !self.enabled(record.metadata()) {
346
return;
347
}
348
349
let mut file = self.file.as_ref().unwrap().lock().unwrap();
350
let _ = writeln!(
351
file,
352
"{} - {} - {} @ {}:{}",
353
record.level(),
354
record.target(),
355
record.args(),
356
record.file().unwrap_or("<unknown>"),
357
record.line().unwrap_or(0)
358
);
359
}
360
361
fn flush(&self) {
362
if let Some(ref file) = self.file {
363
file.lock().unwrap().flush().unwrap();
364
}
365
}
366
}
367
368
if let Some(path) = env::var_os("STYLO_BUILD_LOG") {
369
log::set_max_level(log::LevelFilter::Debug);
370
log::set_boxed_logger(Box::new(BuildLogger {
371
file: fs::File::create(path).ok().map(Mutex::new),
372
filter: env::var("STYLO_BUILD_FILTER")
373
.ok()
374
.unwrap_or_else(|| "bindgen".to_owned()),
375
}))
376
.expect("Failed to set logger.");
377
378
true
379
} else {
380
false
381
}
382
}
383
384
fn generate_atoms() {
385
let script = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
386
.join("gecko")
387
.join("regen_atoms.py");
388
println!("cargo:rerun-if-changed={}", script.display());
389
let status = Command::new(&*PYTHON)
390
.arg(&script)
391
.arg(DISTDIR_PATH.as_os_str())
392
.arg(OUTDIR_PATH.as_os_str())
393
.status()
394
.unwrap();
395
if !status.success() {
396
exit(1);
397
}
398
}
399
400
pub fn generate() {
401
println!("cargo:rerun-if-changed=build_gecko.rs");
402
fs::create_dir_all(&*OUTDIR_PATH).unwrap();
403
setup_logging();
404
generate_structs();
405
generate_atoms();
406
407
for path in ADDED_PATHS.lock().unwrap().iter() {
408
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
409
}
410
}