]> git.lizzy.rs Git - rust.git/blob - crates/ra_project_model/src/cargo_workspace.rs
Remove SmolStr from project model
[rust.git] / crates / ra_project_model / src / cargo_workspace.rs
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 }