Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_macro_utils/src/bevy_manifest.rs
9296 views
1
extern crate proc_macro;
2
3
use alloc::collections::BTreeMap;
4
use proc_macro::TokenStream;
5
use std::sync::{PoisonError, RwLock};
6
use std::{
7
env,
8
path::{Path, PathBuf},
9
time::SystemTime,
10
};
11
use toml_edit::{Document, Item};
12
13
/// The path to the `Cargo.toml` file for the Bevy project.
14
#[derive(Debug)]
15
pub struct BevyManifest {
16
manifest: Document<Box<str>>,
17
modified_time: SystemTime,
18
}
19
20
const BEVY: &str = "bevy";
21
22
impl BevyManifest {
23
/// Calls `f` with a global shared instance of the [`BevyManifest`] struct.
24
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
// TODO: Switch to using RwLockWriteGuard::downgrade when it stabilizes.
46
MANIFESTS
47
.write()
48
.unwrap_or_else(PoisonError::into_inner)
49
.insert(key, manifest);
50
51
f(MANIFESTS
52
.read()
53
.unwrap_or_else(PoisonError::into_inner)
54
.get(&manifest_path)
55
.unwrap())
56
}
57
58
fn get_manifest_path() -> PathBuf {
59
env::var_os("CARGO_MANIFEST_DIR")
60
.map(|path| {
61
let mut path = PathBuf::from(path);
62
path.push("Cargo.toml");
63
assert!(
64
path.exists(),
65
"Cargo manifest does not exist at path {}",
66
path.display()
67
);
68
path
69
})
70
.expect("CARGO_MANIFEST_DIR is not defined.")
71
}
72
73
fn get_manifest_modified_time(
74
cargo_manifest_path: &Path,
75
) -> Result<SystemTime, std::io::Error> {
76
std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified())
77
}
78
79
fn read_manifest(path: &Path) -> Document<Box<str>> {
80
let manifest = std::fs::read_to_string(path)
81
.unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()))
82
.into_boxed_str();
83
Document::parse(manifest)
84
.unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))
85
}
86
87
/// Attempt to retrieve the [path](syn::Path) of a particular package in
88
/// the [manifest](BevyManifest) by [name](str).
89
pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
90
let find_in_deps = |deps: &Item| -> Option<syn::Path> {
91
let package = if deps.get(name).is_some() {
92
return Some(Self::parse_str(name));
93
} else if deps.get(BEVY).is_some() {
94
BEVY
95
} else {
96
// Note: to support bevy crate aliases, we could do scanning here to find a crate with a "package" name that
97
// matches our request, but that would then mean we are scanning every dependency (and dev dependency) for every
98
// macro execution that hits this branch (which includes all built-in bevy crates). Our current stance is that supporting
99
// remapped crate names in derive macros is not worth that "compile time" price of admission. As a workaround, people aliasing
100
// bevy crate names can use "use REMAPPED as bevy_X" or "use REMAPPED::x as bevy_x".
101
return None;
102
};
103
104
let mut path = Self::parse_str::<syn::Path>(&format!("::{package}"));
105
if let Some(module) = name.strip_prefix("bevy_") {
106
path.segments.push(Self::parse_str(module));
107
}
108
Some(path)
109
};
110
111
let deps = self.manifest.get("dependencies");
112
let deps_dev = self.manifest.get("dev-dependencies");
113
114
deps.and_then(find_in_deps)
115
.or_else(|| deps_dev.and_then(find_in_deps))
116
}
117
118
/// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse)
119
pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {
120
syn::parse(path.parse::<TokenStream>().ok()?).ok()
121
}
122
123
/// Returns the path for the crate with the given name.
124
pub fn get_path(&self, name: &str) -> syn::Path {
125
self.maybe_get_path(name)
126
.unwrap_or_else(|| Self::parse_str(name))
127
}
128
129
/// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse).
130
///
131
/// # Panics
132
///
133
/// Will panic if the path is not able to be parsed. For a non-panicking option, see [`try_parse_str`]
134
///
135
/// [`try_parse_str`]: Self::try_parse_str
136
pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
137
Self::try_parse_str(path).unwrap()
138
}
139
}
140
141