From e116d86c2373f69f1b0a7f41bb12015d12feb89e Mon Sep 17 00:00:00 2001 From: Caleb White Date: Wed, 11 Mar 2026 13:25:16 -0500 Subject: [PATCH 1/2] build: automatically fetch stubs from GitHub --- Cargo.lock | 318 +++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 7 ++ build.rs | 128 ++++++++++++++++++--- 3 files changed, 429 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9861a797..e457bb06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,12 @@ dependencies = [ "regex", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -133,6 +139,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.8.0" @@ -327,6 +339,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.5.1" @@ -494,12 +515,33 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -613,6 +655,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.4.1" @@ -937,6 +990,18 @@ version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.10.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + [[package]] name = "libtest-mimic" version = "0.8.1" @@ -1096,6 +1161,16 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -1191,7 +1266,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -1217,6 +1292,7 @@ dependencies = [ "criterion", "datatest-stable", "env_logger", + "flate2", "ignore", "log", "mago-database", @@ -1226,10 +1302,12 @@ dependencies = [ "parking_lot", "serde", "serde_json", + "tar", "tempfile", "tokio", "toml", "tower-lsp", + "ureq", ] [[package]] @@ -1264,6 +1342,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plotters" version = "0.3.7" @@ -1383,6 +1467,15 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -1432,6 +1525,20 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.1.3" @@ -1445,6 +1552,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1635,6 +1777,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simdutf8" version = "0.1.5" @@ -1696,6 +1844,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1729,6 +1883,17 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.25.0" @@ -1736,7 +1901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2012,6 +2177,30 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "uriparse" version = "0.6.4" @@ -2170,6 +2359,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -2185,13 +2392,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -2203,6 +2419,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -2210,58 +2442,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -2371,6 +2651,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yansi" version = "1.0.1" @@ -2441,6 +2731,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index ec14adf8..f96fd69c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,13 @@ memchr = "2" parking_lot = "0.12" toml = "0.8" +[build-dependencies] +ureq = { version = "2", features = ["json"] } +tar = "0.4" +flate2 = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + [dev-dependencies] datatest-stable = "0.2" tempfile = "3" diff --git a/build.rs b/build.rs index f8954030..95d5b621 100644 --- a/build.rs +++ b/build.rs @@ -9,12 +9,18 @@ //! //! The generated file is consumed by `src/stubs.rs` at compile time. //! +//! ## Automatic stub fetching +//! +//! If the stubs directory doesn't exist, the build script will automatically +//! fetch the latest release of phpstorm-stubs from GitHub. This allows +//! `cargo install` to work without requiring `composer install` first. +//! //! ## Re-run strategy //! //! The `stubs/` directory is gitignored, so Cargo's default "re-run when -//! any package file changes" behaviour does not notice when -//! `composer install` creates it. Explicit `rerun-if-changed` on paths -//! inside `stubs/` also fails when the directory doesn't exist yet. +//! any package file changes" behaviour does not notice when stubs are +//! downloaded. Explicit `rerun-if-changed` on paths inside `stubs/` also +//! fails when the directory doesn't exist yet. //! //! Instead we watch the project root directory (`.`). Its mtime changes //! whenever a direct child like `stubs/` is created or removed. We also @@ -29,8 +35,15 @@ use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::fs; +use std::io::Read; use std::path::Path; +use flate2::read::GzDecoder; +use serde::Deserialize; +use tar::Archive; + +const GITHUB_REPO: &str = "JetBrains/phpstorm-stubs"; + /// Relative path from the crate root to the stubs map file. const MAP_FILE: &str = "stubs/jetbrains/phpstorm-stubs/PhpStormStubsMap.php"; @@ -38,6 +51,13 @@ const MAP_FILE: &str = "stubs/jetbrains/phpstorm-stubs/PhpStormStubsMap.php"; /// relative paths found in the map file). const STUBS_DIR: &str = "stubs/jetbrains/phpstorm-stubs"; +/// GitHub API response for latest release. +#[derive(Deserialize)] +struct GitHubRelease { + tag_name: String, + tarball_url: String, +} + fn main() { // Watch the project root directory so that creating/removing `stubs/` // (which is gitignored) is detected via the directory mtime change. @@ -49,8 +69,21 @@ fn main() { println!("cargo:rerun-if-changed=composer.lock"); let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let stubs_path = Path::new(&manifest_dir).join(STUBS_DIR); let map_path = Path::new(&manifest_dir).join(MAP_FILE); + if !map_path.exists() { + eprintln!("cargo:warning=Stubs not found, fetching from GitHub..."); + if let Err(e) = fetch_stubs(&manifest_dir) { + eprintln!("cargo:warning=Failed to fetch stubs from GitHub: {}", e); + eprintln!( + "cargo:warning=Building without stubs. Run `composer install` for full stub support." + ); + write_empty_stubs(); + return; + } + } + let map_content = match fs::read_to_string(&map_path) { Ok(c) => c, Err(e) => { @@ -60,13 +93,7 @@ fn main() { "cargo:warning=Could not read PhpStormStubsMap.php ({}); generating empty stub index", e ); - let content = concat!( - "pub(crate) static STUB_FILES: [&str; 0] = [];\n", - "pub(crate) static STUB_CLASS_MAP: [(&str, usize); 0] = [];\n", - "pub(crate) static STUB_FUNCTION_MAP: [(&str, usize); 0] = [];\n", - "pub(crate) static STUB_CONSTANT_MAP: [(&str, usize); 0] = [];\n", - ); - write_if_changed(content); + write_empty_stubs(); return; } }; @@ -91,11 +118,10 @@ fn main() { } // Only keep files that actually exist on disk. - let stubs_base = Path::new(&manifest_dir).join(STUBS_DIR); let existing_files: Vec<&str> = unique_files .iter() .copied() - .filter(|rel| stubs_base.join(rel).is_file()) + .filter(|rel| stubs_path.join(rel).is_file()) .collect(); // Build a path → index mapping. @@ -122,7 +148,7 @@ fn main() { // Build the include_str! path relative to the generated file's // location ($OUT_DIR). We use an absolute path rooted at CARGO_MANIFEST_DIR // to avoid fragile relative path arithmetic. - let abs = stubs_base.join(rel_path); + let abs = stubs_path.join(rel_path); let abs_str = abs.to_string_lossy().replace('\\', "/"); out.push_str(&format!(" include_str!(\"{}\")", abs_str)); out.push_str(",\n"); @@ -195,6 +221,82 @@ fn main() { write_if_changed(&out); } +fn fetch_stubs(manifest_dir: &str) -> Result<(), Box> { + let api_url = format!( + "https://api.github.com/repos/{}/releases/latest", + GITHUB_REPO + ); + + let response = ureq::get(&api_url) + .set("User-Agent", "phpantom-lsp-build") + .set("Accept", "application/vnd.github.v3+json") + .call()?; + + let release: GitHubRelease = response.into_json()?; + eprintln!( + "cargo:warning=Downloading phpstorm-stubs {}", + release.tag_name + ); + + let tarball_response = ureq::get(&release.tarball_url) + .set("User-Agent", "phpantom-lsp-build") + .call()?; + + let mut tarball_bytes = Vec::new(); + tarball_response + .into_reader() + .read_to_end(&mut tarball_bytes)?; + + let decoder = GzDecoder::new(&tarball_bytes[..]); + let mut archive = Archive::new(decoder); + + let target_dir = Path::new(manifest_dir).join("stubs/jetbrains/phpstorm-stubs"); + fs::create_dir_all(&target_dir)?; + + // GitHub tarballs have a top-level directory like "JetBrains-phpstorm-stubs-abc1234/" + // We need to strip that prefix when extracting. + for entry in archive.entries()? { + let mut entry = entry?; + let path = entry.path()?; + + let components: Vec<_> = path.components().collect(); + if components.len() <= 1 { + continue; + } + + let relative_path: std::path::PathBuf = components[1..].iter().collect(); + let dest_path = target_dir.join(&relative_path); + + if let Some(parent) = dest_path.parent() { + fs::create_dir_all(parent)?; + } + + if entry.header().entry_type().is_dir() { + fs::create_dir_all(&dest_path)?; + } else if entry.header().entry_type().is_file() { + let mut file = fs::File::create(&dest_path)?; + std::io::copy(&mut entry, &mut file)?; + } + } + + eprintln!( + "cargo:warning=Successfully downloaded phpstorm-stubs {}", + release.tag_name + ); + Ok(()) +} + +/// Write an empty stub map when stubs aren't available. +fn write_empty_stubs() { + let content = concat!( + "pub(crate) static STUB_FILES: [&str; 0] = [];\n", + "pub(crate) static STUB_CLASS_MAP: [(&str, usize); 0] = [];\n", + "pub(crate) static STUB_FUNCTION_MAP: [(&str, usize); 0] = [];\n", + "pub(crate) static STUB_CONSTANT_MAP: [(&str, usize); 0] = [];\n", + ); + write_if_changed(content); +} + /// Parse one of the `const CLASSES = array(...)`, `const FUNCTIONS = array(...)`, /// or `const CONSTANTS = array(...)` sections from the PhpStormStubsMap.php file. /// From 713f4b9cf2f8d763e592db7bc668b1f3a4378d6e Mon Sep 17 00:00:00 2001 From: Caleb White Date: Wed, 11 Mar 2026 13:29:12 -0500 Subject: [PATCH 2/2] chore: remove dependency on `composer install` --- build.rs | 9 +++---- composer.json | 8 ------ composer.lock | 63 ------------------------------------------------ docs/BUILDING.md | 11 +++------ src/stubs.rs | 6 ++--- 5 files changed, 9 insertions(+), 88 deletions(-) delete mode 100644 composer.json delete mode 100644 composer.lock diff --git a/build.rs b/build.rs index 95d5b621..ec0f3703 100644 --- a/build.rs +++ b/build.rs @@ -13,7 +13,7 @@ //! //! If the stubs directory doesn't exist, the build script will automatically //! fetch the latest release of phpstorm-stubs from GitHub. This allows -//! `cargo install` to work without requiring `composer install` first. +//! `cargo install` to work without any additional setup. //! //! ## Re-run strategy //! @@ -24,7 +24,7 @@ //! //! Instead we watch the project root directory (`.`). Its mtime changes //! whenever a direct child like `stubs/` is created or removed. We also -//! watch `build.rs` and `composer.lock` for targeted rebuilds. +//! watch `build.rs` for targeted rebuilds. //! //! To avoid unnecessary recompilation of the main crate we compare the //! newly generated content against the existing output file and only write @@ -66,7 +66,6 @@ fn main() { // reliably trigger when they first appear. println!("cargo:rerun-if-changed=."); println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=composer.lock"); let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); let stubs_path = Path::new(&manifest_dir).join(STUBS_DIR); @@ -76,9 +75,7 @@ fn main() { eprintln!("cargo:warning=Stubs not found, fetching from GitHub..."); if let Err(e) = fetch_stubs(&manifest_dir) { eprintln!("cargo:warning=Failed to fetch stubs from GitHub: {}", e); - eprintln!( - "cargo:warning=Building without stubs. Run `composer install` for full stub support." - ); + eprintln!("cargo:warning=Building without stubs (network may be unavailable)."); write_empty_stubs(); return; } diff --git a/composer.json b/composer.json deleted file mode 100644 index e0f2fbe4..00000000 --- a/composer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "require": { - "jetbrains/phpstorm-stubs": "^2025.3" - }, - "config": { - "vendor-dir": "stubs/" - } -} diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 6b89a343..00000000 --- a/composer.lock +++ /dev/null @@ -1,63 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "03d1163fbfe259fcbb66ed6a3c071813", - "packages": [ - { - "name": "jetbrains/phpstorm-stubs", - "version": "v2025.3", - "source": { - "type": "git", - "url": "https://github.com/JetBrains/phpstorm-stubs", - "reference": "d1ee5e570343bd4276a3d5959e6e1c2530b006d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/d1ee5e570343bd4276a3d5959e6e1c2530b006d0", - "reference": "d1ee5e570343bd4276a3d5959e6e1c2530b006d0", - "shasum": "" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.86", - "nikic/php-parser": "^v5.6", - "phpdocumentor/reflection-docblock": "^5.6", - "phpunit/phpunit": "^12.3" - }, - "type": "library", - "autoload": { - "files": [ - "PhpStormStubsMap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "PHP runtime & extensions header files for PhpStorm", - "homepage": "https://www.jetbrains.com/phpstorm", - "keywords": [ - "autocomplete", - "code", - "inference", - "inspection", - "jetbrains", - "phpstorm", - "stubs", - "type" - ], - "time": "2025-09-18T15:47:24+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": {}, - "platform-dev": {}, - "plugin-api-version": "2.6.0" -} diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 7a1e540e..e38585ba 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -3,23 +3,18 @@ ## Quick Start ```bash -composer install # fetch PHP stubs (requires Composer) cargo build --release # build the binary ``` ## Prerequisites - [Rust](https://rustup.rs/) (stable toolchain) -- [Composer](https://getcomposer.org/) (for PHP standard library stubs) ## Build -The PHP stubs are managed as a Composer dependency in `stubs/`. The `build.rs` script embeds the [JetBrains phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs) directly into the binary, giving the LSP full knowledge of built-in PHP classes, functions, and constants with no runtime dependencies. +The `build.rs` script automatically fetches the latest [JetBrains phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs) from GitHub and embeds them directly into the binary. This gives the LSP full knowledge of built-in PHP classes, functions, and constants with no runtime dependencies. -> [!NOTE] -> The build will succeed without `composer install`, but the resulting binary won't know about built-in PHP symbols like `Iterator`, `Countable`, `UnitEnum`, etc. Always run `composer install` first for a fully functional build. - -After updating stubs (`composer update`), just rebuild. `build.rs` watches `composer.lock` and re-embeds everything automatically. +The stubs are downloaded on first build and cached in `stubs/`. To update to the latest stubs, delete the `stubs/` directory and rebuild. For details on how symbol resolution and stub loading work, see [ARCHITECTURE.md](ARCHITECTURE.md). @@ -65,4 +60,4 @@ RUST_LOG=debug cargo run 2>phpantom.log Logs are written to stderr, so redirect as needed. -For editor setup instructions, see [SETUP.md](SETUP.md). \ No newline at end of file +For editor setup instructions, see [SETUP.md](SETUP.md). diff --git a/src/stubs.rs b/src/stubs.rs index f5c69c39..e7436dd1 100644 --- a/src/stubs.rs +++ b/src/stubs.rs @@ -28,9 +28,9 @@ /// /// ## Updating stubs /// -/// Run `composer update jetbrains/phpstorm-stubs` in the project root -/// (with `stubs/` as the vendor directory) and rebuild. The `build.rs` -/// script re-reads the map file and re-embeds everything automatically. +/// Delete the `stubs/` directory and rebuild. The `build.rs` script will +/// automatically fetch the latest release from GitHub, re-read the map +/// file and re-embed everything. use std::collections::HashMap; // Pull in the generated static arrays.