Path: blob/main/crates/bevy_macro_utils/src/bevy_manifest.rs
6595 views
extern crate proc_macro;12use alloc::collections::BTreeMap;3use parking_lot::{lock_api::RwLockReadGuard, MappedRwLockReadGuard, RwLock, RwLockWriteGuard};4use proc_macro::TokenStream;5use std::{6env,7path::{Path, PathBuf},8time::SystemTime,9};10use toml_edit::{Document, Item};1112/// The path to the `Cargo.toml` file for the Bevy project.13#[derive(Debug)]14pub struct BevyManifest {15manifest: Document<Box<str>>,16modified_time: SystemTime,17}1819const BEVY: &str = "bevy";2021impl BevyManifest {22/// Returns a global shared instance of the [`BevyManifest`] struct.23pub fn shared() -> MappedRwLockReadGuard<'static, BevyManifest> {24static MANIFESTS: RwLock<BTreeMap<PathBuf, BevyManifest>> = RwLock::new(BTreeMap::new());25let manifest_path = Self::get_manifest_path();26let modified_time = Self::get_manifest_modified_time(&manifest_path)27.expect("The Cargo.toml should have a modified time");2829if let Ok(manifest) =30RwLockReadGuard::try_map(MANIFESTS.read(), |manifests| manifests.get(&manifest_path))31&& manifest.modified_time == modified_time32{33return manifest;34}3536let manifest = BevyManifest {37manifest: Self::read_manifest(&manifest_path),38modified_time,39};4041let key = manifest_path.clone();42let mut manifests = MANIFESTS.write();43manifests.insert(key, manifest);4445RwLockReadGuard::map(RwLockWriteGuard::downgrade(manifests), |manifests| {46manifests.get(&manifest_path).unwrap()47})48}4950fn get_manifest_path() -> PathBuf {51env::var_os("CARGO_MANIFEST_DIR")52.map(|path| {53let mut path = PathBuf::from(path);54path.push("Cargo.toml");55assert!(56path.exists(),57"Cargo manifest does not exist at path {}",58path.display()59);60path61})62.expect("CARGO_MANIFEST_DIR is not defined.")63}6465fn get_manifest_modified_time(66cargo_manifest_path: &Path,67) -> Result<SystemTime, std::io::Error> {68std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified())69}7071fn read_manifest(path: &Path) -> Document<Box<str>> {72let manifest = std::fs::read_to_string(path)73.unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()))74.into_boxed_str();75Document::parse(manifest)76.unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))77}7879/// Attempt to retrieve the [path](syn::Path) of a particular package in80/// the [manifest](BevyManifest) by [name](str).81pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {82let find_in_deps = |deps: &Item| -> Option<syn::Path> {83let package = if deps.get(name).is_some() {84return Some(Self::parse_str(name));85} else if deps.get(BEVY).is_some() {86BEVY87} else {88// Note: to support bevy crate aliases, we could do scanning here to find a crate with a "package" name that89// matches our request, but that would then mean we are scanning every dependency (and dev dependency) for every90// macro execution that hits this branch (which includes all built-in bevy crates). Our current stance is that supporting91// remapped crate names in derive macros is not worth that "compile time" price of admission. As a workaround, people aliasing92// bevy crate names can use "use REMAPPED as bevy_X" or "use REMAPPED::x as bevy_x".93return None;94};9596let mut path = Self::parse_str::<syn::Path>(&format!("::{package}"));97if let Some(module) = name.strip_prefix("bevy_") {98path.segments.push(Self::parse_str(module));99}100Some(path)101};102103let deps = self.manifest.get("dependencies");104let deps_dev = self.manifest.get("dev-dependencies");105106deps.and_then(find_in_deps)107.or_else(|| deps_dev.and_then(find_in_deps))108}109110/// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse)111pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {112syn::parse(path.parse::<TokenStream>().ok()?).ok()113}114115/// Returns the path for the crate with the given name.116pub fn get_path(&self, name: &str) -> syn::Path {117self.maybe_get_path(name)118.unwrap_or_else(|| Self::parse_str(name))119}120121/// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse).122///123/// # Panics124///125/// Will panic if the path is not able to be parsed. For a non-panicking option, see [`try_parse_str`]126///127/// [`try_parse_str`]: Self::try_parse_str128pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {129Self::try_parse_str(path).unwrap()130}131}132133134