@@ -57,14 +57,16 @@ at your option.
5757*/
5858
5959use 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 ;
6870use 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.
109123pub 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.
153185fn 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