servo: Merge #14754 - Skip invoking bindgen if no header changes (from upsuper:skip-bindgen); r=emilio
authorXidorn Quan <me@upsuper.org>
Wed, 28 Dec 2016 04:51:53 -0800
changeset 478604 83bb24b47d8a4ba9951601c2c0be17d0f229ed67
parent 478603 77f675794490dd0d93991975510b385fab061b93
child 478605 e084e535672be236f45471143ccfceac54d969ec
push id44079
push userbmo:gps@mozilla.com
push dateSat, 04 Feb 2017 00:14:49 +0000
reviewersemilio
servo: Merge #14754 - Skip invoking bindgen if no header changes (from upsuper:skip-bindgen); r=emilio This can avoid doing bindgen when build script is called for updating other files, e.g. properties. This uses a global modified time, so there is a chance that some of the files which can be skipped but not skipped. But given that we do all three files in parallel, that would unlikely affect the actual runtime. Using lots of `Mutex` could be an issue, but it doesn't seem to be in practice. Since only one thread would hold the lock of `ADDED_PATHS`, there is never a competitor for the lock of `LAST_MODIFIED`. r? @emilio Source-Repo: https://github.com/servo/servo Source-Revision: 9e0d269353d9380cb5cd12170679f2e82ab8c9f7
servo/components/style/build_gecko.rs
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -1,18 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 mod common {
     use std::env;
-    use std::path::PathBuf;
+    use std::path::{Path, PathBuf};
+    use std::sync::Mutex;
+    use std::time::SystemTime;
 
     lazy_static! {
         pub static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var("OUT_DIR").unwrap()).join("gecko");
+        pub static ref LAST_MODIFIED: Mutex<SystemTime> =
+            Mutex::new(get_modified_time(&env::current_exe().unwrap())
+                       .expect("Failed to get modified time of executable"));
     }
 
     pub const STRUCTS_DEBUG_FILE: &'static str = "structs_debug.rs";
     pub const STRUCTS_RELEASE_FILE: &'static str = "structs_release.rs";
     pub const BINDINGS_FILE: &'static str = "bindings.rs";
 
     #[derive(Clone, Copy, PartialEq)]
     pub enum BuildType {
@@ -21,26 +26,31 @@ mod common {
     }
 
     pub fn structs_file(build_type: BuildType) -> &'static str {
         match build_type {
             BuildType::Debug => STRUCTS_DEBUG_FILE,
             BuildType::Release => STRUCTS_RELEASE_FILE
         }
     }
+
+    pub fn get_modified_time(file: &Path) -> Option<SystemTime> {
+        file.metadata().and_then(|m| m.modified()).ok()
+    }
 }
 
 #[cfg(feature = "bindgen")]
 mod bindings {
     use libbindgen::{Builder, CodegenConfig};
     use regex::Regex;
+    use std::cmp;
     use std::collections::HashSet;
     use std::env;
     use std::fs::File;
-    use std::io::{BufWriter, Read, Write};
+    use std::io::{Read, Write};
     use std::path::PathBuf;
     use std::sync::Mutex;
     use super::common::*;
 
     lazy_static! {
         static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap();
         static ref DISTDIR_PATH: PathBuf = {
             let path = PathBuf::from(env::var("MOZ_DIST").unwrap());
@@ -54,19 +64,23 @@ mod bindings {
             DISTDIR_PATH.join("include/nspr"),
         ];
         static ref ADDED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
     }
 
     fn search_include(name: &str) -> Option<PathBuf> {
         for path in SEARCH_PATHS.iter() {
             let file = path.join(name);
-            if file.is_file() {
-                return Some(file);
+            if !file.is_file() {
+                continue;
             }
+            let modified = get_modified_time(&file).unwrap();
+            let mut last_modified = LAST_MODIFIED.lock().unwrap();
+            *last_modified = cmp::max(modified, *last_modified);
+            return Some(file);
         }
         None
     }
 
     fn add_headers_recursively(path: PathBuf, added_paths: &mut HashSet<PathBuf>) {
         if added_paths.contains(&path) {
             return;
         }
@@ -173,20 +187,35 @@ mod bindings {
             self.borrowed_type(ty)
                 .hide_type(format!("{}BorrowedMut", ty))
                 .raw_line(format!("pub type {0}BorrowedMut<'a> = &'a mut {0};", ty))
                 .hide_type(format!("{}BorrowedMutOrNull", ty))
                 .raw_line(format!("pub type {0}BorrowedMutOrNull<'a> = Option<&'a mut {0}>;", ty))
         }
     }
 
-    fn write_binding_file(builder: Builder, file: &str) {
-        let bindings = builder.generate().expect("Unable to generate bindings");
-        let binding_file = File::create(&OUTDIR_PATH.join(file)).unwrap();
-        bindings.write(Box::new(BufWriter::new(binding_file))).expect("Unable to write output");
+    struct Fixup {
+        pat: String,
+        rep: String
+    }
+
+    fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) {
+        let out_file = OUTDIR_PATH.join(file);
+        if let Some(modified) = get_modified_time(&out_file) {
+            // Don't generate the file if nothing it depends on was modified.
+            let last_modified = LAST_MODIFIED.lock().unwrap();
+            if *last_modified <= modified {
+                return;
+            }
+        }
+        let mut result = builder.generate().expect("Unable to generate bindings").to_string();
+        for fixup in fixups.iter() {
+            result = Regex::new(&format!(r"\b{}\b", fixup.pat)).unwrap().replace_all(&result, fixup.rep.as_str());
+        }
+        File::create(&out_file).unwrap().write_all(&result.into_bytes()).expect("Unable to write output");
     }
 
     pub fn generate_structs(build_type: BuildType) {
         let mut builder = Builder::get_initial_builder(build_type)
             .enable_cxx_namespaces()
             .with_codegen_config(CodegenConfig {
                 types: true,
                 vars: true,
@@ -385,20 +414,16 @@ mod bindings {
                 servo: "::std::cell::Cell"
             },
             MappedGenericType {
                 generic: false,
                 gecko: "ServoNodeData",
                 servo: "AtomicRefCell<ElementData>",
             }
         ];
-        struct Fixup {
-            pat: String,
-            rep: String
-        }
         let mut fixups = vec![
             Fixup {
                 pat: "root::nsString".into(),
                 rep: "::nsstring::nsStringRepr".into()
             },
         ];
         for &var in whitelist_vars.iter() {
             builder = builder.whitelisted_var(var);
@@ -414,22 +439,17 @@ mod bindings {
             builder = builder.hide_type(ty.gecko)
                 .raw_line(format!("pub type {0}{2} = {1}{2};", gecko_name, ty.servo,
                                   if ty.generic { "<T>" } else { "" }));
             fixups.push(Fixup {
                 pat: format!("root::{}", ty.gecko),
                 rep: format!("::gecko_bindings::structs::{}", gecko_name)
             });
         }
-        let mut result = builder.generate().expect("Unable to generate bindings").to_string();
-        for fixup in fixups.iter() {
-            result = Regex::new(&format!(r"\b{}\b", fixup.pat)).unwrap().replace_all(&result, fixup.rep.as_str());
-        }
-        File::create(&OUTDIR_PATH.join(structs_file(build_type))).unwrap()
-            .write_all(&result.into_bytes()).unwrap();
+        write_binding_file(builder, structs_file(build_type), &fixups);
     }
 
     pub fn generate_bindings() {
         let mut builder = Builder::get_initial_builder(BuildType::Release)
             .disable_name_namespacing()
             .with_codegen_config(CodegenConfig {
                 functions: true,
                 ..CodegenConfig::nothing()
@@ -580,17 +600,17 @@ mod bindings {
         }
         for &ty in servo_borrow_types.iter() {
             builder = builder.mutable_borrowed_type(ty);
             // Right now the only immutable borrow types are ones which we import
             // from the |structs| module. As such, we don't need to create an opaque
             // type with zero_size_type. If we ever introduce immutable borrow types
             // which _do_ need to be opaque, we'll need a separate mode.
         }
-        write_binding_file(builder, BINDINGS_FILE);
+        write_binding_file(builder, BINDINGS_FILE, &Vec::new());
     }
 }
 
 #[cfg(not(feature = "bindgen"))]
 mod bindings {
     use std::fs;
     use std::path::{Path, PathBuf};
     use super::common::*;