diff options
Diffstat (limited to 'crates/ra_lsp_server/src/project_model.rs')
-rw-r--r-- | crates/ra_lsp_server/src/project_model.rs | 207 |
1 files changed, 30 insertions, 177 deletions
diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs index 9f429c9a1..fd5875a0a 100644 --- a/crates/ra_lsp_server/src/project_model.rs +++ b/crates/ra_lsp_server/src/project_model.rs | |||
@@ -1,163 +1,46 @@ | |||
1 | mod cargo_workspace; | ||
2 | mod sysroot; | ||
3 | |||
1 | use std::path::{Path, PathBuf}; | 4 | use std::path::{Path, PathBuf}; |
2 | 5 | ||
3 | use cargo_metadata::{metadata_run, CargoOpt}; | 6 | use failure::bail; |
4 | use ra_syntax::SmolStr; | ||
5 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | use failure::{format_err, bail}; | ||
8 | use thread_worker::{WorkerHandle, Worker}; | 7 | use thread_worker::{WorkerHandle, Worker}; |
9 | 8 | ||
10 | use crate::Result; | 9 | use crate::Result; |
11 | 10 | ||
12 | /// `CargoWorksapce` represents the logical structure of, well, a Cargo | 11 | pub use crate::project_model::{ |
13 | /// workspace. It pretty closely mirrors `cargo metadata` output. | 12 | cargo_workspace::{CargoWorkspace, Package, Target, TargetKind}, |
14 | /// | 13 | sysroot::Sysroot, |
15 | /// Note that internally, rust analyzer uses a differnet structure: | 14 | }; |
16 | /// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, | ||
17 | /// while this knows about `Pacakges` & `Targets`: purely cargo-related | ||
18 | /// concepts. | ||
19 | #[derive(Debug, Clone)] | ||
20 | pub struct CargoWorkspace { | ||
21 | packages: Arena<Package, PackageData>, | ||
22 | targets: Arena<Target, TargetData>, | ||
23 | } | ||
24 | |||
25 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
26 | pub struct Package(RawId); | ||
27 | impl_arena_id!(Package); | ||
28 | |||
29 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
30 | pub struct Target(RawId); | ||
31 | impl_arena_id!(Target); | ||
32 | |||
33 | #[derive(Debug, Clone)] | ||
34 | struct PackageData { | ||
35 | name: SmolStr, | ||
36 | manifest: PathBuf, | ||
37 | targets: Vec<Target>, | ||
38 | is_member: bool, | ||
39 | dependencies: Vec<PackageDependency>, | ||
40 | } | ||
41 | 15 | ||
42 | #[derive(Debug, Clone)] | 16 | #[derive(Debug, Clone)] |
43 | pub struct PackageDependency { | 17 | pub struct ProjectWorkspace { |
44 | pub pkg: Package, | 18 | pub(crate) cargo: CargoWorkspace, |
45 | pub name: SmolStr, | 19 | pub(crate) sysroot: Sysroot, |
46 | } | 20 | } |
47 | 21 | ||
48 | #[derive(Debug, Clone)] | 22 | impl ProjectWorkspace { |
49 | struct TargetData { | 23 | pub fn discover(path: &Path) -> Result<ProjectWorkspace> { |
50 | pkg: Package, | 24 | let cargo_toml = find_cargo_toml(path)?; |
51 | name: SmolStr, | 25 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?; |
52 | root: PathBuf, | 26 | let sysroot = Sysroot::discover(&cargo_toml)?; |
53 | kind: TargetKind, | 27 | let res = ProjectWorkspace { cargo, sysroot }; |
54 | } | 28 | Ok(res) |
55 | |||
56 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
57 | pub enum TargetKind { | ||
58 | Bin, | ||
59 | Lib, | ||
60 | Example, | ||
61 | Test, | ||
62 | Bench, | ||
63 | Other, | ||
64 | } | ||
65 | |||
66 | impl Package { | ||
67 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
68 | ws.packages[self].name.as_str() | ||
69 | } | ||
70 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
71 | ws.packages[self].manifest.parent().unwrap() | ||
72 | } | ||
73 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { | ||
74 | ws.packages[self].targets.iter().cloned() | ||
75 | } | ||
76 | #[allow(unused)] | ||
77 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { | ||
78 | ws.packages[self].is_member | ||
79 | } | ||
80 | pub fn dependencies<'a>( | ||
81 | self, | ||
82 | ws: &'a CargoWorkspace, | ||
83 | ) -> impl Iterator<Item = &'a PackageDependency> + 'a { | ||
84 | ws.packages[self].dependencies.iter() | ||
85 | } | ||
86 | } | ||
87 | |||
88 | impl Target { | ||
89 | pub fn package(self, ws: &CargoWorkspace) -> Package { | ||
90 | ws.targets[self].pkg | ||
91 | } | ||
92 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
93 | ws.targets[self].name.as_str() | ||
94 | } | ||
95 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
96 | ws.targets[self].root.as_path() | ||
97 | } | ||
98 | pub fn kind(self, ws: &CargoWorkspace) -> TargetKind { | ||
99 | ws.targets[self].kind | ||
100 | } | 29 | } |
101 | } | 30 | } |
102 | 31 | ||
103 | impl CargoWorkspace { | 32 | pub fn workspace_loader() -> (Worker<PathBuf, Result<ProjectWorkspace>>, WorkerHandle) { |
104 | pub fn from_cargo_metadata(path: &Path) -> Result<CargoWorkspace> { | 33 | thread_worker::spawn::<PathBuf, Result<ProjectWorkspace>, _>( |
105 | let cargo_toml = find_cargo_toml(path)?; | 34 | "workspace loader", |
106 | let meta = metadata_run( | 35 | 1, |
107 | Some(cargo_toml.as_path()), | 36 | |input_receiver, output_sender| { |
108 | true, | 37 | input_receiver |
109 | Some(CargoOpt::AllFeatures), | 38 | .into_iter() |
110 | ) | 39 | .map(|path| ProjectWorkspace::discover(path.as_path())) |
111 | .map_err(|e| format_err!("cargo metadata failed: {}", e))?; | 40 | .try_for_each(|it| output_sender.send(it)) |
112 | let mut pkg_by_id = FxHashMap::default(); | 41 | .unwrap() |
113 | let mut packages = Arena::default(); | 42 | }, |
114 | let mut targets = Arena::default(); | 43 | ) |
115 | |||
116 | let ws_members = &meta.workspace_members; | ||
117 | |||
118 | for meta_pkg in meta.packages { | ||
119 | let is_member = ws_members.contains(&meta_pkg.id); | ||
120 | let pkg = packages.alloc(PackageData { | ||
121 | name: meta_pkg.name.into(), | ||
122 | manifest: meta_pkg.manifest_path.clone(), | ||
123 | targets: Vec::new(), | ||
124 | is_member, | ||
125 | dependencies: Vec::new(), | ||
126 | }); | ||
127 | let pkg_data = &mut packages[pkg]; | ||
128 | pkg_by_id.insert(meta_pkg.id.clone(), pkg); | ||
129 | for meta_tgt in meta_pkg.targets { | ||
130 | let tgt = targets.alloc(TargetData { | ||
131 | pkg, | ||
132 | name: meta_tgt.name.into(), | ||
133 | root: meta_tgt.src_path.clone(), | ||
134 | kind: TargetKind::new(meta_tgt.kind.as_slice()), | ||
135 | }); | ||
136 | pkg_data.targets.push(tgt); | ||
137 | } | ||
138 | } | ||
139 | let resolve = meta.resolve.expect("metadata executed with deps"); | ||
140 | for node in resolve.nodes { | ||
141 | let source = pkg_by_id[&node.id]; | ||
142 | for dep_node in node.deps { | ||
143 | let dep = PackageDependency { | ||
144 | name: dep_node.name.into(), | ||
145 | pkg: pkg_by_id[&dep_node.pkg], | ||
146 | }; | ||
147 | packages[source].dependencies.push(dep); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | Ok(CargoWorkspace { packages, targets }) | ||
152 | } | ||
153 | pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { | ||
154 | self.packages.iter().map(|(id, _pkg)| id) | ||
155 | } | ||
156 | pub fn target_by_root(&self, root: &Path) -> Option<Target> { | ||
157 | self.packages() | ||
158 | .filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)) | ||
159 | .next() | ||
160 | } | ||
161 | } | 44 | } |
162 | 45 | ||
163 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | 46 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { |
@@ -174,33 +57,3 @@ fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | |||
174 | } | 57 | } |
175 | bail!("can't find Cargo.toml at {}", path.display()) | 58 | bail!("can't find Cargo.toml at {}", path.display()) |
176 | } | 59 | } |
177 | |||
178 | impl TargetKind { | ||
179 | fn new(kinds: &[String]) -> TargetKind { | ||
180 | for kind in kinds { | ||
181 | return match kind.as_str() { | ||
182 | "bin" => TargetKind::Bin, | ||
183 | "test" => TargetKind::Test, | ||
184 | "bench" => TargetKind::Bench, | ||
185 | "example" => TargetKind::Example, | ||
186 | _ if kind.contains("lib") => TargetKind::Lib, | ||
187 | _ => continue, | ||
188 | }; | ||
189 | } | ||
190 | TargetKind::Other | ||
191 | } | ||
192 | } | ||
193 | |||
194 | pub fn workspace_loader() -> (Worker<PathBuf, Result<CargoWorkspace>>, WorkerHandle) { | ||
195 | thread_worker::spawn::<PathBuf, Result<CargoWorkspace>, _>( | ||
196 | "workspace loader", | ||
197 | 1, | ||
198 | |input_receiver, output_sender| { | ||
199 | input_receiver | ||
200 | .into_iter() | ||
201 | .map(|path| CargoWorkspace::from_cargo_metadata(path.as_path())) | ||
202 | .try_for_each(|it| output_sender.send(it)) | ||
203 | .unwrap() | ||
204 | }, | ||
205 | ) | ||
206 | } | ||