Skip to content

Commit a9d018d

Browse files
authored
Merge pull request #23 from jplatte/cache-ra
Make the cache work with rust-analyzer
2 parents 66213a5 + 7c258b2 commit a9d018d

File tree

1 file changed

+65
-33
lines changed

1 file changed

+65
-33
lines changed

src/lib.rs

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,16 @@ at your option.
5757
*/
5858

5959
use std::{
60-
collections::BTreeMap,
60+
collections::btree_map::{self, BTreeMap},
6161
env,
62-
fs::File,
62+
fs::{self, File},
6363
io::{self, Read},
6464
path::{Path, PathBuf},
65+
sync::Mutex,
66+
time::SystemTime,
6567
};
6668

67-
use once_cell::sync::OnceCell;
69+
use once_cell::sync::Lazy;
6870
use toml::{self, value::Table};
6971

7072
/// Error type used by this crate.
@@ -91,6 +93,18 @@ pub enum FoundCrate {
9193
Name(String),
9294
}
9395

96+
// In a rustc invocation, there will only ever be one entry in this map, since every crate is
97+
// compiled with its own rustc process. However, the same is not (currently) the case for
98+
// rust-analyzer.
99+
type Cache = BTreeMap<String, CacheEntry>;
100+
101+
struct CacheEntry {
102+
manifest_ts: SystemTime,
103+
crate_names: CrateNames,
104+
}
105+
106+
type CrateNames = BTreeMap<String, FoundCrate>;
107+
94108
/// Find the crate name for the given `orig_name` in the current `Cargo.toml`.
95109
///
96110
/// `orig_name` should be the original name of the searched crate.
@@ -108,47 +122,65 @@ pub enum FoundCrate {
108122
/// it is ready to be used in `extern crate` as identifier.
109123
pub fn crate_name(orig_name: &str) -> Result<FoundCrate, Error> {
110124
let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| Error::CargoManifestDirNotSet)?;
125+
let manifest_path = Path::new(&manifest_dir).join("Cargo.toml");
126+
let manifest_ts = cargo_toml_timestamp(&manifest_path)?;
111127

112-
struct Cache {
113-
manifest_dir: String,
114-
manifest_path: PathBuf,
115-
crate_names: BTreeMap<String, FoundCrate>,
116-
}
128+
// This `Lazy<Mutex<_>>` can just be a `Mutex<_>` starting in Rust 1.63:
129+
// https://doc.rust-lang.org/beta/std/sync/struct.Mutex.html#method.new
130+
static CACHE: Lazy<Mutex<Cache>> = Lazy::new(Mutex::default);
131+
let mut cache = CACHE.lock().unwrap();
117132

118-
static CACHE: OnceCell<Cache> = OnceCell::new();
119-
let cache = CACHE.get_or_try_init(|| {
120-
let manifest_dir = manifest_dir.clone();
121-
let manifest_path = Path::new(&manifest_dir).join("Cargo.toml");
133+
let crate_names = match cache.entry(manifest_dir) {
134+
btree_map::Entry::Occupied(entry) => {
135+
let cache_entry = entry.into_mut();
122136

123-
if !manifest_path.exists() {
124-
return Err(Error::NotFound(manifest_dir.into()));
125-
}
126-
127-
let manifest = open_cargo_toml(&manifest_path)?;
128-
let crate_names = extract_crate_names(&manifest)?;
129-
130-
Ok(Cache {
131-
manifest_dir,
132-
manifest_path,
133-
crate_names,
134-
})
135-
})?;
137+
// Timestamp changed, rebuild this cache entry.
138+
if manifest_ts != cache_entry.manifest_ts {
139+
*cache_entry = read_cargo_toml(&manifest_path, manifest_ts)?;
140+
}
136141

137-
assert_eq!(
138-
manifest_dir, cache.manifest_dir,
139-
"CARGO_MANIFEST_DIR must not change within one compiler process"
140-
);
142+
&cache_entry.crate_names
143+
}
144+
btree_map::Entry::Vacant(entry) => {
145+
let cache_entry = entry.insert(read_cargo_toml(&manifest_path, manifest_ts)?);
146+
&cache_entry.crate_names
147+
}
148+
};
141149

142-
Ok(cache
143-
.crate_names
150+
Ok(crate_names
144151
.get(orig_name)
145152
.ok_or_else(|| Error::CrateNotFound {
146153
crate_name: orig_name.to_owned(),
147-
path: cache.manifest_path.clone(),
154+
path: manifest_path,
148155
})?
149156
.clone())
150157
}
151158

159+
fn cargo_toml_timestamp(manifest_path: &Path) -> Result<SystemTime, Error> {
160+
fs::metadata(manifest_path)
161+
.and_then(|meta| meta.modified())
162+
.map_err(|source| {
163+
if source.kind() == io::ErrorKind::NotFound {
164+
Error::NotFound(manifest_path.to_owned())
165+
} else {
166+
Error::CouldNotRead {
167+
path: manifest_path.to_owned(),
168+
source,
169+
}
170+
}
171+
})
172+
}
173+
174+
fn read_cargo_toml(manifest_path: &Path, manifest_ts: SystemTime) -> Result<CacheEntry, Error> {
175+
let manifest = open_cargo_toml(manifest_path)?;
176+
let crate_names = extract_crate_names(&manifest)?;
177+
178+
Ok(CacheEntry {
179+
manifest_ts,
180+
crate_names,
181+
})
182+
}
183+
152184
/// Make sure that the given crate name is a valid rust identifier.
153185
fn sanitize_crate_name<S: AsRef<str>>(name: S) -> String {
154186
name.as_ref().replace('-', "_")
@@ -172,7 +204,7 @@ fn open_cargo_toml(path: &Path) -> Result<Table, Error> {
172204

173205
/// Extract all crate names from the given `Cargo.toml` by checking the `dependencies` and
174206
/// `dev-dependencies`.
175-
fn extract_crate_names(cargo_toml: &Table) -> Result<BTreeMap<String, FoundCrate>, Error> {
207+
fn extract_crate_names(cargo_toml: &Table) -> Result<CrateNames, Error> {
176208
let package_name = extract_package_name(cargo_toml);
177209
let root_pkg = package_name.map(|name| {
178210
let cr = match env::var_os("CARGO_TARGET_TMPDIR") {

0 commit comments

Comments
 (0)