Skip to content

Commit

Permalink
v0.2.1
Browse files Browse the repository at this point in the history
* Fixed a bug where rust analyser would resolve the crate paths incorrectly due to caching of `'static` lifetimes.
  See [rust-analyzer#18798](rust-lang/rust-analyzer#18798) and [bevy#17004](bevyengine/bevy#17004).
  • Loading branch information
raldone01 committed Jan 1, 2025
1 parent 6d3e79a commit 314cb01
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 48 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.1] - 2025-01-01

* Fixed a bug where rust analyser would resolve the crate paths incorrectly due to caching of `'static` lifetimes.
See [rust-analyzer#18798](https://github.com/rust-lang/rust-analyzer/issues/18798) and [bevy#17004](https://github.com/bevyengine/bevy/issues/17004).

## [0.2.0] - 2024-12-23

* Added proper support for workspace dependencies.
Expand All @@ -20,7 +25,8 @@ Support reactive compilation using `proc_macro_tracked_env` and `track_path` nig

Initial release.

[Unreleased]: https://github.com/ink-feather-org/trait-cast-rs/compare/v0.2.0...HEAD
[Unreleased]: https://github.com/ink-feather-org/trait-cast-rs/compare/v0.2.1...HEAD
[0.2.1]: https://github.com/ink-feather-org/trait-cast-rs/releases/tag/v0.2.0...v0.2.1
[0.2.0]: https://github.com/ink-feather-org/trait-cast-rs/releases/tag/v0.1.0...v0.2.0
[0.1.0]: https://github.com/ink-feather-org/trait-cast-rs/releases/tag/v0.0.1...v0.1.0
[0.0.1]: https://github.com/ink-feather-org/trait-cast-rs/releases/tag/v0.0.1
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ strip = "debuginfo"

[package]
name = "cargo-manifest-proc-macros"
version = "0.2.0"
version = "0.2.1"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "Find the syn::Path to your own crate from proc-macros reliably."
Expand Down
150 changes: 112 additions & 38 deletions src/cargo_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use alloc::collections::BTreeMap;
use std::{
path::{Path, PathBuf},
process::Command,
sync::LazyLock,
sync::{Mutex, MutexGuard},
};
use thiserror::Error;
use toml_edit::{DocumentMut, Item, Table};
Expand Down Expand Up @@ -61,13 +61,15 @@ struct WorkspaceDependencyResolver<'a> {
}

impl<'a> WorkspaceDependencyResolver<'a> {
#[must_use = "This is a constructor."]
const fn new(crate_manifest_path: &'a Path) -> Self {
Self {
user_crate_manifest_path: crate_manifest_path,
workspace_dependencies: None,
}
}

#[must_use]
fn load_workspace_cargo_manifest(workspace_cargo_toml: &Table) -> BTreeMap<String, String> {
// Get the `[workspace]` section.
let workspace_section = workspace_cargo_toml
Expand Down Expand Up @@ -96,6 +98,7 @@ impl<'a> WorkspaceDependencyResolver<'a> {
}

/// Searches the workspace dependencies for the crate's package name.
#[must_use]
fn resolve_crate_package_name_from_workspace_dependency(
&mut self,
workspace_dependency_name: &str,
Expand Down Expand Up @@ -132,6 +135,9 @@ pub struct CargoManifest {
/// The name of the crate that is currently being built.
user_crate_name: String,

/// The modified time of the `Cargo.toml`, when the `CargoManifest` instance was created.
cargo_manifest_mtime: std::time::SystemTime,

/// The key is the crate's package name.
/// The value is the renamed crate name.
crate_dependencies: BTreeMap<String, DependencyState>,
Expand All @@ -140,47 +146,114 @@ pub struct CargoManifest {
impl CargoManifest {
/// Returns a global shared instance of the [`CargoManifest`] struct.
#[must_use = "This method returns the shared instance of the CargoManifest."]
pub fn shared() -> &'static LazyLock<Self> {
static LAZY_MANIFEST: LazyLock<CargoManifest> = LazyLock::new(|| {
// The environment variable `CARGO_MANIFEST_PATH` is not consistently set in all environments.

// Access environment variables through the `tracked_env` module to ensure that the proc-macro is recompiled when the environment variables change.
let cargo_manifest_dir = proc_macro::tracked_env::var("CARGO_MANIFEST_DIR");

// Append `Cargo.toml` and convert it to a `PathBuf`.
let cargo_manifest_path = cargo_manifest_dir
.map(PathBuf::from)
.map(|mut path| {
path.push("Cargo.toml");
trace!("Loading CARGO_MANIFEST_PATH={}", path.display());

assert!(
path.exists(),
"Cargo.toml does not exist at \"{}\"!",
path.display()
);
path
})
.expect("The CARGO_MANIFEST_DIR environment variable must be defined!");
#[expect(clippy::mut_mutex_lock)]
pub fn shared() -> MutexGuard<'static, Self> {
static MANIFESTS: Mutex<BTreeMap<PathBuf, &'static Mutex<CargoManifest>>> =
Mutex::new(BTreeMap::new());

// Get the current cargo manifest path and its modified time.
// Rust-Analyzer keeps the proc macro server running between invocations and only the environment variables change.
// This means 'static variables are not reset between invocations for different crates.
let current_cargo_manifest_path = Self::get_current_cargo_manifest_path();
let current_cargo_manifest_mtime = Self::get_cargo_manifest_mtime(&current_cargo_manifest_path);

let mut manifests = MANIFESTS.lock().unwrap();

// Get the cached shared instance of the CargoManifest.
let existing_shared_instance =
manifests
.get_mut(&current_cargo_manifest_path)
.map(|cargo_manifest_mutex| {
let mut shared_instance = cargo_manifest_mutex.lock().unwrap();
// We do this to avoid leaking a new CargoManifest instance, when a Cargo.toml we had already parsed previously is changed.
if shared_instance.cargo_manifest_mtime != current_cargo_manifest_mtime {
// The mtime of the crate manifest has changed.
// We need to recompute the CargoManifest.

let cargo_manifest = Self::new_with_current_env_vars(
&current_cargo_manifest_path,
current_cargo_manifest_mtime,
);

// Overwrite the cache with the new cargo manifest version.
*shared_instance = cargo_manifest;
}

let guard = cargo_manifest_mutex.lock().unwrap();
guard
});

let crate_manifest = CargoManifest::parse_cargo_manifest(&cargo_manifest_path);
let shared_instance = existing_shared_instance.unwrap_or_else(move || {
// A new Cargo.toml has been requested, so we have to leak a new CargoManifest instance.
let new_shared_instance = Box::leak(Box::new(Mutex::new(Self::new_with_current_env_vars(
&current_cargo_manifest_path,
current_cargo_manifest_mtime,
))));

// Extract the user crate package name.
let user_crate_name = CargoManifest::extract_user_crate_name(crate_manifest.as_table());
// Overwrite the cache with the new cargo manifest version.
manifests.insert(current_cargo_manifest_path, new_shared_instance);

let mut workspace_dependencies = WorkspaceDependencyResolver::new(&cargo_manifest_path);
new_shared_instance.lock().unwrap()
});

let resolved_dependencies = CargoManifest::extract_dependency_map_for_cargo_manifest(
crate_manifest.as_table(),
&mut workspace_dependencies,
);
shared_instance
}

CargoManifest {
user_crate_name,
crate_dependencies: resolved_dependencies,
}
});
&LAZY_MANIFEST
#[must_use]
fn get_current_cargo_manifest_path() -> PathBuf {
// The environment variable `CARGO_MANIFEST_PATH` is not consistently set in all environments.

// Access environment variables through the `tracked_env` module to ensure that the proc-macro is re-run when the environment variables change.
let cargo_manifest_dir = proc_macro::tracked_env::var("CARGO_MANIFEST_DIR");

// Append `Cargo.toml` and convert it to a `PathBuf`.
let cargo_manifest_path = cargo_manifest_dir
.map(PathBuf::from)
.map(|mut path| {
path.push("Cargo.toml");
trace!("Loading CARGO_MANIFEST_PATH={}", path.display());

assert!(
path.exists(),
"Cargo.toml does not exist at \"{}\"!",
path.display()
);
path
})
.expect("The CARGO_MANIFEST_DIR environment variable must be defined!");
cargo_manifest_path
}

#[must_use]
fn get_cargo_manifest_mtime(cargo_manifest_path: &Path) -> std::time::SystemTime {
std::fs::metadata(cargo_manifest_path)
.expect("Failed to get metadata for the crate manifest!")
.modified()
.expect("Failed to get the modified time of the crate manifest!")
}

#[must_use = "This is a constructor."]
fn new_with_current_env_vars(
cargo_manifest_path: &Path,
cargo_manifest_mtime: std::time::SystemTime,
) -> Self {
let crate_manifest = Self::parse_cargo_manifest(cargo_manifest_path);

// Extract the user crate package name.
let user_crate_name = Self::extract_user_crate_name(crate_manifest.as_table());

let mut workspace_dependencies = WorkspaceDependencyResolver::new(cargo_manifest_path);

let resolved_dependencies = Self::extract_dependency_map_for_cargo_manifest(
crate_manifest.as_table(),
&mut workspace_dependencies,
);

Self {
user_crate_name,
cargo_manifest_mtime,
crate_dependencies: resolved_dependencies,
}
}

#[must_use]
Expand Down Expand Up @@ -268,7 +341,7 @@ impl CargoManifest {

#[must_use]
fn parse_cargo_manifest(cargo_manifest_path: &Path) -> DocumentMut {
// Track the path to ensure that the proc-macro is recompiled when the `Cargo.toml` changes.
// Track the path to ensure that the proc-macro is re-run when the `Cargo.toml` changes.
proc_macro::tracked_path::path(cargo_manifest_path.to_string_lossy());
let cargo_manifest_string =
std::fs::read_to_string(cargo_manifest_path).unwrap_or_else(|err| {
Expand Down Expand Up @@ -532,6 +605,7 @@ mod tests {

CargoManifest {
user_crate_name,
cargo_manifest_mtime: std::time::SystemTime::UNIX_EPOCH,
crate_dependencies: resolved_dependencies,
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/workspace_deps/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 314cb01

Please sign in to comment.