88//! indices into the embedded file array.
99//!
1010//! The generated file is consumed by `src/stubs.rs` at compile time.
11+ //!
12+ //! ## Re-run strategy
13+ //!
14+ //! The `stubs/` directory is gitignored, so Cargo's default "re-run when
15+ //! any package file changes" behaviour does not notice when
16+ //! `composer install` creates it. Explicit `rerun-if-changed` on paths
17+ //! inside `stubs/` also fails when the directory doesn't exist yet.
18+ //!
19+ //! Instead we watch the project root directory (`.`). Its mtime changes
20+ //! whenever a direct child like `stubs/` is created or removed. We also
21+ //! watch `build.rs` and `composer.lock` for targeted rebuilds.
22+ //!
23+ //! To avoid unnecessary recompilation of the main crate we compare the
24+ //! newly generated content against the existing output file and only write
25+ //! when something actually changed. This way `rustc` sees a stable mtime
26+ //! on `stub_map_generated.rs` and skips recompilation when the stubs
27+ //! haven't changed.
1128
1229use std:: collections:: { BTreeMap , BTreeSet } ;
1330use std:: env;
@@ -22,12 +39,14 @@ const MAP_FILE: &str = "stubs/jetbrains/phpstorm-stubs/PhpStormStubsMap.php";
2239const STUBS_DIR : & str = "stubs/jetbrains/phpstorm-stubs" ;
2340
2441fn main ( ) {
25- // Tell Cargo to re-run this script when dependencies change.
26- // We watch composer.lock (rather than PhpStormStubsMap.php directly)
27- // because that's the file that changes when `composer update` pulls
28- // a new version of phpstorm-stubs — more natural for PHP developers.
29- println ! ( "cargo:rerun-if-changed=composer.lock" ) ;
42+ // Watch the project root directory so that creating/removing `stubs/`
43+ // (which is gitignored) is detected via the directory mtime change.
44+ // Without this, Cargo's default "any package file" check ignores
45+ // gitignored paths, and explicit watches on non-existent paths don't
46+ // reliably trigger when they first appear.
47+ println ! ( "cargo:rerun-if-changed=." ) ;
3048 println ! ( "cargo:rerun-if-changed=build.rs" ) ;
49+ println ! ( "cargo:rerun-if-changed=composer.lock" ) ;
3150
3251 let manifest_dir = env:: var ( "CARGO_MANIFEST_DIR" ) . expect ( "CARGO_MANIFEST_DIR not set" ) ;
3352 let map_path = Path :: new ( & manifest_dir) . join ( MAP_FILE ) ;
@@ -41,7 +60,13 @@ fn main() {
4160 "cargo:warning=Could not read PhpStormStubsMap.php ({}); generating empty stub index" ,
4261 e
4362 ) ;
44- write_empty_generated_file ( ) ;
63+ let content = concat ! (
64+ "pub(crate) static STUB_FILES: [&str; 0] = [];\n " ,
65+ "pub(crate) static STUB_CLASS_MAP: [(&str, usize); 0] = [];\n " ,
66+ "pub(crate) static STUB_FUNCTION_MAP: [(&str, usize); 0] = [];\n " ,
67+ "pub(crate) static STUB_CONSTANT_MAP: [(&str, usize); 0] = [];\n " ,
68+ ) ;
69+ write_if_changed ( content) ;
4570 return ;
4671 }
4772 } ;
@@ -82,9 +107,6 @@ fn main() {
82107
83108 // ── Generate Rust source ────────────────────────────────────────────
84109
85- let out_dir = env:: var ( "OUT_DIR" ) . expect ( "OUT_DIR not set" ) ;
86- let dest_path = Path :: new ( & out_dir) . join ( "stub_map_generated.rs" ) ;
87-
88110 let mut out = String :: with_capacity ( 512 * 1024 ) ;
89111
90112 // 1. The embedded file array.
@@ -170,7 +192,7 @@ fn main() {
170192 }
171193 out. push_str ( "];\n " ) ;
172194
173- fs :: write ( & dest_path , & out) . expect ( "Failed to write generated stub map" ) ;
195+ write_if_changed ( & out) ;
174196}
175197
176198/// Parse one of the `const CLASSES = array(...)`, `const FUNCTIONS = array(...)`,
@@ -223,15 +245,20 @@ fn escape(s: &str) -> String {
223245 s. replace ( '\\' , "\\ \\ " ) . replace ( '"' , "\\ \" " )
224246}
225247
226- /// Write an empty generated file when stubs are not available.
227- fn write_empty_generated_file ( ) {
248+ /// Write the generated file only if its content has actually changed.
249+ ///
250+ /// This avoids bumping the mtime on `stub_map_generated.rs` when nothing
251+ /// changed, which in turn prevents `rustc` from unnecessarily recompiling
252+ /// the main crate.
253+ fn write_if_changed ( content : & str ) {
228254 let out_dir = env:: var ( "OUT_DIR" ) . expect ( "OUT_DIR not set" ) ;
229255 let dest_path = Path :: new ( & out_dir) . join ( "stub_map_generated.rs" ) ;
230- let content = concat ! (
231- "pub(crate) static STUB_FILES: [&str; 0] = [];\n " ,
232- "pub(crate) static STUB_CLASS_MAP: [(&str, usize); 0] = [];\n " ,
233- "pub(crate) static STUB_FUNCTION_MAP: [(&str, usize); 0] = [];\n " ,
234- "pub(crate) static STUB_CONSTANT_MAP: [(&str, usize); 0] = [];\n " ,
235- ) ;
236- fs:: write ( & dest_path, content) . expect ( "Failed to write empty generated stub map" ) ;
256+
257+ if let Ok ( existing) = fs:: read_to_string ( & dest_path)
258+ && existing == content
259+ {
260+ return ;
261+ }
262+
263+ fs:: write ( & dest_path, content) . expect ( "Failed to write generated stub map" ) ;
237264}
0 commit comments