|
| 1 | +/// This module should represent the RLS view of the Cargo project model. |
| 2 | +/// At the time of writing though it is very limited, does not contain |
| 3 | +/// the project model per se and implements the minimal amount of functionality |
| 4 | +/// required to make the racer work |
| 5 | +
|
| 6 | + |
| 7 | +use std::{ |
| 8 | + collections::{ |
| 9 | + HashSet, |
| 10 | + hash_map::{self, HashMap}, |
| 11 | + }, |
| 12 | + sync::Arc, |
| 13 | + path::{Path, PathBuf}, |
| 14 | + cell::RefCell, |
| 15 | + rc::Rc, |
| 16 | +}; |
| 17 | +use log::{log, info, warn, debug}; |
| 18 | +use rls_vfs::{Vfs, FileContents}; |
| 19 | +use cargo::{ |
| 20 | + Config, |
| 21 | + ops, |
| 22 | + util::{errors::CargoResult, important_paths::find_root_manifest_for_wd, toml}, |
| 23 | + core::{ |
| 24 | + Workspace, PackageSet, PackageId, registry::PackageRegistry, |
| 25 | + resolver::{EncodableResolve, Method, Resolve}, |
| 26 | + } |
| 27 | +}; |
| 28 | +use racer; |
| 29 | + |
| 30 | +pub fn cargo_project_model(vfs: Arc<Vfs>) -> Box<dyn racer::ProjectModelProvider> { |
| 31 | + Box::new(CargoProjectModel { |
| 32 | + vfs, |
| 33 | + cached_lockfile: Default::default(), |
| 34 | + cached_deps: Default::default(), |
| 35 | + }) |
| 36 | +} |
| 37 | + |
| 38 | +struct CargoProjectModel { |
| 39 | + vfs: Arc<Vfs>, |
| 40 | + /// Cached lockfiles (path to Cargo.lock -> Resolve) |
| 41 | + cached_lockfile: RefCell<HashMap<PathBuf, Rc<Resolve>>>, |
| 42 | + /// Cached dependencie (path to Cargo.toml -> Depedencies) |
| 43 | + cached_deps: RefCell<HashMap<PathBuf, Rc<Dependencies>>>, |
| 44 | +} |
| 45 | + |
| 46 | +macro_rules! cargo_try { |
| 47 | + ($r:expr) => { |
| 48 | + match $r { |
| 49 | + Ok(val) => val, |
| 50 | + Err(err) => { |
| 51 | + warn!("Error in cargo: {}", err); |
| 52 | + return None; |
| 53 | + } |
| 54 | + } |
| 55 | + }; |
| 56 | +} |
| 57 | + |
| 58 | +impl racer::ProjectModelProvider for CargoProjectModel { |
| 59 | + fn discover_project_manifest(&self, path: &Path) -> Option<PathBuf> { |
| 60 | + let path = cargo_try!(find_root_manifest_for_wd(path)); |
| 61 | + Some(path) |
| 62 | + } |
| 63 | + fn resolve_dependency(&self, manifest: &Path, libname: &str) -> Option<PathBuf> { |
| 64 | + let deps = match self.get_deps(manifest) { |
| 65 | + Some(deps) => { |
| 66 | + debug!("[resolve_dependency] cache exists for manifest"); |
| 67 | + deps |
| 68 | + } |
| 69 | + None => { |
| 70 | + // cache doesn't exist |
| 71 | + // calculating depedencies can be bottleneck we use info! here(kngwyu) |
| 72 | + info!("[resolve_dependency] cache doesn't exist"); |
| 73 | + let deps_map = self.resolve_dependencies(&manifest)?; |
| 74 | + self.cache_deps(manifest, deps_map) |
| 75 | + } |
| 76 | + }; |
| 77 | + deps.get_src_path(libname) |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +impl CargoProjectModel { |
| 82 | + pub(crate) fn load_lockfile( |
| 83 | + &self, |
| 84 | + path: &Path, |
| 85 | + resolver: &dyn Fn(&str) -> Option<Resolve>, |
| 86 | + ) -> Option<Rc<Resolve>> |
| 87 | + { |
| 88 | + match self.cached_lockfile.borrow_mut().entry(path.to_owned()) { |
| 89 | + hash_map::Entry::Occupied(occupied) => Some(Rc::clone(occupied.get())), |
| 90 | + hash_map::Entry::Vacant(vacant) => { |
| 91 | + let contents = match self.vfs.load_file(path) { |
| 92 | + Ok(FileContents::Text(f)) => f, |
| 93 | + Ok(_) => return None, |
| 94 | + Err(e) => { |
| 95 | + debug!( |
| 96 | + "[CargoProjectModel::load_lock_file] Failed to load {}: {}", |
| 97 | + path.display(), |
| 98 | + e |
| 99 | + ); |
| 100 | + return None; |
| 101 | + } |
| 102 | + }; |
| 103 | + resolver(&contents) |
| 104 | + .map(|res| Rc::clone(vacant.insert(Rc::new(res)))) |
| 105 | + } |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + fn resolve_dependencies(&self, manifest: &Path) -> Option<HashMap<String, PathBuf>> { |
| 110 | + let mut config = cargo_try!(Config::default()); |
| 111 | + // frozen=true, locked=true |
| 112 | + config.configure(0, Some(true), &None, true, true, &None, &[]).ok()?; |
| 113 | + let ws = cargo_try!(Workspace::new(&manifest, &config)); |
| 114 | + // get resolve from lock file |
| 115 | + let lock_path = ws.root().to_owned().join("Cargo.lock"); |
| 116 | + let lock_file = self.load_lockfile(&lock_path, &|lockfile| { |
| 117 | + let resolve = cargo_try!(toml::parse(&lockfile, &lock_path, ws.config())); |
| 118 | + let v: EncodableResolve = cargo_try!(resolve.try_into()); |
| 119 | + Some(cargo_try!(v.into_resolve(&ws))) |
| 120 | + }); |
| 121 | + // then resolve precisely and add overrides |
| 122 | + let mut registry = cargo_try!(PackageRegistry::new(ws.config())); |
| 123 | + let resolve = cargo_try!(match lock_file { |
| 124 | + Some(prev) => resolve_with_prev(&mut registry, &ws, Some(&*prev)), |
| 125 | + None => resolve_with_prev(&mut registry, &ws, None), |
| 126 | + }); |
| 127 | + let packages = get_resolved_packages(&resolve, registry); |
| 128 | + // we have caches for each crates, so only need depth1 depedencies(= dependencies in Cargo.toml) |
| 129 | + let depth1_dependencies = match ws.current_opt() { |
| 130 | + Some(cur) => cur.dependencies().iter().map(|p| p.name()).collect(), |
| 131 | + None => HashSet::new(), |
| 132 | + }; |
| 133 | + let current_pkg = ws.current().map(|pkg| pkg.name()); |
| 134 | + let is_current_pkg = |name| { |
| 135 | + if let Ok(n) = current_pkg { |
| 136 | + n == name |
| 137 | + } else { |
| 138 | + false |
| 139 | + } |
| 140 | + }; |
| 141 | + let deps_map = packages |
| 142 | + .package_ids() |
| 143 | + .filter_map(|package_id| { |
| 144 | + let pkg = packages.get(package_id).ok()?; |
| 145 | + let pkg_name = pkg.name(); |
| 146 | + // for examples/ or tests/ dir, we have to handle current package specially |
| 147 | + if !is_current_pkg(pkg_name) && !depth1_dependencies.contains(&pkg.name()) { |
| 148 | + return None; |
| 149 | + } |
| 150 | + let targets = pkg.manifest().targets(); |
| 151 | + // we only need library target |
| 152 | + let lib_target = targets.into_iter().find(|target| target.is_lib())?; |
| 153 | + // crate_name returns target.name.replace("-", "_") |
| 154 | + let crate_name = lib_target.crate_name(); |
| 155 | + let src_path = lib_target.src_path().to_owned(); |
| 156 | + Some((crate_name, src_path)) |
| 157 | + }) |
| 158 | + .collect(); |
| 159 | + Some(deps_map) |
| 160 | + } |
| 161 | + |
| 162 | + fn get_deps(&self, manifest: &Path) -> Option<Rc<Dependencies>> { |
| 163 | + let deps = self.cached_deps.borrow(); |
| 164 | + deps.get(manifest).map(|rc| Rc::clone(&rc)) |
| 165 | + } |
| 166 | + |
| 167 | + fn cache_deps(&self, manifest: &Path, cache: HashMap<String, PathBuf>) -> Rc<Dependencies> { |
| 168 | + let manifest = manifest.to_owned(); |
| 169 | + let deps = Rc::new(Dependencies { inner: cache }); |
| 170 | + self.cached_deps.borrow_mut().insert(manifest, deps.clone()); |
| 171 | + deps |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +/// dependencies info of a package |
| 176 | +#[derive(Clone, Debug, Default)] |
| 177 | +struct Dependencies { |
| 178 | + /// dependencies of a package(library name -> src_path) |
| 179 | + inner: HashMap<String, PathBuf>, |
| 180 | +} |
| 181 | + |
| 182 | +impl Dependencies { |
| 183 | + /// Get src path from a library name. |
| 184 | + /// e.g. from query string `bit_set` it returns |
| 185 | + /// `~/.cargo/registry/src/github.1485827954.workers.dev-1ecc6299db9ec823/bit-set-0.4.0` |
| 186 | + fn get_src_path(&self, query: &str) -> Option<PathBuf> { |
| 187 | + let p = self.inner.get(query)?; |
| 188 | + Some(p.to_owned()) |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +// wrapper of resolve_with_previous |
| 193 | +fn resolve_with_prev<'cfg>( |
| 194 | + registry: &mut PackageRegistry<'cfg>, |
| 195 | + ws: &Workspace<'cfg>, |
| 196 | + prev: Option<&Resolve>, |
| 197 | +) -> CargoResult<Resolve> { |
| 198 | + ops::resolve_with_previous( |
| 199 | + registry, |
| 200 | + ws, |
| 201 | + Method::Everything, |
| 202 | + prev, |
| 203 | + None, |
| 204 | + &[], |
| 205 | + true, |
| 206 | + false, |
| 207 | + ) |
| 208 | +} |
| 209 | + |
| 210 | +// until cargo 0.30 is released |
| 211 | +fn get_resolved_packages<'a>(resolve: &Resolve, registry: PackageRegistry<'a>) -> PackageSet<'a> { |
| 212 | + let ids: Vec<PackageId> = resolve.iter().cloned().collect(); |
| 213 | + registry.get(&ids) |
| 214 | +} |
0 commit comments