Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions crates/bevy_encase_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ENCASE: &str = "encase";
fn bevy_encase_path() -> syn::Path {
let bevy_manifest = BevyManifest::shared();
bevy_manifest
.get_subcrate("render")
.maybe_get_path("bevy_render")
.map(|bevy_render_path| {
let mut segments = bevy_render_path.segments;
segments.push(BevyManifest::parse_str("render_resource"));
Expand All @@ -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
147 changes: 98 additions & 49 deletions crates/bevy_macro_utils/src/bevy_manifest.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,124 @@
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> {
let find_in_deps = |deps: &Item| -> Option<syn::Path> {
let package = if deps.get(name).is_some() {
return Some(Self::parse_str(name));
} else if deps.get(BEVY).is_some() {
BEVY
} else {
// Note: to support bevy crate aliases, we could do scanning here to find a crate with a "package" name that
// matches our request, but that would then mean we are scanning every dependency (and dev dependency) for every
// macro execution that hits this branch (which includes all built-in bevy crates). Our current stance is that supporting
// remapped crate names in derive macros is not worth that "compile time" price of admission. As a workaround, people aliasing
// bevy crate names can use "use REMAPPED as bevy_X" or "use REMAPPED::x as bevy_x".
return None;
};

/// 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 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 @@ -66,18 +129,4 @@ impl BevyManifest {
pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
Self::try_parse_str(path).unwrap()
}

/// Attempt to get a subcrate [path](syn::Path) under Bevy by [name](str)
pub fn get_subcrate(&self, subcrate: &str) -> Result<syn::Path, TryResolveCratePathError> {
self.maybe_get_path(BEVY)
.map(|bevy_path| {
let mut segments = bevy_path.segments;
segments.push(BevyManifest::parse_str(subcrate));
syn::Path {
leading_colon: None,
segments,
}
})
.or_else(|_err| 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
6 changes: 0 additions & 6 deletions tests-integration/remapped-test/Cargo.toml

This file was deleted.

21 changes: 0 additions & 21 deletions tests-integration/remapped-test/src/lib.rs

This file was deleted.