Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_encase_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn bevy_encase_path() -> syn::Path {
segments,
}
})
.unwrap_or_else(|_err| bevy_manifest.get_path(ENCASE))
.unwrap_or_else(|| bevy_manifest.get_path(ENCASE))
}

implement!(bevy_encase_path());
5 changes: 4 additions & 1 deletion crates/bevy_macro_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ keywords = ["bevy"]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
cargo-manifest-proc-macros = "0.3.4"
toml_edit = { version = "0.22.7", default-features = false, features = [
"parse",
] }
parking_lot = { version = "0.12" }

[lints]
workspace = true
Expand Down
140 changes: 103 additions & 37 deletions crates/bevy_macro_utils/src/bevy_manifest.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,127 @@
extern crate proc_macro;

use std::sync::MutexGuard;

use cargo_manifest_proc_macros::{
CargoManifest, CrateReExportingPolicy, KnownReExportingCrate, PathPiece,
TryResolveCratePathError,
};
use alloc::collections::BTreeMap;
use parking_lot::{lock_api::RwLockReadGuard, MappedRwLockReadGuard, RwLock, RwLockWriteGuard};
use proc_macro::TokenStream;
use std::{
env,
path::{Path, PathBuf},
time::SystemTime,
};
use toml_edit::{DocumentMut, Item};

struct BevyReExportingPolicy;

impl CrateReExportingPolicy for BevyReExportingPolicy {
fn get_re_exported_crate_path(&self, crate_name: &str) -> Option<PathPiece> {
crate_name.strip_prefix("bevy_").map(|s| {
let mut path = PathPiece::new();
path.push(syn::parse_str::<syn::PathSegment>(s).unwrap());
path
})
}
/// The path to the `Cargo.toml` file for the Bevy project.
#[derive(Debug)]
pub struct BevyManifest {
manifest: DocumentMut,
modified_time: SystemTime,
}

const BEVY: &str = "bevy";

const KNOWN_RE_EXPORTING_CRATE_BEVY: KnownReExportingCrate = KnownReExportingCrate {
re_exporting_crate_package_name: BEVY,
crate_re_exporting_policy: &BevyReExportingPolicy {},
};
impl BevyManifest {
/// Returns a global shared instance of the [`BevyManifest`] struct.
pub fn shared() -> MappedRwLockReadGuard<'static, BevyManifest> {
static MANIFESTS: RwLock<BTreeMap<PathBuf, BevyManifest>> = RwLock::new(BTreeMap::new());
let manifest_path = Self::get_manifest_path();
let modified_time = Self::get_manifest_modified_time(&manifest_path)
.expect("The Cargo.toml should have a modified time");

const ALL_KNOWN_RE_EXPORTING_CRATES: &[&KnownReExportingCrate] = &[&KNOWN_RE_EXPORTING_CRATE_BEVY];
if let Ok(manifest) =
RwLockReadGuard::try_map(MANIFESTS.read(), |manifests| manifests.get(&manifest_path))
{
if manifest.modified_time == modified_time {
return manifest;
}
}
let mut manifests = MANIFESTS.write();
manifests.insert(
manifest_path.clone(),
BevyManifest {
manifest: Self::read_manifest(&manifest_path),
modified_time,
},
);

/// The path to the `Cargo.toml` file for the Bevy project.
pub struct BevyManifest(MutexGuard<'static, CargoManifest>);
RwLockReadGuard::map(RwLockWriteGuard::downgrade(manifests), |manifests| {
manifests.get(&manifest_path).unwrap()
})
}

impl BevyManifest {
/// Returns a global shared instance of the [`BevyManifest`] struct.
pub fn shared() -> Self {
Self(CargoManifest::shared())
fn get_manifest_path() -> PathBuf {
env::var_os("CARGO_MANIFEST_DIR")
.map(|path| {
let mut path = PathBuf::from(path);
path.push("Cargo.toml");
assert!(
path.exists(),
"Cargo manifest does not exist at path {}",
path.display()
);
path
})
.expect("CARGO_MANIFEST_DIR is not defined.")
}

fn get_manifest_modified_time(
cargo_manifest_path: &Path,
) -> Result<SystemTime, std::io::Error> {
std::fs::metadata(cargo_manifest_path).and_then(|metadata| metadata.modified())
}

fn read_manifest(path: &Path) -> DocumentMut {
let manifest = std::fs::read_to_string(path)
.unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()));
manifest
.parse::<DocumentMut>()
.unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))
}

/// Attempt to retrieve the [path](syn::Path) of a particular package in
/// the [manifest](BevyManifest) by [name](str).
pub fn maybe_get_path(&self, name: &str) -> Result<syn::Path, TryResolveCratePathError> {
self.0
.try_resolve_crate_path(name, ALL_KNOWN_RE_EXPORTING_CRATES)
}
pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
fn dep_package(dep: &Item) -> Option<&str> {
if dep.as_str().is_some() {
None
} else {
dep.get("package").map(|name| name.as_str().unwrap())
}
}

/// Returns the path for the crate with the given name.
pub fn get_path(&self, name: &str) -> syn::Path {
self.maybe_get_path(name)
.expect("Failed to get path for crate")
let find_in_deps = |deps: &Item| -> Option<syn::Path> {
let package = if let Some(dep) = deps.get(name) {
return Some(Self::parse_str(dep_package(dep).unwrap_or(name)));
} else if let Some(dep) = deps.get(BEVY) {
dep_package(dep).unwrap_or(BEVY)
} else {
return None;
};

let mut path = Self::parse_str::<syn::Path>(package);
if let Some(module) = name.strip_prefix("bevy_") {
path.segments.push(Self::parse_str(module));
}
Some(path)
};

let deps = self.manifest.get("dependencies");
let deps_dev = self.manifest.get("dev-dependencies");

deps.and_then(find_in_deps)
.or_else(|| deps_dev.and_then(find_in_deps))
}

/// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse)
pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {
syn::parse(path.parse::<TokenStream>().ok()?).ok()
}

/// Returns the path for the crate with the given name.
pub fn get_path(&self, name: &str) -> syn::Path {
self.maybe_get_path(name)
.unwrap_or_else(|| Self::parse_str(name))
}

/// Attempt to parse provided [path](str) as a [syntax tree node](syn::parse::Parse).
///
/// # Panics
Expand All @@ -68,7 +134,7 @@ impl BevyManifest {
}

/// Attempt to get a subcrate [path](syn::Path) under Bevy by [name](str)
pub fn get_subcrate(&self, subcrate: &str) -> Result<syn::Path, TryResolveCratePathError> {
pub fn get_subcrate(&self, subcrate: &str) -> Option<syn::Path> {
self.maybe_get_path(BEVY)
.map(|bevy_path| {
let mut segments = bevy_path.segments;
Expand All @@ -78,6 +144,6 @@ impl BevyManifest {
segments,
}
})
.or_else(|_err| self.maybe_get_path(&format!("bevy_{subcrate}")))
.or_else(|| self.maybe_get_path(&format!("bevy_{subcrate}")))
}
}
1 change: 1 addition & 0 deletions crates/bevy_macro_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

//! A collection of helper types and functions for working on macros within the Bevy ecosystem.

extern crate alloc;
extern crate proc_macro;

mod attrs;
Expand Down
Loading