use std::fmt::Write as FmtWrite;
use std::fs;
use std::path::{Path, PathBuf};
fn main() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let out_dir = std::env::var("OUT_DIR").unwrap();
generate_phpstorm_stubs(Path::new(&manifest_dir), Path::new(&out_dir));
let fixtures_dir = Path::new(&manifest_dir).join("tests").join("fixtures");
let out_path = Path::new(&out_dir).join("fixture_tests.rs");
let mut code = String::from("// Auto-generated by build.rs — do not edit manually\n");
if !fixtures_dir.exists() {
fs::write(&out_path, &code).unwrap();
return;
}
println!("cargo:rerun-if-changed={}", fixtures_dir.display());
let mut categories: Vec<_> = fs::read_dir(&fixtures_dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.collect();
categories.sort_by_key(|e| e.file_name());
for cat_entry in categories {
let cat_dir_name = cat_entry.file_name().to_string_lossy().into_owned();
let cat_mod_name = cat_dir_name.replace('-', "_");
println!("cargo:rerun-if-changed={}", cat_entry.path().display());
let mut fixtures: Vec<_> = fs::read_dir(cat_entry.path())
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.map(|ext| ext == "phpt")
.unwrap_or(false)
})
.collect();
fixtures.sort_by_key(|e| e.file_name());
if fixtures.is_empty() {
continue;
}
code.push_str(&format!("\nmod {cat_mod_name} {{\n"));
for fixture in fixtures {
let path = fixture.path();
let stem = path
.file_stem()
.unwrap()
.to_string_lossy()
.replace('-', "_");
let file_name = path.file_name().unwrap().to_string_lossy().into_owned();
let rel = format!("tests/fixtures/{cat_dir_name}/{file_name}");
println!("cargo:rerun-if-changed={manifest_dir}/{rel}");
code.push_str(&format!(
" #[test]\n fn {stem}() {{\n \
mir_analyzer::test_utils::run_fixture(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/{rel}\"));\n \
}}\n"
));
}
code.push_str("}\n");
}
fs::write(&out_path, code).unwrap();
}
const STUB_DIRS: &[&str] = &[
"Core",
"standard",
"SPL",
"bcmath",
"ctype",
"curl",
"date",
"dom",
"fileinfo",
"filter",
"gmp",
"hash",
"iconv",
"intl",
"json",
"libxml",
"mbstring",
"mysqli",
"openssl",
"pcntl",
"pcre",
"PDO",
"posix",
"random",
"Reflection",
"session",
"SimpleXML",
"sodium",
"sockets",
"tokenizer",
"xml",
"zip",
"zlib",
];
fn generate_phpstorm_stubs(manifest_dir: &Path, out_dir: &Path) {
let stubs_root = manifest_dir.join("phpstorm-stubs");
let out_path = out_dir.join("phpstorm_stubs.rs");
if !stubs_root.exists() {
fs::write(
&out_path,
"/// phpstorm-stubs submodule not found; run `git submodule update --init`.\n\
pub(crate) static PHPSTORM_STUB_FILES: &[(&str, &str)] = &[];\n",
)
.unwrap();
return;
}
println!("cargo:rerun-if-changed=phpstorm-stubs");
let mut code = String::from(
"/// PHP standard-library stubs embedded from phpstorm-stubs.\n\
/// Auto-generated by build.rs — do not edit directly.\n\
pub(crate) static PHPSTORM_STUB_FILES: &[(&str, &str)] = &[\n",
);
for dir_name in STUB_DIRS {
let dir = stubs_root.join(dir_name);
if dir.is_dir() {
println!("cargo:rerun-if-changed={}", dir.display());
collect_php_files(&dir, &stubs_root, &mut code);
}
}
code.push_str("];\n");
fs::write(&out_path, code).unwrap();
}
fn collect_php_files(dir: &Path, stubs_root: &Path, code: &mut String) {
let mut entries: Vec<PathBuf> = match fs::read_dir(dir) {
Ok(rd) => rd.filter_map(|e| e.ok()).map(|e| e.path()).collect(),
Err(_) => return,
};
entries.sort();
for path in entries {
if path.is_dir() {
collect_php_files(&path, stubs_root, code);
} else if path.extension().is_some_and(|e| e == "php") {
let relative = path
.strip_prefix(stubs_root)
.unwrap_or(&path)
.to_string_lossy()
.replace('\\', "/");
let abs = path
.canonicalize()
.unwrap_or_else(|_| path.clone())
.to_string_lossy()
.replace('\\', "/");
writeln!(
code,
" ({}, include_str!({})),",
format_args!("{relative:?}"),
format_args!("{abs:?}"),
)
.unwrap();
}
}
}