diff options
Diffstat (limited to 'crates/ra_project_model/src')
-rw-r--r-- | crates/ra_project_model/src/json_project.rs | 49 | ||||
-rw-r--r-- | crates/ra_project_model/src/lib.rs | 243 |
2 files changed, 223 insertions, 69 deletions
diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs new file mode 100644 index 000000000..9a9eb9e1f --- /dev/null +++ b/crates/ra_project_model/src/json_project.rs | |||
@@ -0,0 +1,49 @@ | |||
1 | use std::path::PathBuf; | ||
2 | |||
3 | use serde::Deserialize; | ||
4 | |||
5 | /// A root points to the directory which contains Rust crates. rust-analyzer watches all files in | ||
6 | /// all roots. Roots might be nested. | ||
7 | #[derive(Clone, Debug, Deserialize)] | ||
8 | #[serde(transparent)] | ||
9 | pub struct Root { | ||
10 | pub(crate) path: PathBuf, | ||
11 | } | ||
12 | |||
13 | /// A crate points to the root module of a crate and lists the dependencies of the crate. This is | ||
14 | /// useful in creating the crate graph. | ||
15 | #[derive(Clone, Debug, Deserialize)] | ||
16 | pub struct Crate { | ||
17 | pub(crate) root_module: PathBuf, | ||
18 | pub(crate) edition: Edition, | ||
19 | pub(crate) deps: Vec<Dep>, | ||
20 | } | ||
21 | |||
22 | #[derive(Clone, Copy, Debug, Deserialize)] | ||
23 | #[serde(rename = "edition")] | ||
24 | pub enum Edition { | ||
25 | #[serde(rename = "2015")] | ||
26 | Edition2015, | ||
27 | #[serde(rename = "2018")] | ||
28 | Edition2018, | ||
29 | } | ||
30 | |||
31 | /// Identifies a crate by position in the crates array. | ||
32 | #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] | ||
33 | #[serde(transparent)] | ||
34 | pub struct CrateId(pub usize); | ||
35 | |||
36 | /// A dependency of a crate, identified by its id in the crates array and name. | ||
37 | #[derive(Clone, Debug, Deserialize)] | ||
38 | pub struct Dep { | ||
39 | #[serde(rename = "crate")] | ||
40 | pub(crate) krate: CrateId, | ||
41 | pub(crate) name: String, | ||
42 | } | ||
43 | |||
44 | /// Roots and crates that compose this Rust project. | ||
45 | #[derive(Clone, Debug, Deserialize)] | ||
46 | pub struct JsonProject { | ||
47 | pub(crate) roots: Vec<Root>, | ||
48 | pub(crate) crates: Vec<Crate>, | ||
49 | } | ||
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 1b18ac836..c566ec0fb 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -1,15 +1,23 @@ | |||
1 | mod cargo_workspace; | 1 | mod cargo_workspace; |
2 | mod json_project; | ||
2 | mod sysroot; | 3 | mod sysroot; |
3 | 4 | ||
4 | use std::path::{Path, PathBuf}; | 5 | use std::{ |
6 | fs::File, | ||
7 | io::BufReader, | ||
8 | path::{Path, PathBuf}, | ||
9 | }; | ||
5 | 10 | ||
6 | use failure::bail; | 11 | use failure::bail; |
7 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashMap; |
8 | 13 | ||
9 | use ra_db::{CrateGraph, FileId, Edition}; | 14 | use ra_db::{CrateGraph, FileId, Edition}; |
10 | 15 | ||
16 | use serde_json::from_reader; | ||
17 | |||
11 | pub use crate::{ | 18 | pub use crate::{ |
12 | cargo_workspace::{CargoWorkspace, Package, Target, TargetKind}, | 19 | cargo_workspace::{CargoWorkspace, Package, Target, TargetKind}, |
20 | json_project::JsonProject, | ||
13 | sysroot::Sysroot, | 21 | sysroot::Sysroot, |
14 | }; | 22 | }; |
15 | 23 | ||
@@ -17,105 +25,202 @@ pub use crate::{ | |||
17 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 25 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
18 | 26 | ||
19 | #[derive(Debug, Clone)] | 27 | #[derive(Debug, Clone)] |
20 | pub struct ProjectWorkspace { | 28 | pub enum ProjectWorkspace { |
21 | pub cargo: CargoWorkspace, | 29 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. |
22 | pub sysroot: Sysroot, | 30 | Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, |
31 | /// Project workspace was manually specified using a `rust-project.json` file. | ||
32 | Json { project: JsonProject }, | ||
23 | } | 33 | } |
24 | 34 | ||
25 | impl ProjectWorkspace { | 35 | impl ProjectWorkspace { |
26 | pub fn discover(path: &Path) -> Result<ProjectWorkspace> { | 36 | pub fn discover(path: &Path) -> Result<ProjectWorkspace> { |
27 | let cargo_toml = find_cargo_toml(path)?; | 37 | match find_rust_project_json(path) { |
28 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?; | 38 | Some(json_path) => { |
29 | let sysroot = Sysroot::discover(&cargo_toml)?; | 39 | let file = File::open(json_path)?; |
30 | let res = ProjectWorkspace { cargo, sysroot }; | 40 | let reader = BufReader::new(file); |
31 | Ok(res) | 41 | Ok(ProjectWorkspace::Json { project: from_reader(reader)? }) |
42 | } | ||
43 | None => { | ||
44 | let cargo_toml = find_cargo_toml(path)?; | ||
45 | Ok(ProjectWorkspace::Cargo { | ||
46 | cargo: CargoWorkspace::from_cargo_metadata(&cargo_toml)?, | ||
47 | sysroot: Sysroot::discover(&cargo_toml)?, | ||
48 | }) | ||
49 | } | ||
50 | } | ||
32 | } | 51 | } |
33 | 52 | ||
34 | pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option<FileId>) -> CrateGraph { | 53 | pub fn to_roots(&self) -> Vec<PathBuf> { |
35 | let mut crate_graph = CrateGraph::default(); | 54 | match self { |
36 | let mut sysroot_crates = FxHashMap::default(); | 55 | ProjectWorkspace::Json { project } => { |
37 | for krate in self.sysroot.crates() { | 56 | let mut roots = Vec::with_capacity(project.roots.len()); |
38 | if let Some(file_id) = load(krate.root(&self.sysroot)) { | 57 | for root in &project.roots { |
39 | sysroot_crates | 58 | roots.push(root.path.clone()); |
40 | .insert(krate, crate_graph.add_crate_root(file_id, Edition::Edition2015)); | 59 | } |
60 | roots | ||
41 | } | 61 | } |
42 | } | 62 | ProjectWorkspace::Cargo { cargo, sysroot } => { |
43 | for from in self.sysroot.crates() { | 63 | let mut roots = |
44 | for to in from.deps(&self.sysroot) { | 64 | Vec::with_capacity(cargo.packages().count() + sysroot.crates().count()); |
45 | let name = to.name(&self.sysroot); | 65 | for pkg in cargo.packages() { |
46 | if let (Some(&from), Some(&to)) = | 66 | roots.push(pkg.root(&cargo).to_path_buf()); |
47 | (sysroot_crates.get(&from), sysroot_crates.get(&to)) | 67 | } |
48 | { | 68 | for krate in sysroot.crates() { |
49 | if let Err(_) = crate_graph.add_dep(from, name.into(), to) { | 69 | roots.push(krate.root_dir(&sysroot).to_path_buf()) |
50 | log::error!("cyclic dependency between sysroot crates") | ||
51 | } | ||
52 | } | 70 | } |
71 | roots | ||
53 | } | 72 | } |
54 | } | 73 | } |
74 | } | ||
55 | 75 | ||
56 | let libstd = self.sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); | 76 | pub fn count(&self) -> usize { |
57 | 77 | match self { | |
58 | let mut pkg_to_lib_crate = FxHashMap::default(); | 78 | ProjectWorkspace::Json { project } => project.crates.len(), |
59 | let mut pkg_crates = FxHashMap::default(); | 79 | ProjectWorkspace::Cargo { cargo, .. } => cargo.packages().count(), |
60 | // Next, create crates for each package, target pair | 80 | } |
61 | for pkg in self.cargo.packages() { | 81 | } |
62 | let mut lib_tgt = None; | 82 | |
63 | for tgt in pkg.targets(&self.cargo) { | 83 | pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option<FileId>) -> CrateGraph { |
64 | let root = tgt.root(&self.cargo); | 84 | let mut crate_graph = CrateGraph::default(); |
65 | if let Some(file_id) = load(root) { | 85 | match self { |
66 | let edition = pkg.edition(&self.cargo); | 86 | ProjectWorkspace::Json { project } => { |
67 | let crate_id = crate_graph.add_crate_root(file_id, edition); | 87 | let mut crates = FxHashMap::default(); |
68 | if tgt.kind(&self.cargo) == TargetKind::Lib { | 88 | for (id, krate) in project.crates.iter().enumerate() { |
69 | lib_tgt = Some(crate_id); | 89 | let crate_id = json_project::CrateId(id); |
70 | pkg_to_lib_crate.insert(pkg, crate_id); | 90 | if let Some(file_id) = load(&krate.root_module) { |
91 | let edition = match krate.edition { | ||
92 | json_project::Edition::Edition2015 => Edition::Edition2015, | ||
93 | json_project::Edition::Edition2018 => Edition::Edition2018, | ||
94 | }; | ||
95 | crates.insert(crate_id, crate_graph.add_crate_root(file_id, edition)); | ||
71 | } | 96 | } |
72 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
73 | } | 97 | } |
74 | } | ||
75 | 98 | ||
76 | // Set deps to the std and to the lib target of the current package | 99 | for (id, krate) in project.crates.iter().enumerate() { |
77 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | 100 | for dep in &krate.deps { |
78 | if let Some(to) = lib_tgt { | 101 | let from_crate_id = json_project::CrateId(id); |
79 | if to != from { | 102 | let to_crate_id = dep.krate; |
80 | if let Err(_) = crate_graph.add_dep(from, pkg.name(&self.cargo).into(), to) | 103 | if let (Some(&from), Some(&to)) = |
104 | (crates.get(&from_crate_id), crates.get(&to_crate_id)) | ||
81 | { | 105 | { |
82 | log::error!( | 106 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { |
83 | "cyclic dependency between targets of {}", | 107 | log::error!( |
84 | pkg.name(&self.cargo) | 108 | "cyclic dependency {:?} -> {:?}", |
85 | ) | 109 | from_crate_id, |
110 | to_crate_id | ||
111 | ); | ||
112 | } | ||
86 | } | 113 | } |
87 | } | 114 | } |
88 | } | 115 | } |
89 | if let Some(std) = libstd { | 116 | } |
90 | if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { | 117 | ProjectWorkspace::Cargo { cargo, sysroot } => { |
91 | log::error!("cyclic dependency on std for {}", pkg.name(&self.cargo)) | 118 | let mut sysroot_crates = FxHashMap::default(); |
119 | for krate in sysroot.crates() { | ||
120 | if let Some(file_id) = load(krate.root(&sysroot)) { | ||
121 | sysroot_crates.insert( | ||
122 | krate, | ||
123 | crate_graph.add_crate_root(file_id, Edition::Edition2015), | ||
124 | ); | ||
92 | } | 125 | } |
93 | } | 126 | } |
94 | } | 127 | for from in sysroot.crates() { |
95 | } | 128 | for to in from.deps(&sysroot) { |
129 | let name = to.name(&sysroot); | ||
130 | if let (Some(&from), Some(&to)) = | ||
131 | (sysroot_crates.get(&from), sysroot_crates.get(&to)) | ||
132 | { | ||
133 | if let Err(_) = crate_graph.add_dep(from, name.into(), to) { | ||
134 | log::error!("cyclic dependency between sysroot crates") | ||
135 | } | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | let libstd = sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); | ||
141 | |||
142 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
143 | let mut pkg_crates = FxHashMap::default(); | ||
144 | // Next, create crates for each package, target pair | ||
145 | for pkg in cargo.packages() { | ||
146 | let mut lib_tgt = None; | ||
147 | for tgt in pkg.targets(&cargo) { | ||
148 | let root = tgt.root(&cargo); | ||
149 | if let Some(file_id) = load(root) { | ||
150 | let edition = pkg.edition(&cargo); | ||
151 | let crate_id = crate_graph.add_crate_root(file_id, edition); | ||
152 | if tgt.kind(&cargo) == TargetKind::Lib { | ||
153 | lib_tgt = Some(crate_id); | ||
154 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
155 | } | ||
156 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
157 | } | ||
158 | } | ||
96 | 159 | ||
97 | // Now add a dep ednge from all targets of upstream to the lib | 160 | // Set deps to the std and to the lib target of the current package |
98 | // target of downstream. | ||
99 | for pkg in self.cargo.packages() { | ||
100 | for dep in pkg.dependencies(&self.cargo) { | ||
101 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
102 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | 161 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { |
103 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { | 162 | if let Some(to) = lib_tgt { |
104 | log::error!( | 163 | if to != from { |
105 | "cyclic dependency {} -> {}", | 164 | if let Err(_) = |
106 | pkg.name(&self.cargo), | 165 | crate_graph.add_dep(from, pkg.name(&cargo).into(), to) |
107 | dep.pkg.name(&self.cargo) | 166 | { |
108 | ) | 167 | log::error!( |
168 | "cyclic dependency between targets of {}", | ||
169 | pkg.name(&cargo) | ||
170 | ) | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | if let Some(std) = libstd { | ||
175 | if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { | ||
176 | log::error!("cyclic dependency on std for {}", pkg.name(&cargo)) | ||
177 | } | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | |||
182 | // Now add a dep ednge from all targets of upstream to the lib | ||
183 | // target of downstream. | ||
184 | for pkg in cargo.packages() { | ||
185 | for dep in pkg.dependencies(&cargo) { | ||
186 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
187 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
188 | if let Err(_) = | ||
189 | crate_graph.add_dep(from, dep.name.clone().into(), to) | ||
190 | { | ||
191 | log::error!( | ||
192 | "cyclic dependency {} -> {}", | ||
193 | pkg.name(&cargo), | ||
194 | dep.pkg.name(&cargo) | ||
195 | ) | ||
196 | } | ||
197 | } | ||
109 | } | 198 | } |
110 | } | 199 | } |
111 | } | 200 | } |
112 | } | 201 | } |
113 | } | 202 | } |
114 | |||
115 | crate_graph | 203 | crate_graph |
116 | } | 204 | } |
117 | } | 205 | } |
118 | 206 | ||
207 | fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | ||
208 | if path.ends_with("rust-project.json") { | ||
209 | return Some(path.to_path_buf()); | ||
210 | } | ||
211 | |||
212 | let mut curr = Some(path); | ||
213 | while let Some(path) = curr { | ||
214 | let candidate = path.join("rust-project.json"); | ||
215 | if candidate.exists() { | ||
216 | return Some(candidate); | ||
217 | } | ||
218 | curr = path.parent(); | ||
219 | } | ||
220 | |||
221 | None | ||
222 | } | ||
223 | |||
119 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | 224 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { |
120 | if path.ends_with("Cargo.toml") { | 225 | if path.ends_with("Cargo.toml") { |
121 | return Ok(path.to_path_buf()); | 226 | return Ok(path.to_path_buf()); |