diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-09 10:21:13 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-09 10:21:13 +0000 |
commit | d0a32627a741826502692f2c3de71512b7ec23cf (patch) | |
tree | ee3cef56bd9738f5c3b98caae9b8ce1b8dbf401f /crates/ra_project_model/src/cargo_workspace.rs | |
parent | 34398a8756b56c323d3b4b2ef32fbca32d88a105 (diff) | |
parent | e91a46eb0c4a355af25656d77dead55c2e29258e (diff) |
Merge #767
767: Extract project model to separate crate r=matklad a=flodiebold
I'm looking into creating a separate crate that would allow getting a HIR db for a project for 'batch' analyses, and this seems to be an obvious first step. We'd probably want to change the error handling to not rely on failure, though, right?
Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_project_model/src/cargo_workspace.rs')
-rw-r--r-- | crates/ra_project_model/src/cargo_workspace.rs | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs new file mode 100644 index 000000000..8adf463a6 --- /dev/null +++ b/crates/ra_project_model/src/cargo_workspace.rs | |||
@@ -0,0 +1,172 @@ | |||
1 | use std::path::{Path, PathBuf}; | ||
2 | |||
3 | use cargo_metadata::{MetadataCommand, CargoOpt}; | ||
4 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | use failure::format_err; | ||
7 | |||
8 | use crate::Result; | ||
9 | |||
10 | /// `CargoWorkspace` represents the logical structure of, well, a Cargo | ||
11 | /// workspace. It pretty closely mirrors `cargo metadata` output. | ||
12 | /// | ||
13 | /// Note that internally, rust analyzer uses a different structure: | ||
14 | /// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, | ||
15 | /// while this knows about `Pacakges` & `Targets`: purely cargo-related | ||
16 | /// concepts. | ||
17 | #[derive(Debug, Clone)] | ||
18 | pub struct CargoWorkspace { | ||
19 | packages: Arena<Package, PackageData>, | ||
20 | targets: Arena<Target, TargetData>, | ||
21 | } | ||
22 | |||
23 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
24 | pub struct Package(RawId); | ||
25 | impl_arena_id!(Package); | ||
26 | |||
27 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
28 | pub struct Target(RawId); | ||
29 | impl_arena_id!(Target); | ||
30 | |||
31 | #[derive(Debug, Clone)] | ||
32 | struct PackageData { | ||
33 | name: String, | ||
34 | manifest: PathBuf, | ||
35 | targets: Vec<Target>, | ||
36 | is_member: bool, | ||
37 | dependencies: Vec<PackageDependency>, | ||
38 | } | ||
39 | |||
40 | #[derive(Debug, Clone)] | ||
41 | pub struct PackageDependency { | ||
42 | pub pkg: Package, | ||
43 | pub name: String, | ||
44 | } | ||
45 | |||
46 | #[derive(Debug, Clone)] | ||
47 | struct TargetData { | ||
48 | pkg: Package, | ||
49 | name: String, | ||
50 | root: PathBuf, | ||
51 | kind: TargetKind, | ||
52 | } | ||
53 | |||
54 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
55 | pub enum TargetKind { | ||
56 | Bin, | ||
57 | Lib, | ||
58 | Example, | ||
59 | Test, | ||
60 | Bench, | ||
61 | Other, | ||
62 | } | ||
63 | |||
64 | impl TargetKind { | ||
65 | fn new(kinds: &[String]) -> TargetKind { | ||
66 | for kind in kinds { | ||
67 | return match kind.as_str() { | ||
68 | "bin" => TargetKind::Bin, | ||
69 | "test" => TargetKind::Test, | ||
70 | "bench" => TargetKind::Bench, | ||
71 | "example" => TargetKind::Example, | ||
72 | _ if kind.contains("lib") => TargetKind::Lib, | ||
73 | _ => continue, | ||
74 | }; | ||
75 | } | ||
76 | TargetKind::Other | ||
77 | } | ||
78 | } | ||
79 | |||
80 | impl Package { | ||
81 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
82 | ws.packages[self].name.as_str() | ||
83 | } | ||
84 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
85 | ws.packages[self].manifest.parent().unwrap() | ||
86 | } | ||
87 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { | ||
88 | ws.packages[self].targets.iter().cloned() | ||
89 | } | ||
90 | #[allow(unused)] | ||
91 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { | ||
92 | ws.packages[self].is_member | ||
93 | } | ||
94 | pub fn dependencies<'a>( | ||
95 | self, | ||
96 | ws: &'a CargoWorkspace, | ||
97 | ) -> impl Iterator<Item = &'a PackageDependency> + 'a { | ||
98 | ws.packages[self].dependencies.iter() | ||
99 | } | ||
100 | } | ||
101 | |||
102 | impl Target { | ||
103 | pub fn package(self, ws: &CargoWorkspace) -> Package { | ||
104 | ws.targets[self].pkg | ||
105 | } | ||
106 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
107 | ws.targets[self].name.as_str() | ||
108 | } | ||
109 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
110 | ws.targets[self].root.as_path() | ||
111 | } | ||
112 | pub fn kind(self, ws: &CargoWorkspace) -> TargetKind { | ||
113 | ws.targets[self].kind | ||
114 | } | ||
115 | } | ||
116 | |||
117 | impl CargoWorkspace { | ||
118 | pub fn from_cargo_metadata(cargo_toml: &Path) -> Result<CargoWorkspace> { | ||
119 | let mut meta = MetadataCommand::new(); | ||
120 | meta.manifest_path(cargo_toml).features(CargoOpt::AllFeatures); | ||
121 | if let Some(parent) = cargo_toml.parent() { | ||
122 | meta.current_dir(parent); | ||
123 | } | ||
124 | let meta = meta.exec().map_err(|e| format_err!("cargo metadata failed: {}", e))?; | ||
125 | let mut pkg_by_id = FxHashMap::default(); | ||
126 | let mut packages = Arena::default(); | ||
127 | let mut targets = Arena::default(); | ||
128 | |||
129 | let ws_members = &meta.workspace_members; | ||
130 | |||
131 | for meta_pkg in meta.packages { | ||
132 | let is_member = ws_members.contains(&meta_pkg.id); | ||
133 | let pkg = packages.alloc(PackageData { | ||
134 | name: meta_pkg.name.into(), | ||
135 | manifest: meta_pkg.manifest_path.clone(), | ||
136 | targets: Vec::new(), | ||
137 | is_member, | ||
138 | dependencies: Vec::new(), | ||
139 | }); | ||
140 | let pkg_data = &mut packages[pkg]; | ||
141 | pkg_by_id.insert(meta_pkg.id.clone(), pkg); | ||
142 | for meta_tgt in meta_pkg.targets { | ||
143 | let tgt = targets.alloc(TargetData { | ||
144 | pkg, | ||
145 | name: meta_tgt.name.into(), | ||
146 | root: meta_tgt.src_path.clone(), | ||
147 | kind: TargetKind::new(meta_tgt.kind.as_slice()), | ||
148 | }); | ||
149 | pkg_data.targets.push(tgt); | ||
150 | } | ||
151 | } | ||
152 | let resolve = meta.resolve.expect("metadata executed with deps"); | ||
153 | for node in resolve.nodes { | ||
154 | let source = pkg_by_id[&node.id]; | ||
155 | for dep_node in node.deps { | ||
156 | let dep = | ||
157 | PackageDependency { name: dep_node.name.into(), pkg: pkg_by_id[&dep_node.pkg] }; | ||
158 | packages[source].dependencies.push(dep); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | Ok(CargoWorkspace { packages, targets }) | ||
163 | } | ||
164 | |||
165 | pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { | ||
166 | self.packages.iter().map(|(id, _pkg)| id) | ||
167 | } | ||
168 | |||
169 | pub fn target_by_root(&self, root: &Path) -> Option<Target> { | ||
170 | self.packages().filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)).next() | ||
171 | } | ||
172 | } | ||