extern crate proc_macro;
use alloc::collections::BTreeMap;
use proc_macro::TokenStream;
use std::sync::{PoisonError, RwLock, RwLockWriteGuard};
use std::{
env,
path::{Path, PathBuf},
time::SystemTime,
};
use toml_edit::{Document, Item};
#[derive(Debug)]
pub struct BevyManifest {
manifest: Document<Box<str>>,
modified_time: SystemTime,
}
const BEVY: &str = "bevy";
impl BevyManifest {
pub fn shared<R>(f: impl FnOnce(&BevyManifest) -> R) -> R {
static MANIFESTS: RwLock<BTreeMap<PathBuf, BevyManifest>> = RwLock::new(BTreeMap::new());
let manifest_path = Self::get_manifest_path();
let modified_time = Self::get_manifest_modified_time(&manifest_path)
.expect("The Cargo.toml should have a modified time");
let manifests = MANIFESTS.read().unwrap_or_else(PoisonError::into_inner);
if let Some(manifest) = manifests.get(&manifest_path)
&& manifest.modified_time == modified_time
{
return f(manifest);
}
drop(manifests);
let manifest = BevyManifest {
manifest: Self::read_manifest(&manifest_path),
modified_time,
};
let key = manifest_path.clone();
let mut manifests = MANIFESTS.write().unwrap_or_else(PoisonError::into_inner);
manifests.insert(key, manifest);
f(RwLockWriteGuard::downgrade(manifests)
.get(&manifest_path)
.unwrap())
}
fn get_manifest_path() -> PathBuf {
env::var_os("CARGO_MANIFEST_DIR")
.map(|path| {
let mut path = PathBuf::from(path);
path.push("Cargo.toml");
assert!(
path.exists(),
"Cargo manifest does not exist at path {}",
path.display()
);
path
})
.expect("CARGO_MANIFEST_DIR is not defined.")
}
fn get_manifest_modified_time(
cargo_manifest_path: &Path,
) -> Result<SystemTime, std::io::Error> {
std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified())
}
fn read_manifest(path: &Path) -> Document<Box<str>> {
let manifest = std::fs::read_to_string(path)
.unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()))
.into_boxed_str();
Document::parse(manifest)
.unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))
}
pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
let find_in_deps = |deps: &Item| -> Option<syn::Path> {
let package = if deps.get(name).is_some() {
return Some(Self::parse_str(name));
} else if deps.get(BEVY).is_some() {
BEVY
} else {
return None;
};
let mut path = Self::parse_str::<syn::Path>(&format!("::{package}"));
if let Some(module) = name.strip_prefix("bevy_") {
path.segments.push(Self::parse_str(module));
}
Some(path)
};
let deps = self.manifest.get("dependencies");
let deps_dev = self.manifest.get("dev-dependencies");
deps.and_then(find_in_deps)
.or_else(|| deps_dev.and_then(find_in_deps))
}
pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {
syn::parse(path.parse::<TokenStream>().ok()?).ok()
}
pub fn get_path(&self, name: &str) -> syn::Path {
self.maybe_get_path(name)
.unwrap_or_else(|| Self::parse_str(name))
}
pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
Self::try_parse_str(path).unwrap()
}
}