diff options
author | David Wood <[email protected]> | 2019-03-05 21:29:23 +0000 |
---|---|---|
committer | David Wood <[email protected]> | 2019-03-07 00:05:03 +0000 |
commit | 00d927a1885ec2938d3365a8e136993445b437f5 (patch) | |
tree | e60f63e68def477d5b7ceb39dcc965ff128d6e19 /crates/ra_project_model/src | |
parent | b1a1d20e067c25fb80fbab43b2956b6747a8dd3c (diff) |
Initial implementation of project-lock.json.
This commit adds a initial implementation of project-lock.json, a build
system agnostic method of specifying the crate graph and roots.
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 | 238 |
2 files changed, 218 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..ded222446 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,197 @@ 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 add_roots(&self, roots: &mut 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 | for root in &project.roots { |
38 | if let Some(file_id) = load(krate.root(&self.sysroot)) { | 57 | roots.push(root.path.clone()); |
39 | sysroot_crates | 58 | } |
40 | .insert(krate, crate_graph.add_crate_root(file_id, Edition::Edition2015)); | ||
41 | } | 59 | } |
42 | } | 60 | ProjectWorkspace::Cargo { cargo, sysroot } => { |
43 | for from in self.sysroot.crates() { | 61 | for pkg in cargo.packages() { |
44 | for to in from.deps(&self.sysroot) { | 62 | roots.push(pkg.root(&cargo).to_path_buf()); |
45 | let name = to.name(&self.sysroot); | 63 | } |
46 | if let (Some(&from), Some(&to)) = | 64 | for krate in sysroot.crates() { |
47 | (sysroot_crates.get(&from), sysroot_crates.get(&to)) | 65 | roots.push(krate.root_dir(&sysroot).to_path_buf()) |
48 | { | ||
49 | if let Err(_) = crate_graph.add_dep(from, name.into(), to) { | ||
50 | log::error!("cyclic dependency between sysroot crates") | ||
51 | } | ||
52 | } | 66 | } |
53 | } | 67 | } |
54 | } | 68 | } |
69 | } | ||
55 | 70 | ||
56 | let libstd = self.sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); | 71 | pub fn count(&self) -> usize { |
57 | 72 | match self { | |
58 | let mut pkg_to_lib_crate = FxHashMap::default(); | 73 | ProjectWorkspace::Json { project } => project.crates.len(), |
59 | let mut pkg_crates = FxHashMap::default(); | 74 | ProjectWorkspace::Cargo { cargo, .. } => cargo.packages().count(), |
60 | // Next, create crates for each package, target pair | 75 | } |
61 | for pkg in self.cargo.packages() { | 76 | } |
62 | let mut lib_tgt = None; | 77 | |
63 | for tgt in pkg.targets(&self.cargo) { | 78 | pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option<FileId>) -> CrateGraph { |
64 | let root = tgt.root(&self.cargo); | 79 | let mut crate_graph = CrateGraph::default(); |
65 | if let Some(file_id) = load(root) { | 80 | match self { |
66 | let edition = pkg.edition(&self.cargo); | 81 | ProjectWorkspace::Json { project } => { |
67 | let crate_id = crate_graph.add_crate_root(file_id, edition); | 82 | let mut crates = FxHashMap::default(); |
68 | if tgt.kind(&self.cargo) == TargetKind::Lib { | 83 | for (id, krate) in project.crates.iter().enumerate() { |
69 | lib_tgt = Some(crate_id); | 84 | let crate_id = json_project::CrateId(id); |
70 | pkg_to_lib_crate.insert(pkg, crate_id); | 85 | if let Some(file_id) = load(&krate.root_module) { |
86 | let edition = match krate.edition { | ||
87 | json_project::Edition::Edition2015 => Edition::Edition2015, | ||
88 | json_project::Edition::Edition2018 => Edition::Edition2018, | ||
89 | }; | ||
90 | crates.insert(crate_id, crate_graph.add_crate_root(file_id, edition)); | ||
71 | } | 91 | } |
72 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
73 | } | 92 | } |
74 | } | ||
75 | 93 | ||
76 | // Set deps to the std and to the lib target of the current package | 94 | for (id, krate) in project.crates.iter().enumerate() { |
77 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | 95 | for dep in &krate.deps { |
78 | if let Some(to) = lib_tgt { | 96 | let from_crate_id = json_project::CrateId(id); |
79 | if to != from { | 97 | let to_crate_id = dep.krate; |
80 | if let Err(_) = crate_graph.add_dep(from, pkg.name(&self.cargo).into(), to) | 98 | if let (Some(&from), Some(&to)) = |
99 | (crates.get(&from_crate_id), crates.get(&to_crate_id)) | ||
81 | { | 100 | { |
82 | log::error!( | 101 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { |
83 | "cyclic dependency between targets of {}", | 102 | log::error!( |
84 | pkg.name(&self.cargo) | 103 | "cyclic dependency {:?} -> {:?}", |
85 | ) | 104 | from_crate_id, |
105 | to_crate_id | ||
106 | ); | ||
107 | } | ||
86 | } | 108 | } |
87 | } | 109 | } |
88 | } | 110 | } |
89 | if let Some(std) = libstd { | 111 | } |
90 | if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { | 112 | ProjectWorkspace::Cargo { cargo, sysroot } => { |
91 | log::error!("cyclic dependency on std for {}", pkg.name(&self.cargo)) | 113 | let mut sysroot_crates = FxHashMap::default(); |
114 | for krate in sysroot.crates() { | ||
115 | if let Some(file_id) = load(krate.root(&sysroot)) { | ||
116 | sysroot_crates.insert( | ||
117 | krate, | ||
118 | crate_graph.add_crate_root(file_id, Edition::Edition2015), | ||
119 | ); | ||
92 | } | 120 | } |
93 | } | 121 | } |
94 | } | 122 | for from in sysroot.crates() { |
95 | } | 123 | for to in from.deps(&sysroot) { |
124 | let name = to.name(&sysroot); | ||
125 | if let (Some(&from), Some(&to)) = | ||
126 | (sysroot_crates.get(&from), sysroot_crates.get(&to)) | ||
127 | { | ||
128 | if let Err(_) = crate_graph.add_dep(from, name.into(), to) { | ||
129 | log::error!("cyclic dependency between sysroot crates") | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | let libstd = sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); | ||
136 | |||
137 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
138 | let mut pkg_crates = FxHashMap::default(); | ||
139 | // Next, create crates for each package, target pair | ||
140 | for pkg in cargo.packages() { | ||
141 | let mut lib_tgt = None; | ||
142 | for tgt in pkg.targets(&cargo) { | ||
143 | let root = tgt.root(&cargo); | ||
144 | if let Some(file_id) = load(root) { | ||
145 | let edition = pkg.edition(&cargo); | ||
146 | let crate_id = crate_graph.add_crate_root(file_id, edition); | ||
147 | if tgt.kind(&cargo) == TargetKind::Lib { | ||
148 | lib_tgt = Some(crate_id); | ||
149 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
150 | } | ||
151 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
152 | } | ||
153 | } | ||
96 | 154 | ||
97 | // Now add a dep ednge from all targets of upstream to the lib | 155 | // 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() { | 156 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { |
103 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { | 157 | if let Some(to) = lib_tgt { |
104 | log::error!( | 158 | if to != from { |
105 | "cyclic dependency {} -> {}", | 159 | if let Err(_) = |
106 | pkg.name(&self.cargo), | 160 | crate_graph.add_dep(from, pkg.name(&cargo).into(), to) |
107 | dep.pkg.name(&self.cargo) | 161 | { |
108 | ) | 162 | log::error!( |
163 | "cyclic dependency between targets of {}", | ||
164 | pkg.name(&cargo) | ||
165 | ) | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | if let Some(std) = libstd { | ||
170 | if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { | ||
171 | log::error!("cyclic dependency on std for {}", pkg.name(&cargo)) | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | |||
177 | // Now add a dep ednge from all targets of upstream to the lib | ||
178 | // target of downstream. | ||
179 | for pkg in cargo.packages() { | ||
180 | for dep in pkg.dependencies(&cargo) { | ||
181 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
182 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
183 | if let Err(_) = | ||
184 | crate_graph.add_dep(from, dep.name.clone().into(), to) | ||
185 | { | ||
186 | log::error!( | ||
187 | "cyclic dependency {} -> {}", | ||
188 | pkg.name(&cargo), | ||
189 | dep.pkg.name(&cargo) | ||
190 | ) | ||
191 | } | ||
192 | } | ||
109 | } | 193 | } |
110 | } | 194 | } |
111 | } | 195 | } |
112 | } | 196 | } |
113 | } | 197 | } |
114 | |||
115 | crate_graph | 198 | crate_graph |
116 | } | 199 | } |
117 | } | 200 | } |
118 | 201 | ||
202 | fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | ||
203 | if path.ends_with("rust-project.json") { | ||
204 | return Some(path.to_path_buf()); | ||
205 | } | ||
206 | |||
207 | let mut curr = Some(path); | ||
208 | while let Some(path) = curr { | ||
209 | let candidate = path.join("rust-project.json"); | ||
210 | if candidate.exists() { | ||
211 | return Some(candidate); | ||
212 | } | ||
213 | curr = path.parent(); | ||
214 | } | ||
215 | |||
216 | None | ||
217 | } | ||
218 | |||
119 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | 219 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { |
120 | if path.ends_with("Cargo.toml") { | 220 | if path.ends_with("Cargo.toml") { |
121 | return Ok(path.to_path_buf()); | 221 | return Ok(path.to_path_buf()); |