Path: blob/main/crates/bevy_macro_utils/src/bevy_manifest.rs
9296 views
extern crate proc_macro;12use alloc::collections::BTreeMap;3use proc_macro::TokenStream;4use std::sync::{PoisonError, RwLock};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/// Calls `f` with a global shared instance of the [`BevyManifest`] struct.23pub fn shared<R>(f: impl FnOnce(&BevyManifest) -> R) -> R {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");2829let manifests = MANIFESTS.read().unwrap_or_else(PoisonError::into_inner);30if let Some(manifest) = manifests.get(&manifest_path)31&& manifest.modified_time == modified_time32{33return f(manifest);34}3536drop(manifests);3738let manifest = BevyManifest {39manifest: Self::read_manifest(&manifest_path),40modified_time,41};4243let key = manifest_path.clone();44// TODO: Switch to using RwLockWriteGuard::downgrade when it stabilizes.45MANIFESTS46.write()47.unwrap_or_else(PoisonError::into_inner)48.insert(key, manifest);4950f(MANIFESTS51.read()52.unwrap_or_else(PoisonError::into_inner)53.get(&manifest_path)54.unwrap())55}5657fn get_manifest_path() -> PathBuf {58env::var_os("CARGO_MANIFEST_DIR")59.map(|path| {60let mut path = PathBuf::from(path);61path.push("Cargo.toml");62assert!(63path.exists(),64"Cargo manifest does not exist at path {}",65path.display()66);67path68})69.expect("CARGO_MANIFEST_DIR is not defined.")70}7172fn get_manifest_modified_time(73cargo_manifest_path: &Path,74) -> Result<SystemTime, std::io::Error> {75std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified())76}7778fn read_manifest(path: &Path) -> Document<Box<str>> {79let manifest = std::fs::read_to_string(path)80.unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()))81.into_boxed_str();82Document::parse(manifest)83.unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))84}8586/// Attempt to retrieve the [path](syn::Path) of a particular package in87/// the [manifest](BevyManifest) by [name](str).88pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {89let find_in_deps = |deps: &Item| -> Option<syn::Path> {90let package = if deps.get(name).is_some() {91return Some(Self::parse_str(name));92} else if deps.get(BEVY).is_some() {93BEVY94} else {95// Note: to support bevy crate aliases, we could do scanning here to find a crate with a "package" name that96// matches our request, but that would then mean we are scanning every dependency (and dev dependency) for every97// macro execution that hits this branch (which includes all built-in bevy crates). Our current stance is that supporting98// remapped crate names in derive macros is not worth that "compile time" price of admission. As a workaround, people aliasing99// bevy crate names can use "use REMAPPED as bevy_X" or "use REMAPPED::x as bevy_x".100return None;101};102103let mut path = Self::parse_str::<syn::Path>(&format!("::{package}"));104if let Some(module) = name.strip_prefix("bevy_") {105path.segments.push(Self::parse_str(module));106}107Some(path)108};109110let deps = self.manifest.get("dependencies");111let deps_dev = self.manifest.get("dev-dependencies");112113deps.and_then(find_in_deps)114.or_else(|| deps_dev.and_then(find_in_deps))115}116117/// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse)118pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {119syn::parse(path.parse::<TokenStream>().ok()?).ok()120}121122/// Returns the path for the crate with the given name.123pub fn get_path(&self, name: &str) -> syn::Path {124self.maybe_get_path(name)125.unwrap_or_else(|| Self::parse_str(name))126}127128/// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse).129///130/// # Panics131///132/// Will panic if the path is not able to be parsed. For a non-panicking option, see [`try_parse_str`]133///134/// [`try_parse_str`]: Self::try_parse_str135pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {136Self::try_parse_str(path).unwrap()137}138}139140141