tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

build_gecko.rs (13159B)


      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::LazyLock;
     17 use std::sync::Mutex;
     18 use std::time::SystemTime;
     19 use toml;
     20 use toml::value::Table;
     21 
     22 static OUTDIR_PATH: LazyLock<PathBuf> =
     23    LazyLock::new(|| PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko"));
     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 static CONFIG: LazyLock<Table> = LazyLock::new(|| {
     43    // Load Gecko's binding generator config from the source tree.
     44    let path = mozbuild::TOPSRCDIR.join("layout/style/ServoBindings.toml");
     45    read_config(&path)
     46 });
     47 static BINDGEN_FLAGS: LazyLock<Vec<String>> = LazyLock::new(|| {
     48    mozbuild::config::BINDGEN_SYSTEM_FLAGS
     49        .iter()
     50        .chain(&mozbuild::config::NSPR_CFLAGS)
     51        .chain(&mozbuild::config::MOZ_PIXMAN_CFLAGS)
     52        .chain(&mozbuild::config::MOZ_ICU_CFLAGS)
     53        .map(|s| s.to_string())
     54        .collect()
     55 });
     56 static INCLUDE_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"#include\s*"(.+?)""#).unwrap());
     57 static DISTDIR_PATH: LazyLock<PathBuf> = LazyLock::new(|| mozbuild::TOPOBJDIR.join("dist"));
     58 static SEARCH_PATHS: LazyLock<Vec<PathBuf>> = LazyLock::new(|| {
     59    vec![
     60        DISTDIR_PATH.join("include"),
     61        DISTDIR_PATH.join("include/nspr"),
     62    ]
     63 });
     64 static ADDED_PATHS: LazyLock<Mutex<HashSet<PathBuf>>> =
     65    LazyLock::new(|| Mutex::new(HashSet::new()));
     66 static LAST_MODIFIED: LazyLock<Mutex<SystemTime>> = LazyLock::new(|| {
     67    Mutex::new(
     68        get_modified_time(&env::current_exe().unwrap())
     69            .expect("Failed to get modified time of executable"),
     70    )
     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        // Disable rust unions, because we replace some types inside of
    129        // them.
    130        let mut builder = Builder::default()
    131            .size_t_is_usize(true)
    132            .disable_untagged_union();
    133 
    134        let rustfmt_path = env::var_os("RUSTFMT")
    135            .filter(|p| !p.is_empty())
    136            .map(PathBuf::from);
    137        if let Some(path) = rustfmt_path {
    138            builder = builder.with_rustfmt(path);
    139        }
    140 
    141        for dir in SEARCH_PATHS.iter() {
    142            builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap());
    143        }
    144 
    145        builder = builder.include(add_include("mozilla-config.h"));
    146 
    147        if env::var("CARGO_FEATURE_GECKO_DEBUG").is_ok() {
    148            builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1");
    149        }
    150 
    151        for item in &*BINDGEN_FLAGS {
    152            builder = builder.clang_arg(item);
    153        }
    154 
    155        builder
    156    }
    157    fn include<T: Into<String>>(self, file: T) -> Builder {
    158        self.clang_arg("-include").clang_arg(file)
    159    }
    160 }
    161 
    162 struct Fixup {
    163    pat: String,
    164    rep: String,
    165 }
    166 
    167 fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) {
    168    let out_file = OUTDIR_PATH.join(file);
    169    if let Some(modified) = get_modified_time(&out_file) {
    170        // Don't generate the file if nothing it depends on was modified.
    171        let last_modified = LAST_MODIFIED.lock().unwrap();
    172        if *last_modified <= modified {
    173            return;
    174        }
    175    }
    176    let command_line_opts = builder.command_line_flags();
    177    let result = builder.generate();
    178    let mut result = match result {
    179        Ok(bindings) => bindings.to_string(),
    180        Err(_) => {
    181            panic!(
    182                "Failed to generate bindings, flags: {:?}",
    183                command_line_opts
    184            );
    185        },
    186    };
    187 
    188    for fixup in fixups.iter() {
    189        result = Regex::new(&fixup.pat)
    190            .unwrap()
    191            .replace_all(&result, &*fixup.rep)
    192            .into_owned()
    193            .into();
    194    }
    195    let bytes = result.into_bytes();
    196    File::create(&out_file)
    197        .unwrap()
    198        .write_all(&bytes)
    199        .expect("Unable to write output");
    200 }
    201 
    202 struct BuilderWithConfig<'a> {
    203    builder: Builder,
    204    config: &'a Table,
    205    used_keys: HashSet<&'static str>,
    206 }
    207 impl<'a> BuilderWithConfig<'a> {
    208    fn new(builder: Builder, config: &'a Table) -> Self {
    209        BuilderWithConfig {
    210            builder,
    211            config,
    212            used_keys: HashSet::new(),
    213        }
    214    }
    215 
    216    fn handle_list<F>(self, key: &'static str, func: F) -> BuilderWithConfig<'a>
    217    where
    218        F: FnOnce(Builder, slice::Iter<'a, toml::Value>) -> Builder,
    219    {
    220        let mut builder = self.builder;
    221        let config = self.config;
    222        let mut used_keys = self.used_keys;
    223        if let Some(list) = config.get(key) {
    224            used_keys.insert(key);
    225            builder = func(builder, list.as_array().unwrap().as_slice().iter());
    226        }
    227        BuilderWithConfig {
    228            builder,
    229            config,
    230            used_keys,
    231        }
    232    }
    233    fn handle_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    234    where
    235        F: FnMut(Builder, &'a toml::Value) -> Builder,
    236    {
    237        self.handle_list(key, |b, iter| iter.fold(b, |b, item| func(b, item)))
    238    }
    239    fn handle_str_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    240    where
    241        F: FnMut(Builder, &'a str) -> Builder,
    242    {
    243        self.handle_items(key, |b, item| func(b, item.as_str().unwrap()))
    244    }
    245    fn handle_table_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a>
    246    where
    247        F: FnMut(Builder, &'a Table) -> Builder,
    248    {
    249        self.handle_items(key, |b, item| func(b, item.as_table().unwrap()))
    250    }
    251    fn handle_common(self, fixups: &mut Vec<Fixup>) -> BuilderWithConfig<'a> {
    252        self.handle_str_items("headers", |b, item| b.header(add_include(item)))
    253            .handle_str_items("raw-lines", |b, item| b.raw_line(item))
    254            .handle_str_items("hide-types", |b, item| b.blocklist_type(item))
    255            .handle_table_items("fixups", |builder, item| {
    256                fixups.push(Fixup {
    257                    pat: item["pat"].as_str().unwrap().into(),
    258                    rep: item["rep"].as_str().unwrap().into(),
    259                });
    260                builder
    261            })
    262    }
    263 
    264    fn get_builder(self) -> Builder {
    265        for key in self.config.keys() {
    266            if !self.used_keys.contains(key.as_str()) {
    267                panic!("Unknown key: {}", key);
    268            }
    269        }
    270        self.builder
    271    }
    272 }
    273 
    274 fn generate_structs() {
    275    let builder = Builder::get_initial_builder()
    276        .enable_cxx_namespaces()
    277        .with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS);
    278    let mut fixups = vec![];
    279    let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
    280        .handle_common(&mut fixups)
    281        .handle_str_items("allowlist-functions", |b, item| b.allowlist_function(item))
    282        .handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
    283        .handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
    284        .handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item))
    285        .handle_str_items("allowlist-types", |b, item| b.allowlist_type(item))
    286        .handle_str_items("opaque-types", |b, item| b.opaque_type(item))
    287        .handle_table_items("cbindgen-types", |b, item| {
    288            let gecko = item["gecko"].as_str().unwrap();
    289            let servo = item["servo"].as_str().unwrap();
    290            b.blocklist_type(format!("mozilla::{}", gecko))
    291                .module_raw_line("root::mozilla", format!("pub use {} as {};", servo, gecko))
    292        })
    293        .handle_table_items("mapped-generic-types", |builder, item| {
    294            let generic = item["generic"].as_bool().unwrap();
    295            let gecko = item["gecko"].as_str().unwrap();
    296            let servo = item["servo"].as_str().unwrap();
    297            let gecko_name = gecko.rsplit("::").next().unwrap();
    298            let gecko = gecko
    299                .split("::")
    300                .map(|s| format!("\\s*{}\\s*", s))
    301                .collect::<Vec<_>>()
    302                .join("::");
    303 
    304            fixups.push(Fixup {
    305                pat: format!("\\broot\\s*::\\s*{}\\b", gecko),
    306                rep: format!("crate::gecko_bindings::structs::{}", gecko_name),
    307            });
    308            builder.blocklist_type(gecko).raw_line(format!(
    309                "pub type {0}{2} = {1}{2};",
    310                gecko_name,
    311                servo,
    312                if generic { "<T>" } else { "" }
    313            ))
    314        })
    315        .get_builder();
    316    write_binding_file(builder, STRUCTS_FILE, &fixups);
    317 }
    318 
    319 fn setup_logging() -> bool {
    320    struct BuildLogger {
    321        file: Option<Mutex<fs::File>>,
    322        filter: String,
    323    }
    324 
    325    impl log::Log for BuildLogger {
    326        fn enabled(&self, meta: &log::Metadata) -> bool {
    327            self.file.is_some() && meta.target().contains(&self.filter)
    328        }
    329 
    330        fn log(&self, record: &log::Record) {
    331            if !self.enabled(record.metadata()) {
    332                return;
    333            }
    334 
    335            let mut file = self.file.as_ref().unwrap().lock().unwrap();
    336            let _ = writeln!(
    337                file,
    338                "{} - {} - {} @ {}:{}",
    339                record.level(),
    340                record.target(),
    341                record.args(),
    342                record.file().unwrap_or("<unknown>"),
    343                record.line().unwrap_or(0)
    344            );
    345        }
    346 
    347        fn flush(&self) {
    348            if let Some(ref file) = self.file {
    349                file.lock().unwrap().flush().unwrap();
    350            }
    351        }
    352    }
    353 
    354    if let Some(path) = env::var_os("STYLO_BUILD_LOG") {
    355        log::set_max_level(log::LevelFilter::Debug);
    356        log::set_boxed_logger(Box::new(BuildLogger {
    357            file: fs::File::create(path).ok().map(Mutex::new),
    358            filter: env::var("STYLO_BUILD_FILTER")
    359                .ok()
    360                .unwrap_or_else(|| "bindgen".to_owned()),
    361        }))
    362        .expect("Failed to set logger.");
    363 
    364        true
    365    } else {
    366        false
    367    }
    368 }
    369 
    370 fn generate_atoms() {
    371    let script = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
    372        .join("gecko")
    373        .join("regen_atoms.py");
    374    println!("cargo:rerun-if-changed={}", script.display());
    375    let status = Command::new(&*PYTHON)
    376        .arg(&script)
    377        .arg(DISTDIR_PATH.as_os_str())
    378        .arg(OUTDIR_PATH.as_os_str())
    379        .status()
    380        .unwrap();
    381    if !status.success() {
    382        exit(1);
    383    }
    384 }
    385 
    386 pub fn generate() {
    387    println!("cargo:rerun-if-changed=build_gecko.rs");
    388    fs::create_dir_all(&*OUTDIR_PATH).unwrap();
    389    setup_logging();
    390    generate_structs();
    391    generate_atoms();
    392 
    393    for path in ADDED_PATHS.lock().unwrap().iter() {
    394        println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
    395    }
    396 }