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