aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_project_model/src/lib.rs')
-rw-r--r--crates/ra_project_model/src/lib.rs135
1 files changed, 49 insertions, 86 deletions
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 9541362f5..8b85b4831 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -1,25 +1,25 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod cargo_workspace; 3mod cargo_workspace;
4mod json_project; 4mod project_json;
5mod sysroot; 5mod sysroot;
6 6
7use std::{ 7use std::{
8 fs::{read_dir, File, ReadDir}, 8 fs::{self, read_dir, ReadDir},
9 io::{self, BufReader}, 9 io,
10 path::{Path, PathBuf}, 10 path::Path,
11 process::{Command, Output}, 11 process::{Command, Output},
12}; 12};
13 13
14use anyhow::{bail, Context, Result}; 14use anyhow::{bail, Context, Result};
15use paths::{AbsPath, AbsPathBuf};
15use ra_cfg::CfgOptions; 16use ra_cfg::CfgOptions;
16use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; 17use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
17use rustc_hash::{FxHashMap, FxHashSet}; 18use rustc_hash::{FxHashMap, FxHashSet};
18use serde_json::from_reader;
19 19
20pub use crate::{ 20pub use crate::{
21 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, 21 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
22 json_project::JsonProject, 22 project_json::{ProjectJson, ProjectJsonData},
23 sysroot::Sysroot, 23 sysroot::Sysroot,
24}; 24};
25pub use ra_proc_macro::ProcMacroClient; 25pub use ra_proc_macro::ProcMacroClient;
@@ -29,7 +29,7 @@ pub enum ProjectWorkspace {
29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. 29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
30 Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, 30 Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
31 /// Project workspace was manually specified using a `rust-project.json` file. 31 /// Project workspace was manually specified using a `rust-project.json` file.
32 Json { project: JsonProject, project_location: PathBuf }, 32 Json { project: ProjectJson },
33} 33}
34 34
35/// `PackageRoot` describes a package root folder. 35/// `PackageRoot` describes a package root folder.
@@ -38,22 +38,22 @@ pub enum ProjectWorkspace {
38#[derive(Debug, Clone)] 38#[derive(Debug, Clone)]
39pub struct PackageRoot { 39pub struct PackageRoot {
40 /// Path to the root folder 40 /// Path to the root folder
41 path: PathBuf, 41 path: AbsPathBuf,
42 /// Is a member of the current workspace 42 /// Is a member of the current workspace
43 is_member: bool, 43 is_member: bool,
44 out_dir: Option<PathBuf>, 44 out_dir: Option<AbsPathBuf>,
45} 45}
46impl PackageRoot { 46impl PackageRoot {
47 pub fn new_member(path: PathBuf) -> PackageRoot { 47 pub fn new_member(path: AbsPathBuf) -> PackageRoot {
48 Self { path, is_member: true, out_dir: None } 48 Self { path, is_member: true, out_dir: None }
49 } 49 }
50 pub fn new_non_member(path: PathBuf) -> PackageRoot { 50 pub fn new_non_member(path: AbsPathBuf) -> PackageRoot {
51 Self { path, is_member: false, out_dir: None } 51 Self { path, is_member: false, out_dir: None }
52 } 52 }
53 pub fn path(&self) -> &Path { 53 pub fn path(&self) -> &AbsPath {
54 &self.path 54 &self.path
55 } 55 }
56 pub fn out_dir(&self) -> Option<&Path> { 56 pub fn out_dir(&self) -> Option<&AbsPath> {
57 self.out_dir.as_deref() 57 self.out_dir.as_deref()
58 } 58 }
59 pub fn is_member(&self) -> bool { 59 pub fn is_member(&self) -> bool {
@@ -63,12 +63,12 @@ impl PackageRoot {
63 63
64#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] 64#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
65pub enum ProjectManifest { 65pub enum ProjectManifest {
66 ProjectJson(PathBuf), 66 ProjectJson(AbsPathBuf),
67 CargoToml(PathBuf), 67 CargoToml(AbsPathBuf),
68} 68}
69 69
70impl ProjectManifest { 70impl ProjectManifest {
71 pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> { 71 pub fn from_manifest_file(path: AbsPathBuf) -> Result<ProjectManifest> {
72 if path.ends_with("rust-project.json") { 72 if path.ends_with("rust-project.json") {
73 return Ok(ProjectManifest::ProjectJson(path)); 73 return Ok(ProjectManifest::ProjectJson(path));
74 } 74 }
@@ -78,7 +78,7 @@ impl ProjectManifest {
78 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) 78 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
79 } 79 }
80 80
81 pub fn discover_single(path: &Path) -> Result<ProjectManifest> { 81 pub fn discover_single(path: &AbsPath) -> Result<ProjectManifest> {
82 let mut candidates = ProjectManifest::discover(path)?; 82 let mut candidates = ProjectManifest::discover(path)?;
83 let res = match candidates.pop() { 83 let res = match candidates.pop() {
84 None => bail!("no projects"), 84 None => bail!("no projects"),
@@ -91,23 +91,23 @@ impl ProjectManifest {
91 Ok(res) 91 Ok(res)
92 } 92 }
93 93
94 pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> { 94 pub fn discover(path: &AbsPath) -> io::Result<Vec<ProjectManifest>> {
95 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { 95 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
96 return Ok(vec![ProjectManifest::ProjectJson(project_json)]); 96 return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
97 } 97 }
98 return find_cargo_toml(path) 98 return find_cargo_toml(path)
99 .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect()); 99 .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
100 100
101 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { 101 fn find_cargo_toml(path: &AbsPath) -> io::Result<Vec<AbsPathBuf>> {
102 match find_in_parent_dirs(path, "Cargo.toml") { 102 match find_in_parent_dirs(path, "Cargo.toml") {
103 Some(it) => Ok(vec![it]), 103 Some(it) => Ok(vec![it]),
104 None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)), 104 None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
105 } 105 }
106 } 106 }
107 107
108 fn find_in_parent_dirs(path: &Path, target_file_name: &str) -> Option<PathBuf> { 108 fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<AbsPathBuf> {
109 if path.ends_with(target_file_name) { 109 if path.ends_with(target_file_name) {
110 return Some(path.to_owned()); 110 return Some(path.to_path_buf());
111 } 111 }
112 112
113 let mut curr = Some(path); 113 let mut curr = Some(path);
@@ -123,17 +123,18 @@ impl ProjectManifest {
123 None 123 None
124 } 124 }
125 125
126 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { 126 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<AbsPathBuf> {
127 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects 127 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
128 entities 128 entities
129 .filter_map(Result::ok) 129 .filter_map(Result::ok)
130 .map(|it| it.path().join("Cargo.toml")) 130 .map(|it| it.path().join("Cargo.toml"))
131 .filter(|it| it.exists()) 131 .filter(|it| it.exists())
132 .map(AbsPathBuf::assert)
132 .collect() 133 .collect()
133 } 134 }
134 } 135 }
135 136
136 pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> { 137 pub fn discover_all(paths: &[impl AsRef<AbsPath>]) -> Vec<ProjectManifest> {
137 let mut res = paths 138 let mut res = paths
138 .iter() 139 .iter()
139 .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) 140 .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
@@ -154,20 +155,15 @@ impl ProjectWorkspace {
154 ) -> Result<ProjectWorkspace> { 155 ) -> Result<ProjectWorkspace> {
155 let res = match manifest { 156 let res = match manifest {
156 ProjectManifest::ProjectJson(project_json) => { 157 ProjectManifest::ProjectJson(project_json) => {
157 let file = File::open(&project_json).with_context(|| { 158 let file = fs::read_to_string(&project_json).with_context(|| {
158 format!("Failed to open json file {}", project_json.display()) 159 format!("Failed to read json file {}", project_json.display())
159 })?; 160 })?;
160 let reader = BufReader::new(file); 161 let data = serde_json::from_str(&file).with_context(|| {
161 let project_location = match project_json.parent() { 162 format!("Failed to deserialize json file {}", project_json.display())
162 Some(parent) => PathBuf::from(parent), 163 })?;
163 None => PathBuf::new(), 164 let project_location = project_json.parent().unwrap().to_path_buf();
164 }; 165 let project = ProjectJson::new(&project_location, data);
165 ProjectWorkspace::Json { 166 ProjectWorkspace::Json { project }
166 project: from_reader(reader).with_context(|| {
167 format!("Failed to deserialize json file {}", project_json.display())
168 })?,
169 project_location: project_location,
170 }
171 } 167 }
172 ProjectManifest::CargoToml(cargo_toml) => { 168 ProjectManifest::CargoToml(cargo_toml) => {
173 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) 169 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
@@ -199,11 +195,9 @@ impl ProjectWorkspace {
199 /// the root is a member of the current workspace 195 /// the root is a member of the current workspace
200 pub fn to_roots(&self) -> Vec<PackageRoot> { 196 pub fn to_roots(&self) -> Vec<PackageRoot> {
201 match self { 197 match self {
202 ProjectWorkspace::Json { project, project_location } => project 198 ProjectWorkspace::Json { project } => {
203 .roots 199 project.roots.iter().map(|r| PackageRoot::new_member(r.path.clone())).collect()
204 .iter() 200 }
205 .map(|r| PackageRoot::new_member(project_location.join(&r.path)))
206 .collect(),
207 ProjectWorkspace::Cargo { cargo, sysroot } => cargo 201 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
208 .packages() 202 .packages()
209 .map(|pkg| PackageRoot { 203 .map(|pkg| PackageRoot {
@@ -218,9 +212,9 @@ impl ProjectWorkspace {
218 } 212 }
219 } 213 }
220 214
221 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { 215 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
222 match self { 216 match self {
223 ProjectWorkspace::Json { project, .. } => project 217 ProjectWorkspace::Json { project } => project
224 .crates 218 .crates
225 .iter() 219 .iter()
226 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref()) 220 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref())
@@ -246,49 +240,26 @@ impl ProjectWorkspace {
246 pub fn to_crate_graph( 240 pub fn to_crate_graph(
247 &self, 241 &self,
248 target: Option<&str>, 242 target: Option<&str>,
249 extern_source_roots: &FxHashMap<PathBuf, ExternSourceId>,
250 proc_macro_client: &ProcMacroClient, 243 proc_macro_client: &ProcMacroClient,
251 load: &mut dyn FnMut(&Path) -> Option<FileId>, 244 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
252 ) -> CrateGraph { 245 ) -> CrateGraph {
253 let mut crate_graph = CrateGraph::default(); 246 let mut crate_graph = CrateGraph::default();
254 match self { 247 match self {
255 ProjectWorkspace::Json { project, project_location } => { 248 ProjectWorkspace::Json { project } => {
256 let crates: FxHashMap<_, _> = project 249 let crates: FxHashMap<_, _> = project
257 .crates 250 .crates
258 .iter() 251 .iter()
259 .enumerate() 252 .enumerate()
260 .filter_map(|(seq_index, krate)| { 253 .filter_map(|(seq_index, krate)| {
261 let file_path = project_location.join(&krate.root_module); 254 let file_path = &krate.root_module;
262 let file_id = load(&file_path)?; 255 let file_id = load(&file_path)?;
263 let edition = match krate.edition {
264 json_project::Edition::Edition2015 => Edition::Edition2015,
265 json_project::Edition::Edition2018 => Edition::Edition2018,
266 };
267 let cfg_options = {
268 let mut opts = CfgOptions::default();
269 for cfg in &krate.cfg {
270 match cfg.find('=') {
271 None => opts.insert_atom(cfg.into()),
272 Some(pos) => {
273 let key = &cfg[..pos];
274 let value = cfg[pos + 1..].trim_matches('"');
275 opts.insert_key_value(key.into(), value.into());
276 }
277 }
278 }
279 opts
280 };
281 256
282 let mut env = Env::default(); 257 let mut env = Env::default();
283 let mut extern_source = ExternSource::default();
284 if let Some(out_dir) = &krate.out_dir { 258 if let Some(out_dir) = &krate.out_dir {
285 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() 259 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
286 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { 260 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
287 env.set("OUT_DIR", out_dir); 261 env.set("OUT_DIR", out_dir);
288 } 262 }
289 if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
290 extern_source.set_extern_path(&out_dir, extern_source_id);
291 }
292 } 263 }
293 let proc_macro = krate 264 let proc_macro = krate
294 .proc_macro_dylib_path 265 .proc_macro_dylib_path
@@ -296,15 +267,14 @@ impl ProjectWorkspace {
296 .map(|it| proc_macro_client.by_dylib_path(&it)); 267 .map(|it| proc_macro_client.by_dylib_path(&it));
297 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env 268 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env
298 Some(( 269 Some((
299 json_project::CrateId(seq_index), 270 CrateId(seq_index as u32),
300 crate_graph.add_crate_root( 271 crate_graph.add_crate_root(
301 file_id, 272 file_id,
302 edition, 273 krate.edition,
303 // FIXME json definitions can store the crate name 274 // FIXME json definitions can store the crate name
304 None, 275 None,
305 cfg_options, 276 krate.cfg.clone(),
306 env, 277 env,
307 extern_source,
308 proc_macro.unwrap_or_default(), 278 proc_macro.unwrap_or_default(),
309 ), 279 ),
310 )) 280 ))
@@ -313,8 +283,8 @@ impl ProjectWorkspace {
313 283
314 for (id, krate) in project.crates.iter().enumerate() { 284 for (id, krate) in project.crates.iter().enumerate() {
315 for dep in &krate.deps { 285 for dep in &krate.deps {
316 let from_crate_id = json_project::CrateId(id); 286 let from_crate_id = CrateId(id as u32);
317 let to_crate_id = dep.krate; 287 let to_crate_id = dep.crate_id;
318 if let (Some(&from), Some(&to)) = 288 if let (Some(&from), Some(&to)) =
319 (crates.get(&from_crate_id), crates.get(&to_crate_id)) 289 (crates.get(&from_crate_id), crates.get(&to_crate_id))
320 { 290 {
@@ -341,7 +311,6 @@ impl ProjectWorkspace {
341 let file_id = load(&sysroot[krate].root)?; 311 let file_id = load(&sysroot[krate].root)?;
342 312
343 let env = Env::default(); 313 let env = Env::default();
344 let extern_source = ExternSource::default();
345 let proc_macro = vec![]; 314 let proc_macro = vec![];
346 let crate_name = CrateName::new(&sysroot[krate].name) 315 let crate_name = CrateName::new(&sysroot[krate].name)
347 .expect("Sysroot crate names should not contain dashes"); 316 .expect("Sysroot crate names should not contain dashes");
@@ -352,7 +321,6 @@ impl ProjectWorkspace {
352 Some(crate_name), 321 Some(crate_name),
353 cfg_options.clone(), 322 cfg_options.clone(),
354 env, 323 env,
355 extern_source,
356 proc_macro, 324 proc_macro,
357 ); 325 );
358 Some((krate, crate_id)) 326 Some((krate, crate_id))
@@ -409,15 +377,11 @@ impl ProjectWorkspace {
409 opts 377 opts
410 }; 378 };
411 let mut env = Env::default(); 379 let mut env = Env::default();
412 let mut extern_source = ExternSource::default();
413 if let Some(out_dir) = &cargo[pkg].out_dir { 380 if let Some(out_dir) = &cargo[pkg].out_dir {
414 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() 381 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
415 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { 382 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
416 env.set("OUT_DIR", out_dir); 383 env.set("OUT_DIR", out_dir);
417 } 384 }
418 if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
419 extern_source.set_extern_path(&out_dir, extern_source_id);
420 }
421 } 385 }
422 let proc_macro = cargo[pkg] 386 let proc_macro = cargo[pkg]
423 .proc_macro_dylib_path 387 .proc_macro_dylib_path
@@ -431,7 +395,6 @@ impl ProjectWorkspace {
431 Some(CrateName::normalize_dashes(&cargo[pkg].name)), 395 Some(CrateName::normalize_dashes(&cargo[pkg].name)),
432 cfg_options, 396 cfg_options,
433 env, 397 env,
434 extern_source,
435 proc_macro.clone(), 398 proc_macro.clone(),
436 ); 399 );
437 if cargo[tgt].kind == TargetKind::Lib { 400 if cargo[tgt].kind == TargetKind::Lib {
@@ -537,15 +500,15 @@ impl ProjectWorkspace {
537 crate_graph 500 crate_graph
538 } 501 }
539 502
540 pub fn workspace_root_for(&self, path: &Path) -> Option<&Path> { 503 pub fn workspace_root_for(&self, path: &Path) -> Option<&AbsPath> {
541 match self { 504 match self {
542 ProjectWorkspace::Cargo { cargo, .. } => { 505 ProjectWorkspace::Cargo { cargo, .. } => {
543 Some(cargo.workspace_root()).filter(|root| path.starts_with(root)) 506 Some(cargo.workspace_root()).filter(|root| path.starts_with(root))
544 } 507 }
545 ProjectWorkspace::Json { project: JsonProject { roots, .. }, .. } => roots 508 ProjectWorkspace::Json { project: ProjectJson { roots, .. }, .. } => roots
546 .iter() 509 .iter()
547 .find(|root| path.starts_with(&root.path)) 510 .find(|root| path.starts_with(&root.path))
548 .map(|root| root.path.as_ref()), 511 .map(|root| root.path.as_path()),
549 } 512 }
550 } 513 }
551} 514}