bevy_macro_utils/
bevy_manifest.rs1extern crate proc_macro;
2
3use alloc::collections::BTreeMap;
4use parking_lot::{lock_api::RwLockReadGuard, MappedRwLockReadGuard, RwLock, RwLockWriteGuard};
5use proc_macro::TokenStream;
6use std::{
7 env,
8 path::{Path, PathBuf},
9 time::SystemTime,
10};
11use toml_edit::{ImDocument, Item};
12
13#[derive(Debug)]
15pub struct BevyManifest {
16 manifest: ImDocument<Box<str>>,
17 modified_time: SystemTime,
18}
19
20const BEVY: &str = "bevy";
21
22impl BevyManifest {
23 pub fn shared() -> MappedRwLockReadGuard<'static, BevyManifest> {
25 static MANIFESTS: RwLock<BTreeMap<PathBuf, BevyManifest>> = RwLock::new(BTreeMap::new());
26 let manifest_path = Self::get_manifest_path();
27 let modified_time = Self::get_manifest_modified_time(&manifest_path)
28 .expect("The Cargo.toml should have a modified time");
29
30 if let Ok(manifest) =
31 RwLockReadGuard::try_map(MANIFESTS.read(), |manifests| manifests.get(&manifest_path))
32 {
33 if manifest.modified_time == modified_time {
34 return manifest;
35 }
36 }
37
38 let manifest = BevyManifest {
39 manifest: Self::read_manifest(&manifest_path),
40 modified_time,
41 };
42
43 let key = manifest_path.clone();
44 let mut manifests = MANIFESTS.write();
45 manifests.insert(key, manifest);
46
47 RwLockReadGuard::map(RwLockWriteGuard::downgrade(manifests), |manifests| {
48 manifests.get(&manifest_path).unwrap()
49 })
50 }
51
52 fn get_manifest_path() -> PathBuf {
53 env::var_os("CARGO_MANIFEST_DIR")
54 .map(|path| {
55 let mut path = PathBuf::from(path);
56 path.push("Cargo.toml");
57 assert!(
58 path.exists(),
59 "Cargo manifest does not exist at path {}",
60 path.display()
61 );
62 path
63 })
64 .expect("CARGO_MANIFEST_DIR is not defined.")
65 }
66
67 fn get_manifest_modified_time(
68 cargo_manifest_path: &Path,
69 ) -> Result<SystemTime, std::io::Error> {
70 std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified())
71 }
72
73 fn read_manifest(path: &Path) -> ImDocument<Box<str>> {
74 let manifest = std::fs::read_to_string(path)
75 .unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()))
76 .into_boxed_str();
77 ImDocument::parse(manifest)
78 .unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))
79 }
80
81 pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
84 let find_in_deps = |deps: &Item| -> Option<syn::Path> {
85 let package = if deps.get(name).is_some() {
86 return Some(Self::parse_str(name));
87 } else if deps.get(BEVY).is_some() {
88 BEVY
89 } else {
90 return None;
96 };
97
98 let mut path = Self::parse_str::<syn::Path>(package);
99 if let Some(module) = name.strip_prefix("bevy_") {
100 path.segments.push(Self::parse_str(module));
101 }
102 Some(path)
103 };
104
105 let deps = self.manifest.get("dependencies");
106 let deps_dev = self.manifest.get("dev-dependencies");
107
108 deps.and_then(find_in_deps)
109 .or_else(|| deps_dev.and_then(find_in_deps))
110 }
111
112 pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {
114 syn::parse(path.parse::<TokenStream>().ok()?).ok()
115 }
116
117 pub fn get_path(&self, name: &str) -> syn::Path {
119 self.maybe_get_path(name)
120 .unwrap_or_else(|| Self::parse_str(name))
121 }
122
123 pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
131 Self::try_parse_str(path).unwrap()
132 }
133}