diff options
author | Pavan Kumar Sunkara <[email protected]> | 2020-08-13 11:05:30 +0100 |
---|---|---|
committer | Pavan Kumar Sunkara <[email protected]> | 2020-08-13 11:05:30 +0100 |
commit | eac24d52e672c0a9c118e8969bf1b839c3e7f1f3 (patch) | |
tree | 4e1218cc53bef75f54df35be80c6a254b85b8d9c /crates/ra_project_model/src/cargo_workspace.rs | |
parent | b5cb16fb90b4a1076604c5795552ee4abe07a057 (diff) |
Rename ra_project_model -> project_model
Diffstat (limited to 'crates/ra_project_model/src/cargo_workspace.rs')
-rw-r--r-- | crates/ra_project_model/src/cargo_workspace.rs | 362 |
1 files changed, 0 insertions, 362 deletions
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs deleted file mode 100644 index abf8dca96..000000000 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ /dev/null | |||
@@ -1,362 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::{ | ||
4 | ffi::OsStr, | ||
5 | ops, | ||
6 | path::{Path, PathBuf}, | ||
7 | process::Command, | ||
8 | }; | ||
9 | |||
10 | use anyhow::{Context, Result}; | ||
11 | use arena::{Arena, Idx}; | ||
12 | use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; | ||
13 | use paths::{AbsPath, AbsPathBuf}; | ||
14 | use ra_db::Edition; | ||
15 | use rustc_hash::FxHashMap; | ||
16 | |||
17 | use crate::cfg_flag::CfgFlag; | ||
18 | |||
19 | /// `CargoWorkspace` represents the logical structure of, well, a Cargo | ||
20 | /// workspace. It pretty closely mirrors `cargo metadata` output. | ||
21 | /// | ||
22 | /// Note that internally, rust analyzer uses a different structure: | ||
23 | /// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, | ||
24 | /// while this knows about `Packages` & `Targets`: purely cargo-related | ||
25 | /// concepts. | ||
26 | /// | ||
27 | /// We use absolute paths here, `cargo metadata` guarantees to always produce | ||
28 | /// abs paths. | ||
29 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
30 | pub struct CargoWorkspace { | ||
31 | packages: Arena<PackageData>, | ||
32 | targets: Arena<TargetData>, | ||
33 | workspace_root: AbsPathBuf, | ||
34 | } | ||
35 | |||
36 | impl ops::Index<Package> for CargoWorkspace { | ||
37 | type Output = PackageData; | ||
38 | fn index(&self, index: Package) -> &PackageData { | ||
39 | &self.packages[index] | ||
40 | } | ||
41 | } | ||
42 | |||
43 | impl ops::Index<Target> for CargoWorkspace { | ||
44 | type Output = TargetData; | ||
45 | fn index(&self, index: Target) -> &TargetData { | ||
46 | &self.targets[index] | ||
47 | } | ||
48 | } | ||
49 | |||
50 | #[derive(Default, Clone, Debug, PartialEq, Eq)] | ||
51 | pub struct CargoConfig { | ||
52 | /// Do not activate the `default` feature. | ||
53 | pub no_default_features: bool, | ||
54 | |||
55 | /// Activate all available features | ||
56 | pub all_features: bool, | ||
57 | |||
58 | /// List of features to activate. | ||
59 | /// This will be ignored if `cargo_all_features` is true. | ||
60 | pub features: Vec<String>, | ||
61 | |||
62 | /// Runs cargo check on launch to figure out the correct values of OUT_DIR | ||
63 | pub load_out_dirs_from_check: bool, | ||
64 | |||
65 | /// rustc target | ||
66 | pub target: Option<String>, | ||
67 | } | ||
68 | |||
69 | pub type Package = Idx<PackageData>; | ||
70 | |||
71 | pub type Target = Idx<TargetData>; | ||
72 | |||
73 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
74 | pub struct PackageData { | ||
75 | pub version: String, | ||
76 | pub name: String, | ||
77 | pub manifest: AbsPathBuf, | ||
78 | pub targets: Vec<Target>, | ||
79 | pub is_member: bool, | ||
80 | pub dependencies: Vec<PackageDependency>, | ||
81 | pub edition: Edition, | ||
82 | pub features: Vec<String>, | ||
83 | pub cfgs: Vec<CfgFlag>, | ||
84 | pub out_dir: Option<AbsPathBuf>, | ||
85 | pub proc_macro_dylib_path: Option<AbsPathBuf>, | ||
86 | } | ||
87 | |||
88 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
89 | pub struct PackageDependency { | ||
90 | pub pkg: Package, | ||
91 | pub name: String, | ||
92 | } | ||
93 | |||
94 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
95 | pub struct TargetData { | ||
96 | pub package: Package, | ||
97 | pub name: String, | ||
98 | pub root: AbsPathBuf, | ||
99 | pub kind: TargetKind, | ||
100 | pub is_proc_macro: bool, | ||
101 | } | ||
102 | |||
103 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
104 | pub enum TargetKind { | ||
105 | Bin, | ||
106 | /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...). | ||
107 | Lib, | ||
108 | Example, | ||
109 | Test, | ||
110 | Bench, | ||
111 | Other, | ||
112 | } | ||
113 | |||
114 | impl TargetKind { | ||
115 | fn new(kinds: &[String]) -> TargetKind { | ||
116 | for kind in kinds { | ||
117 | return match kind.as_str() { | ||
118 | "bin" => TargetKind::Bin, | ||
119 | "test" => TargetKind::Test, | ||
120 | "bench" => TargetKind::Bench, | ||
121 | "example" => TargetKind::Example, | ||
122 | "proc-macro" => TargetKind::Lib, | ||
123 | _ if kind.contains("lib") => TargetKind::Lib, | ||
124 | _ => continue, | ||
125 | }; | ||
126 | } | ||
127 | TargetKind::Other | ||
128 | } | ||
129 | } | ||
130 | |||
131 | impl PackageData { | ||
132 | pub fn root(&self) -> &AbsPath { | ||
133 | self.manifest.parent().unwrap() | ||
134 | } | ||
135 | } | ||
136 | |||
137 | impl CargoWorkspace { | ||
138 | pub fn from_cargo_metadata( | ||
139 | cargo_toml: &AbsPath, | ||
140 | cargo_features: &CargoConfig, | ||
141 | ) -> Result<CargoWorkspace> { | ||
142 | let mut meta = MetadataCommand::new(); | ||
143 | meta.cargo_path(toolchain::cargo()); | ||
144 | meta.manifest_path(cargo_toml.to_path_buf()); | ||
145 | if cargo_features.all_features { | ||
146 | meta.features(CargoOpt::AllFeatures); | ||
147 | } else { | ||
148 | if cargo_features.no_default_features { | ||
149 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` | ||
150 | // https://github.com/oli-obk/cargo_metadata/issues/79 | ||
151 | meta.features(CargoOpt::NoDefaultFeatures); | ||
152 | } | ||
153 | if !cargo_features.features.is_empty() { | ||
154 | meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); | ||
155 | } | ||
156 | } | ||
157 | if let Some(parent) = cargo_toml.parent() { | ||
158 | meta.current_dir(parent.to_path_buf()); | ||
159 | } | ||
160 | if let Some(target) = cargo_features.target.as_ref() { | ||
161 | meta.other_options(vec![String::from("--filter-platform"), target.clone()]); | ||
162 | } | ||
163 | let mut meta = meta.exec().with_context(|| { | ||
164 | format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display()) | ||
165 | })?; | ||
166 | |||
167 | let mut out_dir_by_id = FxHashMap::default(); | ||
168 | let mut cfgs = FxHashMap::default(); | ||
169 | let mut proc_macro_dylib_paths = FxHashMap::default(); | ||
170 | if cargo_features.load_out_dirs_from_check { | ||
171 | let resources = load_extern_resources(cargo_toml, cargo_features)?; | ||
172 | out_dir_by_id = resources.out_dirs; | ||
173 | cfgs = resources.cfgs; | ||
174 | proc_macro_dylib_paths = resources.proc_dylib_paths; | ||
175 | } | ||
176 | |||
177 | let mut pkg_by_id = FxHashMap::default(); | ||
178 | let mut packages = Arena::default(); | ||
179 | let mut targets = Arena::default(); | ||
180 | |||
181 | let ws_members = &meta.workspace_members; | ||
182 | |||
183 | meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); | ||
184 | for meta_pkg in meta.packages { | ||
185 | let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = | ||
186 | meta_pkg; | ||
187 | let is_member = ws_members.contains(&id); | ||
188 | let edition = edition | ||
189 | .parse::<Edition>() | ||
190 | .with_context(|| format!("Failed to parse edition {}", edition))?; | ||
191 | let pkg = packages.alloc(PackageData { | ||
192 | name, | ||
193 | version: version.to_string(), | ||
194 | manifest: AbsPathBuf::assert(manifest_path), | ||
195 | targets: Vec::new(), | ||
196 | is_member, | ||
197 | edition, | ||
198 | dependencies: Vec::new(), | ||
199 | features: Vec::new(), | ||
200 | cfgs: cfgs.get(&id).cloned().unwrap_or_default(), | ||
201 | out_dir: out_dir_by_id.get(&id).cloned(), | ||
202 | proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), | ||
203 | }); | ||
204 | let pkg_data = &mut packages[pkg]; | ||
205 | pkg_by_id.insert(id, pkg); | ||
206 | for meta_tgt in meta_pkg.targets { | ||
207 | let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"]; | ||
208 | let tgt = targets.alloc(TargetData { | ||
209 | package: pkg, | ||
210 | name: meta_tgt.name, | ||
211 | root: AbsPathBuf::assert(meta_tgt.src_path.clone()), | ||
212 | kind: TargetKind::new(meta_tgt.kind.as_slice()), | ||
213 | is_proc_macro, | ||
214 | }); | ||
215 | pkg_data.targets.push(tgt); | ||
216 | } | ||
217 | } | ||
218 | let resolve = meta.resolve.expect("metadata executed with deps"); | ||
219 | for mut node in resolve.nodes { | ||
220 | let source = match pkg_by_id.get(&node.id) { | ||
221 | Some(&src) => src, | ||
222 | // FIXME: replace this and a similar branch below with `.unwrap`, once | ||
223 | // https://github.com/rust-lang/cargo/issues/7841 | ||
224 | // is fixed and hits stable (around 1.43-is probably?). | ||
225 | None => { | ||
226 | log::error!("Node id do not match in cargo metadata, ignoring {}", node.id); | ||
227 | continue; | ||
228 | } | ||
229 | }; | ||
230 | node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg)); | ||
231 | for dep_node in node.deps { | ||
232 | let pkg = match pkg_by_id.get(&dep_node.pkg) { | ||
233 | Some(&pkg) => pkg, | ||
234 | None => { | ||
235 | log::error!( | ||
236 | "Dep node id do not match in cargo metadata, ignoring {}", | ||
237 | dep_node.pkg | ||
238 | ); | ||
239 | continue; | ||
240 | } | ||
241 | }; | ||
242 | let dep = PackageDependency { name: dep_node.name, pkg }; | ||
243 | packages[source].dependencies.push(dep); | ||
244 | } | ||
245 | packages[source].features.extend(node.features); | ||
246 | } | ||
247 | |||
248 | let workspace_root = AbsPathBuf::assert(meta.workspace_root); | ||
249 | Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root }) | ||
250 | } | ||
251 | |||
252 | pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a { | ||
253 | self.packages.iter().map(|(id, _pkg)| id) | ||
254 | } | ||
255 | |||
256 | pub fn target_by_root(&self, root: &AbsPath) -> Option<Target> { | ||
257 | self.packages() | ||
258 | .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root)) | ||
259 | .next() | ||
260 | .copied() | ||
261 | } | ||
262 | |||
263 | pub fn workspace_root(&self) -> &AbsPath { | ||
264 | &self.workspace_root | ||
265 | } | ||
266 | |||
267 | pub fn package_flag(&self, package: &PackageData) -> String { | ||
268 | if self.is_unique(&*package.name) { | ||
269 | package.name.clone() | ||
270 | } else { | ||
271 | format!("{}:{}", package.name, package.version) | ||
272 | } | ||
273 | } | ||
274 | |||
275 | fn is_unique(&self, name: &str) -> bool { | ||
276 | self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 | ||
277 | } | ||
278 | } | ||
279 | |||
280 | #[derive(Debug, Clone, Default)] | ||
281 | pub struct ExternResources { | ||
282 | out_dirs: FxHashMap<PackageId, AbsPathBuf>, | ||
283 | proc_dylib_paths: FxHashMap<PackageId, AbsPathBuf>, | ||
284 | cfgs: FxHashMap<PackageId, Vec<CfgFlag>>, | ||
285 | } | ||
286 | |||
287 | pub fn load_extern_resources( | ||
288 | cargo_toml: &Path, | ||
289 | cargo_features: &CargoConfig, | ||
290 | ) -> Result<ExternResources> { | ||
291 | let mut cmd = Command::new(toolchain::cargo()); | ||
292 | cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); | ||
293 | if cargo_features.all_features { | ||
294 | cmd.arg("--all-features"); | ||
295 | } else { | ||
296 | if cargo_features.no_default_features { | ||
297 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` | ||
298 | // https://github.com/oli-obk/cargo_metadata/issues/79 | ||
299 | cmd.arg("--no-default-features"); | ||
300 | } | ||
301 | if !cargo_features.features.is_empty() { | ||
302 | cmd.arg("--features"); | ||
303 | cmd.arg(cargo_features.features.join(" ")); | ||
304 | } | ||
305 | } | ||
306 | |||
307 | let output = cmd.output()?; | ||
308 | |||
309 | let mut res = ExternResources::default(); | ||
310 | |||
311 | for message in cargo_metadata::Message::parse_stream(output.stdout.as_slice()) { | ||
312 | if let Ok(message) = message { | ||
313 | match message { | ||
314 | Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => { | ||
315 | let cfgs = { | ||
316 | let mut acc = Vec::new(); | ||
317 | for cfg in cfgs { | ||
318 | match cfg.parse::<CfgFlag>() { | ||
319 | Ok(it) => acc.push(it), | ||
320 | Err(err) => { | ||
321 | anyhow::bail!("invalid cfg from cargo-metadata: {}", err) | ||
322 | } | ||
323 | }; | ||
324 | } | ||
325 | acc | ||
326 | }; | ||
327 | // cargo_metadata crate returns default (empty) path for | ||
328 | // older cargos, which is not absolute, so work around that. | ||
329 | if out_dir != PathBuf::default() { | ||
330 | let out_dir = AbsPathBuf::assert(out_dir); | ||
331 | res.out_dirs.insert(package_id.clone(), out_dir); | ||
332 | res.cfgs.insert(package_id, cfgs); | ||
333 | } | ||
334 | } | ||
335 | Message::CompilerArtifact(message) => { | ||
336 | if message.target.kind.contains(&"proc-macro".to_string()) { | ||
337 | let package_id = message.package_id; | ||
338 | // Skip rmeta file | ||
339 | if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) | ||
340 | { | ||
341 | let filename = AbsPathBuf::assert(filename.clone()); | ||
342 | res.proc_dylib_paths.insert(package_id, filename); | ||
343 | } | ||
344 | } | ||
345 | } | ||
346 | Message::CompilerMessage(_) => (), | ||
347 | Message::Unknown => (), | ||
348 | Message::BuildFinished(_) => {} | ||
349 | Message::TextLine(_) => {} | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | Ok(res) | ||
354 | } | ||
355 | |||
356 | // FIXME: File a better way to know if it is a dylib | ||
357 | fn is_dylib(path: &Path) -> bool { | ||
358 | match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) { | ||
359 | None => false, | ||
360 | Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), | ||
361 | } | ||
362 | } | ||