diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-02-29 15:36:03 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-29 15:36:03 +0000 |
commit | 099a8f37f5fd41f7afe26039b063973617133153 (patch) | |
tree | 1ee876766ad8be3b7dedefbe781f856b0cca63bd /crates | |
parent | 0ae7054210b0bbc48ea51c3672be640d3096cfdd (diff) | |
parent | b9fbb3da1740ddef0ab5d9dcbb75e50b92ba0c09 (diff) |
Merge #3309
3309: Find cargo toml up the fs r=matklad a=not-much-io
Currently rust-analyzer will look for Cargo.toml in the root of the project and if failing that then go down the filesystem until root.
This unfortunately wouldn't work automatically with (what I imagine is) a fairly common project structure. As an example with multiple languages like:
```
js/
..
rust/
Cargo.toml
...
```
Added this small change so rust-analyzer would glance one level up if not found in root or down the filesystem.
## Why not go deeper?
Could be problematic with large project vendored dependencies etc.
## Why not add a Cargo.toml manual setting option?
Loosely related and a good idea, however the convenience of having this automated also is hard to pass up.
## Testing?
Build a binary with various logs and checked it in a project with such a structure:
```
[ERROR ra_project_model] find_cargo_toml()
[ERROR ra_project_model] find_cargo_toml_up_the_fs()
[ERROR ra_project_model] entities: ReadDir("/workspaces/my-project")
[ERROR ra_project_model] candidate: "/workspaces/my-project/rust/Cargo.toml", exists: true
```
## Edge Cases?
If you have multiple Cargo.toml files one level deeper AND not in the root, will get whatever comes first (order undefined), example:
```
crate1/
Cargo.toml
crate2/
Cargo.toml
... (no root Cargo.toml)
```
However this is quite unusual and wouldn't have worked before either. This is only resolvable via manually choosing.
Co-authored-by: nmio <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_project_model/src/lib.rs | 75 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 5 |
2 files changed, 70 insertions, 10 deletions
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 9df6a0e07..bcf12460d 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -6,7 +6,7 @@ mod sysroot; | |||
6 | 6 | ||
7 | use std::{ | 7 | use std::{ |
8 | error::Error, | 8 | error::Error, |
9 | fs::File, | 9 | fs::{read_dir, File, ReadDir}, |
10 | io::BufReader, | 10 | io::BufReader, |
11 | path::{Path, PathBuf}, | 11 | path::{Path, PathBuf}, |
12 | process::Command, | 12 | process::Command, |
@@ -25,11 +25,19 @@ pub use crate::{ | |||
25 | }; | 25 | }; |
26 | 26 | ||
27 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] | 27 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
28 | pub struct CargoTomlNotFoundError(pub PathBuf); | 28 | pub struct CargoTomlNotFoundError { |
29 | pub searched_at: PathBuf, | ||
30 | pub reason: String, | ||
31 | } | ||
29 | 32 | ||
30 | impl std::fmt::Display for CargoTomlNotFoundError { | 33 | impl std::fmt::Display for CargoTomlNotFoundError { |
31 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 34 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
32 | write!(fmt, "can't find Cargo.toml at {}", self.0.display()) | 35 | write!( |
36 | fmt, | ||
37 | "can't find Cargo.toml at {}, due to {}", | ||
38 | self.searched_at.display(), | ||
39 | self.reason | ||
40 | ) | ||
33 | } | 41 | } |
34 | } | 42 | } |
35 | 43 | ||
@@ -406,19 +414,68 @@ fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | |||
406 | None | 414 | None |
407 | } | 415 | } |
408 | 416 | ||
409 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | 417 | fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> { |
410 | if path.ends_with("Cargo.toml") { | ||
411 | return Ok(path.to_path_buf()); | ||
412 | } | ||
413 | let mut curr = Some(path); | 418 | let mut curr = Some(path); |
414 | while let Some(path) = curr { | 419 | while let Some(path) = curr { |
415 | let candidate = path.join("Cargo.toml"); | 420 | let candidate = path.join("Cargo.toml"); |
416 | if candidate.exists() { | 421 | if candidate.exists() { |
417 | return Ok(candidate); | 422 | return Some(candidate); |
418 | } | 423 | } |
419 | curr = path.parent(); | 424 | curr = path.parent(); |
420 | } | 425 | } |
421 | Err(CargoTomlNotFoundError(path.to_path_buf()).into()) | 426 | |
427 | None | ||
428 | } | ||
429 | |||
430 | fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { | ||
431 | // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects | ||
432 | let mut valid_canditates = vec![]; | ||
433 | for entity in entities.filter_map(Result::ok) { | ||
434 | let candidate = entity.path().join("Cargo.toml"); | ||
435 | if candidate.exists() { | ||
436 | valid_canditates.push(candidate) | ||
437 | } | ||
438 | } | ||
439 | valid_canditates | ||
440 | } | ||
441 | |||
442 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | ||
443 | if path.ends_with("Cargo.toml") { | ||
444 | return Ok(path.to_path_buf()); | ||
445 | } | ||
446 | |||
447 | if let Some(p) = find_cargo_toml_in_parent_dir(path) { | ||
448 | return Ok(p); | ||
449 | } | ||
450 | |||
451 | let entities = match read_dir(path) { | ||
452 | Ok(entities) => entities, | ||
453 | Err(e) => { | ||
454 | return Err(CargoTomlNotFoundError { | ||
455 | searched_at: path.to_path_buf(), | ||
456 | reason: format!("file system error: {}", e), | ||
457 | } | ||
458 | .into()); | ||
459 | } | ||
460 | }; | ||
461 | |||
462 | let mut valid_canditates = find_cargo_toml_in_child_dir(entities); | ||
463 | match valid_canditates.len() { | ||
464 | 1 => Ok(valid_canditates.remove(0)), | ||
465 | 0 => Err(CargoTomlNotFoundError { | ||
466 | searched_at: path.to_path_buf(), | ||
467 | reason: "no Cargo.toml file found".to_string(), | ||
468 | } | ||
469 | .into()), | ||
470 | _ => Err(CargoTomlNotFoundError { | ||
471 | searched_at: path.to_path_buf(), | ||
472 | reason: format!( | ||
473 | "multiple equally valid Cargo.toml files found: {:?}", | ||
474 | valid_canditates | ||
475 | ), | ||
476 | } | ||
477 | .into()), | ||
478 | } | ||
422 | } | 479 | } |
423 | 480 | ||
424 | pub fn get_rustc_cfg_options() -> CfgOptions { | 481 | pub fn get_rustc_cfg_options() -> CfgOptions { |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 2b25f5443..fe804aada 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -115,12 +115,15 @@ pub fn main_loop( | |||
115 | Ok(workspace) => loaded_workspaces.push(workspace), | 115 | Ok(workspace) => loaded_workspaces.push(workspace), |
116 | Err(e) => { | 116 | Err(e) => { |
117 | log::error!("loading workspace failed: {:?}", e); | 117 | log::error!("loading workspace failed: {:?}", e); |
118 | if let Some(ra_project_model::CargoTomlNotFoundError(_)) = e.downcast_ref() | 118 | |
119 | if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = | ||
120 | e.downcast_ref() | ||
119 | { | 121 | { |
120 | if !feature_flags.get("notifications.cargo-toml-not-found") { | 122 | if !feature_flags.get("notifications.cargo-toml-not-found") { |
121 | continue; | 123 | continue; |
122 | } | 124 | } |
123 | } | 125 | } |
126 | |||
124 | show_message( | 127 | show_message( |
125 | req::MessageType::Error, | 128 | req::MessageType::Error, |
126 | format!("rust-analyzer failed to load workspace: {:?}", e), | 129 | format!("rust-analyzer failed to load workspace: {:?}", e), |