diff options
author | Aleksey Kladov <[email protected]> | 2020-11-13 15:44:48 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-11-13 15:44:48 +0000 |
commit | aeda30e301d74e40fc1eb992fad581afb627126f (patch) | |
tree | 0b3a116ab450e5a1eae24bde65ed683e75f9555a /crates/project_model | |
parent | f0f13d8cfa3a9d6d3968be1cc852eb3120c94653 (diff) |
Move tricky workspace logic to a separate module
Diffstat (limited to 'crates/project_model')
-rw-r--r-- | crates/project_model/src/lib.rs | 606 | ||||
-rw-r--r-- | crates/project_model/src/workspace.rs | 607 |
2 files changed, 611 insertions, 602 deletions
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 4531b1928..24aa9b8fa 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs | |||
@@ -4,74 +4,27 @@ mod cargo_workspace; | |||
4 | mod project_json; | 4 | mod project_json; |
5 | mod sysroot; | 5 | mod sysroot; |
6 | mod cfg_flag; | 6 | mod cfg_flag; |
7 | mod workspace; | ||
7 | 8 | ||
8 | use std::{ | 9 | use std::{ |
9 | fmt, | 10 | fs::{read_dir, ReadDir}, |
10 | fs::{self, read_dir, ReadDir}, | ||
11 | io, | 11 | io, |
12 | path::Component, | ||
13 | process::Command, | 12 | process::Command, |
14 | }; | 13 | }; |
15 | 14 | ||
16 | use anyhow::{bail, Context, Result}; | 15 | use anyhow::{bail, Context, Result}; |
17 | use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId}; | ||
18 | use cfg::CfgOptions; | ||
19 | use paths::{AbsPath, AbsPathBuf}; | 16 | use paths::{AbsPath, AbsPathBuf}; |
20 | use rustc_hash::{FxHashMap, FxHashSet}; | 17 | use rustc_hash::FxHashSet; |
21 | |||
22 | use crate::cfg_flag::CfgFlag; | ||
23 | 18 | ||
24 | pub use crate::{ | 19 | pub use crate::{ |
25 | cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, | 20 | cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, |
26 | project_json::{ProjectJson, ProjectJsonData}, | 21 | project_json::{ProjectJson, ProjectJsonData}, |
27 | sysroot::Sysroot, | 22 | sysroot::Sysroot, |
23 | workspace::{PackageRoot, ProjectWorkspace}, | ||
28 | }; | 24 | }; |
29 | 25 | ||
30 | pub use proc_macro_api::ProcMacroClient; | 26 | pub use proc_macro_api::ProcMacroClient; |
31 | 27 | ||
32 | #[derive(Clone, Eq, PartialEq)] | ||
33 | pub enum ProjectWorkspace { | ||
34 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. | ||
35 | Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> }, | ||
36 | /// Project workspace was manually specified using a `rust-project.json` file. | ||
37 | Json { project: ProjectJson, sysroot: Option<Sysroot> }, | ||
38 | } | ||
39 | |||
40 | impl fmt::Debug for ProjectWorkspace { | ||
41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
42 | match self { | ||
43 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f | ||
44 | .debug_struct("Cargo") | ||
45 | .field("n_packages", &cargo.packages().len()) | ||
46 | .field("n_sysroot_crates", &sysroot.crates().len()) | ||
47 | .field( | ||
48 | "n_rustc_compiler_crates", | ||
49 | &rustc.as_ref().map_or(0, |rc| rc.packages().len()), | ||
50 | ) | ||
51 | .finish(), | ||
52 | ProjectWorkspace::Json { project, sysroot } => { | ||
53 | let mut debug_struct = f.debug_struct("Json"); | ||
54 | debug_struct.field("n_crates", &project.n_crates()); | ||
55 | if let Some(sysroot) = sysroot { | ||
56 | debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); | ||
57 | } | ||
58 | debug_struct.finish() | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// `PackageRoot` describes a package root folder. | ||
65 | /// Which may be an external dependency, or a member of | ||
66 | /// the current workspace. | ||
67 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] | ||
68 | pub struct PackageRoot { | ||
69 | /// Is a member of the current workspace | ||
70 | pub is_member: bool, | ||
71 | pub include: Vec<AbsPathBuf>, | ||
72 | pub exclude: Vec<AbsPathBuf>, | ||
73 | } | ||
74 | |||
75 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] | 28 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] |
76 | pub enum ProjectManifest { | 29 | pub enum ProjectManifest { |
77 | ProjectJson(AbsPathBuf), | 30 | ProjectJson(AbsPathBuf), |
@@ -158,462 +111,6 @@ impl ProjectManifest { | |||
158 | } | 111 | } |
159 | } | 112 | } |
160 | 113 | ||
161 | impl ProjectWorkspace { | ||
162 | pub fn load( | ||
163 | manifest: ProjectManifest, | ||
164 | cargo_config: &CargoConfig, | ||
165 | with_sysroot: bool, | ||
166 | ) -> Result<ProjectWorkspace> { | ||
167 | let res = match manifest { | ||
168 | ProjectManifest::ProjectJson(project_json) => { | ||
169 | let file = fs::read_to_string(&project_json).with_context(|| { | ||
170 | format!("Failed to read json file {}", project_json.display()) | ||
171 | })?; | ||
172 | let data = serde_json::from_str(&file).with_context(|| { | ||
173 | format!("Failed to deserialize json file {}", project_json.display()) | ||
174 | })?; | ||
175 | let project_location = project_json.parent().unwrap().to_path_buf(); | ||
176 | let project = ProjectJson::new(&project_location, data); | ||
177 | let sysroot = match &project.sysroot_src { | ||
178 | Some(path) => Some(Sysroot::load(path)?), | ||
179 | None => None, | ||
180 | }; | ||
181 | ProjectWorkspace::Json { project, sysroot } | ||
182 | } | ||
183 | ProjectManifest::CargoToml(cargo_toml) => { | ||
184 | let cargo_version = utf8_stdout({ | ||
185 | let mut cmd = Command::new(toolchain::cargo()); | ||
186 | cmd.arg("--version"); | ||
187 | cmd | ||
188 | })?; | ||
189 | |||
190 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config) | ||
191 | .with_context(|| { | ||
192 | format!( | ||
193 | "Failed to read Cargo metadata from Cargo.toml file {}, {}", | ||
194 | cargo_toml.display(), | ||
195 | cargo_version | ||
196 | ) | ||
197 | })?; | ||
198 | let sysroot = if with_sysroot { | ||
199 | Sysroot::discover(&cargo_toml).with_context(|| { | ||
200 | format!( | ||
201 | "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", | ||
202 | cargo_toml.display() | ||
203 | ) | ||
204 | })? | ||
205 | } else { | ||
206 | Sysroot::default() | ||
207 | }; | ||
208 | |||
209 | let rustc = if let Some(rustc_dir) = &cargo_config.rustc_source { | ||
210 | Some( | ||
211 | CargoWorkspace::from_cargo_metadata(&rustc_dir, cargo_config) | ||
212 | .with_context(|| { | ||
213 | format!("Failed to read Cargo metadata for Rust sources") | ||
214 | })?, | ||
215 | ) | ||
216 | } else { | ||
217 | None | ||
218 | }; | ||
219 | |||
220 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } | ||
221 | } | ||
222 | }; | ||
223 | |||
224 | Ok(res) | ||
225 | } | ||
226 | |||
227 | pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> { | ||
228 | let sysroot = match &project_json.sysroot_src { | ||
229 | Some(path) => Some(Sysroot::load(path)?), | ||
230 | None => None, | ||
231 | }; | ||
232 | |||
233 | Ok(ProjectWorkspace::Json { project: project_json, sysroot }) | ||
234 | } | ||
235 | |||
236 | /// Returns the roots for the current `ProjectWorkspace` | ||
237 | /// The return type contains the path and whether or not | ||
238 | /// the root is a member of the current workspace | ||
239 | pub fn to_roots(&self) -> Vec<PackageRoot> { | ||
240 | match self { | ||
241 | ProjectWorkspace::Json { project, sysroot } => project | ||
242 | .crates() | ||
243 | .map(|(_, krate)| PackageRoot { | ||
244 | is_member: krate.is_workspace_member, | ||
245 | include: krate.include.clone(), | ||
246 | exclude: krate.exclude.clone(), | ||
247 | }) | ||
248 | .collect::<FxHashSet<_>>() | ||
249 | .into_iter() | ||
250 | .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| { | ||
251 | sysroot.crates().map(move |krate| PackageRoot { | ||
252 | is_member: false, | ||
253 | include: vec![sysroot[krate].root_dir().to_path_buf()], | ||
254 | exclude: Vec::new(), | ||
255 | }) | ||
256 | })) | ||
257 | .collect::<Vec<_>>(), | ||
258 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { | ||
259 | let roots = cargo | ||
260 | .packages() | ||
261 | .map(|pkg| { | ||
262 | let is_member = cargo[pkg].is_member; | ||
263 | let pkg_root = cargo[pkg].root().to_path_buf(); | ||
264 | |||
265 | let mut include = vec![pkg_root.clone()]; | ||
266 | include.extend(cargo[pkg].out_dir.clone()); | ||
267 | |||
268 | let mut exclude = vec![pkg_root.join(".git")]; | ||
269 | if is_member { | ||
270 | exclude.push(pkg_root.join("target")); | ||
271 | } else { | ||
272 | exclude.push(pkg_root.join("tests")); | ||
273 | exclude.push(pkg_root.join("examples")); | ||
274 | exclude.push(pkg_root.join("benches")); | ||
275 | } | ||
276 | PackageRoot { is_member, include, exclude } | ||
277 | }) | ||
278 | .chain(sysroot.crates().map(|krate| PackageRoot { | ||
279 | is_member: false, | ||
280 | include: vec![sysroot[krate].root_dir().to_path_buf()], | ||
281 | exclude: Vec::new(), | ||
282 | })); | ||
283 | if let Some(rustc_packages) = rustc { | ||
284 | roots | ||
285 | .chain(rustc_packages.packages().map(|krate| PackageRoot { | ||
286 | is_member: false, | ||
287 | include: vec![rustc_packages[krate].root().to_path_buf()], | ||
288 | exclude: Vec::new(), | ||
289 | })) | ||
290 | .collect() | ||
291 | } else { | ||
292 | roots.collect() | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | } | ||
297 | |||
298 | pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> { | ||
299 | match self { | ||
300 | ProjectWorkspace::Json { project, sysroot: _ } => project | ||
301 | .crates() | ||
302 | .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) | ||
303 | .cloned() | ||
304 | .collect(), | ||
305 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo | ||
306 | .packages() | ||
307 | .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) | ||
308 | .cloned() | ||
309 | .collect(), | ||
310 | } | ||
311 | } | ||
312 | |||
313 | pub fn n_packages(&self) -> usize { | ||
314 | match self { | ||
315 | ProjectWorkspace::Json { project, .. } => project.n_crates(), | ||
316 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { | ||
317 | let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len()); | ||
318 | cargo.packages().len() + sysroot.crates().len() + rustc_package_len | ||
319 | } | ||
320 | } | ||
321 | } | ||
322 | |||
323 | pub fn to_crate_graph( | ||
324 | &self, | ||
325 | target: Option<&str>, | ||
326 | proc_macro_client: &ProcMacroClient, | ||
327 | load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, | ||
328 | ) -> CrateGraph { | ||
329 | let mut crate_graph = CrateGraph::default(); | ||
330 | match self { | ||
331 | ProjectWorkspace::Json { project, sysroot } => { | ||
332 | let sysroot_dps = sysroot | ||
333 | .as_ref() | ||
334 | .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); | ||
335 | |||
336 | let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default(); | ||
337 | let crates: FxHashMap<_, _> = project | ||
338 | .crates() | ||
339 | .filter_map(|(crate_id, krate)| { | ||
340 | let file_path = &krate.root_module; | ||
341 | let file_id = match load(&file_path) { | ||
342 | Some(id) => id, | ||
343 | None => { | ||
344 | log::error!("failed to load crate root {}", file_path.display()); | ||
345 | return None; | ||
346 | } | ||
347 | }; | ||
348 | |||
349 | let env = krate.env.clone().into_iter().collect(); | ||
350 | let proc_macro = krate | ||
351 | .proc_macro_dylib_path | ||
352 | .clone() | ||
353 | .map(|it| proc_macro_client.by_dylib_path(&it)); | ||
354 | |||
355 | let target = krate.target.as_deref().or(target); | ||
356 | let target_cfgs = cfg_cache | ||
357 | .entry(target) | ||
358 | .or_insert_with(|| get_rustc_cfg_options(target)); | ||
359 | |||
360 | let mut cfg_options = CfgOptions::default(); | ||
361 | cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned()); | ||
362 | |||
363 | Some(( | ||
364 | crate_id, | ||
365 | crate_graph.add_crate_root( | ||
366 | file_id, | ||
367 | krate.edition, | ||
368 | krate.display_name.clone(), | ||
369 | cfg_options, | ||
370 | env, | ||
371 | proc_macro.unwrap_or_default(), | ||
372 | ), | ||
373 | )) | ||
374 | }) | ||
375 | .collect(); | ||
376 | |||
377 | for (from, krate) in project.crates() { | ||
378 | if let Some(&from) = crates.get(&from) { | ||
379 | if let Some((public_deps, _proc_macro)) = &sysroot_dps { | ||
380 | for (name, to) in public_deps.iter() { | ||
381 | if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) { | ||
382 | log::error!("cyclic dependency on {} for {:?}", name, from) | ||
383 | } | ||
384 | } | ||
385 | } | ||
386 | |||
387 | for dep in &krate.deps { | ||
388 | let to_crate_id = dep.crate_id; | ||
389 | if let Some(&to) = crates.get(&to_crate_id) { | ||
390 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) { | ||
391 | log::error!("cyclic dependency {:?} -> {:?}", from, to); | ||
392 | } | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | } | ||
398 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { | ||
399 | let (public_deps, libproc_macro) = | ||
400 | sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load); | ||
401 | |||
402 | let mut cfg_options = CfgOptions::default(); | ||
403 | cfg_options.extend(get_rustc_cfg_options(target)); | ||
404 | |||
405 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
406 | |||
407 | // Add test cfg for non-sysroot crates | ||
408 | cfg_options.insert_atom("test".into()); | ||
409 | cfg_options.insert_atom("debug_assertions".into()); | ||
410 | |||
411 | let mut pkg_crates = FxHashMap::default(); | ||
412 | |||
413 | // Next, create crates for each package, target pair | ||
414 | for pkg in cargo.packages() { | ||
415 | let mut lib_tgt = None; | ||
416 | for &tgt in cargo[pkg].targets.iter() { | ||
417 | if let Some(crate_id) = add_target_crate_root( | ||
418 | &mut crate_graph, | ||
419 | &cargo[pkg], | ||
420 | &cargo[tgt], | ||
421 | &cfg_options, | ||
422 | proc_macro_client, | ||
423 | load, | ||
424 | ) { | ||
425 | if cargo[tgt].kind == TargetKind::Lib { | ||
426 | lib_tgt = Some((crate_id, cargo[tgt].name.clone())); | ||
427 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
428 | } | ||
429 | if cargo[tgt].is_proc_macro { | ||
430 | if let Some(proc_macro) = libproc_macro { | ||
431 | if let Err(_) = crate_graph.add_dep( | ||
432 | crate_id, | ||
433 | CrateName::new("proc_macro").unwrap(), | ||
434 | proc_macro, | ||
435 | ) { | ||
436 | log::error!( | ||
437 | "cyclic dependency on proc_macro for {}", | ||
438 | &cargo[pkg].name | ||
439 | ) | ||
440 | } | ||
441 | } | ||
442 | } | ||
443 | |||
444 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
445 | } | ||
446 | } | ||
447 | |||
448 | // Set deps to the core, std and to the lib target of the current package | ||
449 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
450 | if let Some((to, name)) = lib_tgt.clone() { | ||
451 | // For root projects with dashes in their name, | ||
452 | // cargo metadata does not do any normalization, | ||
453 | // so we do it ourselves currently | ||
454 | let name = CrateName::normalize_dashes(&name); | ||
455 | if to != from && crate_graph.add_dep(from, name, to).is_err() { | ||
456 | log::error!( | ||
457 | "cyclic dependency between targets of {}", | ||
458 | &cargo[pkg].name | ||
459 | ) | ||
460 | } | ||
461 | } | ||
462 | for (name, krate) in public_deps.iter() { | ||
463 | if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) { | ||
464 | log::error!( | ||
465 | "cyclic dependency on {} for {}", | ||
466 | name, | ||
467 | &cargo[pkg].name | ||
468 | ) | ||
469 | } | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | |||
474 | // Now add a dep edge from all targets of upstream to the lib | ||
475 | // target of downstream. | ||
476 | for pkg in cargo.packages() { | ||
477 | for dep in cargo[pkg].dependencies.iter() { | ||
478 | let name = CrateName::new(&dep.name).unwrap(); | ||
479 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
480 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
481 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
482 | log::error!( | ||
483 | "cyclic dependency {} -> {}", | ||
484 | &cargo[pkg].name, | ||
485 | &cargo[dep.pkg].name | ||
486 | ) | ||
487 | } | ||
488 | } | ||
489 | } | ||
490 | } | ||
491 | } | ||
492 | |||
493 | let mut rustc_pkg_crates = FxHashMap::default(); | ||
494 | |||
495 | // If the user provided a path to rustc sources, we add all the rustc_private crates | ||
496 | // and create dependencies on them for the crates in the current workspace | ||
497 | if let Some(rustc_workspace) = rustc { | ||
498 | for pkg in rustc_workspace.packages() { | ||
499 | for &tgt in rustc_workspace[pkg].targets.iter() { | ||
500 | if rustc_workspace[tgt].kind != TargetKind::Lib { | ||
501 | continue; | ||
502 | } | ||
503 | // Exclude alloc / core / std | ||
504 | if rustc_workspace[tgt] | ||
505 | .root | ||
506 | .components() | ||
507 | .any(|c| c == Component::Normal("library".as_ref())) | ||
508 | { | ||
509 | continue; | ||
510 | } | ||
511 | |||
512 | if let Some(crate_id) = add_target_crate_root( | ||
513 | &mut crate_graph, | ||
514 | &rustc_workspace[pkg], | ||
515 | &rustc_workspace[tgt], | ||
516 | &cfg_options, | ||
517 | proc_macro_client, | ||
518 | load, | ||
519 | ) { | ||
520 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
521 | // Add dependencies on the core / std / alloc for rustc | ||
522 | for (name, krate) in public_deps.iter() { | ||
523 | if let Err(_) = | ||
524 | crate_graph.add_dep(crate_id, name.clone(), *krate) | ||
525 | { | ||
526 | log::error!( | ||
527 | "cyclic dependency on {} for {}", | ||
528 | name, | ||
529 | &cargo[pkg].name | ||
530 | ) | ||
531 | } | ||
532 | } | ||
533 | rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
534 | } | ||
535 | } | ||
536 | } | ||
537 | // Now add a dep edge from all targets of upstream to the lib | ||
538 | // target of downstream. | ||
539 | for pkg in rustc_workspace.packages() { | ||
540 | for dep in rustc_workspace[pkg].dependencies.iter() { | ||
541 | let name = CrateName::new(&dep.name).unwrap(); | ||
542 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
543 | for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() { | ||
544 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
545 | log::error!( | ||
546 | "cyclic dependency {} -> {}", | ||
547 | &rustc_workspace[pkg].name, | ||
548 | &rustc_workspace[dep.pkg].name | ||
549 | ) | ||
550 | } | ||
551 | } | ||
552 | } | ||
553 | } | ||
554 | } | ||
555 | |||
556 | // Add dependencies for all the crates of the current workspace to rustc_private libraries | ||
557 | for dep in rustc_workspace.packages() { | ||
558 | let name = CrateName::normalize_dashes(&rustc_workspace[dep].name); | ||
559 | |||
560 | if let Some(&to) = pkg_to_lib_crate.get(&dep) { | ||
561 | for pkg in cargo.packages() { | ||
562 | if !cargo[pkg].is_member { | ||
563 | continue; | ||
564 | } | ||
565 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
566 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
567 | log::error!( | ||
568 | "cyclic dependency {} -> {}", | ||
569 | &cargo[pkg].name, | ||
570 | &rustc_workspace[dep].name | ||
571 | ) | ||
572 | } | ||
573 | } | ||
574 | } | ||
575 | } | ||
576 | } | ||
577 | } | ||
578 | } | ||
579 | } | ||
580 | if crate_graph.patch_cfg_if() { | ||
581 | log::debug!("Patched std to depend on cfg-if") | ||
582 | } else { | ||
583 | log::debug!("Did not patch std to depend on cfg-if") | ||
584 | } | ||
585 | crate_graph | ||
586 | } | ||
587 | } | ||
588 | |||
589 | fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> { | ||
590 | let mut res = Vec::new(); | ||
591 | |||
592 | // Some nightly-only cfgs, which are required for stdlib | ||
593 | res.push(CfgFlag::Atom("target_thread_local".into())); | ||
594 | for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() { | ||
595 | for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() { | ||
596 | res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() }); | ||
597 | } | ||
598 | } | ||
599 | |||
600 | let rustc_cfgs = { | ||
601 | let mut cmd = Command::new(toolchain::rustc()); | ||
602 | cmd.args(&["--print", "cfg", "-O"]); | ||
603 | if let Some(target) = target { | ||
604 | cmd.args(&["--target", target]); | ||
605 | } | ||
606 | utf8_stdout(cmd) | ||
607 | }; | ||
608 | |||
609 | match rustc_cfgs { | ||
610 | Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())), | ||
611 | Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), | ||
612 | } | ||
613 | |||
614 | res | ||
615 | } | ||
616 | |||
617 | fn utf8_stdout(mut cmd: Command) -> Result<String> { | 114 | fn utf8_stdout(mut cmd: Command) -> Result<String> { |
618 | let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; | 115 | let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; |
619 | if !output.status.success() { | 116 | if !output.status.success() { |
@@ -627,98 +124,3 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> { | |||
627 | let stdout = String::from_utf8(output.stdout)?; | 124 | let stdout = String::from_utf8(output.stdout)?; |
628 | Ok(stdout.trim().to_string()) | 125 | Ok(stdout.trim().to_string()) |
629 | } | 126 | } |
630 | |||
631 | fn add_target_crate_root( | ||
632 | crate_graph: &mut CrateGraph, | ||
633 | pkg: &cargo_workspace::PackageData, | ||
634 | tgt: &cargo_workspace::TargetData, | ||
635 | cfg_options: &CfgOptions, | ||
636 | proc_macro_client: &ProcMacroClient, | ||
637 | load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, | ||
638 | ) -> Option<CrateId> { | ||
639 | let root = tgt.root.as_path(); | ||
640 | if let Some(file_id) = load(root) { | ||
641 | let edition = pkg.edition; | ||
642 | let cfg_options = { | ||
643 | let mut opts = cfg_options.clone(); | ||
644 | for feature in pkg.features.iter() { | ||
645 | opts.insert_key_value("feature".into(), feature.into()); | ||
646 | } | ||
647 | opts.extend(pkg.cfgs.iter().cloned()); | ||
648 | opts | ||
649 | }; | ||
650 | let mut env = Env::default(); | ||
651 | if let Some(out_dir) = &pkg.out_dir { | ||
652 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() | ||
653 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { | ||
654 | env.set("OUT_DIR", out_dir); | ||
655 | } | ||
656 | } | ||
657 | let proc_macro = pkg | ||
658 | .proc_macro_dylib_path | ||
659 | .as_ref() | ||
660 | .map(|it| proc_macro_client.by_dylib_path(&it)) | ||
661 | .unwrap_or_default(); | ||
662 | |||
663 | let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone()); | ||
664 | let crate_id = crate_graph.add_crate_root( | ||
665 | file_id, | ||
666 | edition, | ||
667 | Some(display_name), | ||
668 | cfg_options, | ||
669 | env, | ||
670 | proc_macro.clone(), | ||
671 | ); | ||
672 | |||
673 | return Some(crate_id); | ||
674 | } | ||
675 | None | ||
676 | } | ||
677 | fn sysroot_to_crate_graph( | ||
678 | crate_graph: &mut CrateGraph, | ||
679 | sysroot: &Sysroot, | ||
680 | target: Option<&str>, | ||
681 | load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, | ||
682 | ) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) { | ||
683 | let mut cfg_options = CfgOptions::default(); | ||
684 | cfg_options.extend(get_rustc_cfg_options(target)); | ||
685 | let sysroot_crates: FxHashMap<_, _> = sysroot | ||
686 | .crates() | ||
687 | .filter_map(|krate| { | ||
688 | let file_id = load(&sysroot[krate].root)?; | ||
689 | |||
690 | let env = Env::default(); | ||
691 | let proc_macro = vec![]; | ||
692 | let name = CrateName::new(&sysroot[krate].name) | ||
693 | .expect("Sysroot crates' names do not contain dashes"); | ||
694 | let crate_id = crate_graph.add_crate_root( | ||
695 | file_id, | ||
696 | Edition::Edition2018, | ||
697 | Some(name.into()), | ||
698 | cfg_options.clone(), | ||
699 | env, | ||
700 | proc_macro, | ||
701 | ); | ||
702 | Some((krate, crate_id)) | ||
703 | }) | ||
704 | .collect(); | ||
705 | |||
706 | for from in sysroot.crates() { | ||
707 | for &to in sysroot[from].deps.iter() { | ||
708 | let name = CrateName::new(&sysroot[to].name).unwrap(); | ||
709 | if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) { | ||
710 | if let Err(_) = crate_graph.add_dep(from, name, to) { | ||
711 | log::error!("cyclic dependency between sysroot crates") | ||
712 | } | ||
713 | } | ||
714 | } | ||
715 | } | ||
716 | |||
717 | let public_deps = sysroot | ||
718 | .public_deps() | ||
719 | .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx])) | ||
720 | .collect::<Vec<_>>(); | ||
721 | |||
722 | let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied()); | ||
723 | (public_deps, libproc_macro) | ||
724 | } | ||
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs new file mode 100644 index 000000000..43ea351d1 --- /dev/null +++ b/crates/project_model/src/workspace.rs | |||
@@ -0,0 +1,607 @@ | |||
1 | use std::{fmt, fs, path::Component, process::Command}; | ||
2 | |||
3 | use anyhow::{Context, Result}; | ||
4 | use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId}; | ||
5 | use cfg::CfgOptions; | ||
6 | use paths::{AbsPath, AbsPathBuf}; | ||
7 | use proc_macro_api::ProcMacroClient; | ||
8 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
9 | |||
10 | use crate::{ | ||
11 | cargo_workspace, cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoWorkspace, ProjectJson, | ||
12 | ProjectManifest, Sysroot, TargetKind, | ||
13 | }; | ||
14 | |||
15 | /// `PackageRoot` describes a package root folder. | ||
16 | /// Which may be an external dependency, or a member of | ||
17 | /// the current workspace. | ||
18 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] | ||
19 | pub struct PackageRoot { | ||
20 | /// Is a member of the current workspace | ||
21 | pub is_member: bool, | ||
22 | pub include: Vec<AbsPathBuf>, | ||
23 | pub exclude: Vec<AbsPathBuf>, | ||
24 | } | ||
25 | |||
26 | #[derive(Clone, Eq, PartialEq)] | ||
27 | pub enum ProjectWorkspace { | ||
28 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. | ||
29 | Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> }, | ||
30 | /// Project workspace was manually specified using a `rust-project.json` file. | ||
31 | Json { project: ProjectJson, sysroot: Option<Sysroot> }, | ||
32 | } | ||
33 | |||
34 | impl fmt::Debug for ProjectWorkspace { | ||
35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
36 | match self { | ||
37 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f | ||
38 | .debug_struct("Cargo") | ||
39 | .field("n_packages", &cargo.packages().len()) | ||
40 | .field("n_sysroot_crates", &sysroot.crates().len()) | ||
41 | .field( | ||
42 | "n_rustc_compiler_crates", | ||
43 | &rustc.as_ref().map_or(0, |rc| rc.packages().len()), | ||
44 | ) | ||
45 | .finish(), | ||
46 | ProjectWorkspace::Json { project, sysroot } => { | ||
47 | let mut debug_struct = f.debug_struct("Json"); | ||
48 | debug_struct.field("n_crates", &project.n_crates()); | ||
49 | if let Some(sysroot) = sysroot { | ||
50 | debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); | ||
51 | } | ||
52 | debug_struct.finish() | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | |||
58 | impl ProjectWorkspace { | ||
59 | pub fn load( | ||
60 | manifest: ProjectManifest, | ||
61 | cargo_config: &CargoConfig, | ||
62 | with_sysroot: bool, | ||
63 | ) -> Result<ProjectWorkspace> { | ||
64 | let res = match manifest { | ||
65 | ProjectManifest::ProjectJson(project_json) => { | ||
66 | let file = fs::read_to_string(&project_json).with_context(|| { | ||
67 | format!("Failed to read json file {}", project_json.display()) | ||
68 | })?; | ||
69 | let data = serde_json::from_str(&file).with_context(|| { | ||
70 | format!("Failed to deserialize json file {}", project_json.display()) | ||
71 | })?; | ||
72 | let project_location = project_json.parent().unwrap().to_path_buf(); | ||
73 | let project = ProjectJson::new(&project_location, data); | ||
74 | let sysroot = match &project.sysroot_src { | ||
75 | Some(path) => Some(Sysroot::load(path)?), | ||
76 | None => None, | ||
77 | }; | ||
78 | ProjectWorkspace::Json { project, sysroot } | ||
79 | } | ||
80 | ProjectManifest::CargoToml(cargo_toml) => { | ||
81 | let cargo_version = utf8_stdout({ | ||
82 | let mut cmd = Command::new(toolchain::cargo()); | ||
83 | cmd.arg("--version"); | ||
84 | cmd | ||
85 | })?; | ||
86 | |||
87 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config) | ||
88 | .with_context(|| { | ||
89 | format!( | ||
90 | "Failed to read Cargo metadata from Cargo.toml file {}, {}", | ||
91 | cargo_toml.display(), | ||
92 | cargo_version | ||
93 | ) | ||
94 | })?; | ||
95 | let sysroot = if with_sysroot { | ||
96 | Sysroot::discover(&cargo_toml).with_context(|| { | ||
97 | format!( | ||
98 | "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", | ||
99 | cargo_toml.display() | ||
100 | ) | ||
101 | })? | ||
102 | } else { | ||
103 | Sysroot::default() | ||
104 | }; | ||
105 | |||
106 | let rustc = if let Some(rustc_dir) = &cargo_config.rustc_source { | ||
107 | Some( | ||
108 | CargoWorkspace::from_cargo_metadata(&rustc_dir, cargo_config) | ||
109 | .with_context(|| { | ||
110 | format!("Failed to read Cargo metadata for Rust sources") | ||
111 | })?, | ||
112 | ) | ||
113 | } else { | ||
114 | None | ||
115 | }; | ||
116 | |||
117 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } | ||
118 | } | ||
119 | }; | ||
120 | |||
121 | Ok(res) | ||
122 | } | ||
123 | |||
124 | pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> { | ||
125 | let sysroot = match &project_json.sysroot_src { | ||
126 | Some(path) => Some(Sysroot::load(path)?), | ||
127 | None => None, | ||
128 | }; | ||
129 | |||
130 | Ok(ProjectWorkspace::Json { project: project_json, sysroot }) | ||
131 | } | ||
132 | |||
133 | /// Returns the roots for the current `ProjectWorkspace` | ||
134 | /// The return type contains the path and whether or not | ||
135 | /// the root is a member of the current workspace | ||
136 | pub fn to_roots(&self) -> Vec<PackageRoot> { | ||
137 | match self { | ||
138 | ProjectWorkspace::Json { project, sysroot } => project | ||
139 | .crates() | ||
140 | .map(|(_, krate)| PackageRoot { | ||
141 | is_member: krate.is_workspace_member, | ||
142 | include: krate.include.clone(), | ||
143 | exclude: krate.exclude.clone(), | ||
144 | }) | ||
145 | .collect::<FxHashSet<_>>() | ||
146 | .into_iter() | ||
147 | .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| { | ||
148 | sysroot.crates().map(move |krate| PackageRoot { | ||
149 | is_member: false, | ||
150 | include: vec![sysroot[krate].root_dir().to_path_buf()], | ||
151 | exclude: Vec::new(), | ||
152 | }) | ||
153 | })) | ||
154 | .collect::<Vec<_>>(), | ||
155 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { | ||
156 | let roots = cargo | ||
157 | .packages() | ||
158 | .map(|pkg| { | ||
159 | let is_member = cargo[pkg].is_member; | ||
160 | let pkg_root = cargo[pkg].root().to_path_buf(); | ||
161 | |||
162 | let mut include = vec![pkg_root.clone()]; | ||
163 | include.extend(cargo[pkg].out_dir.clone()); | ||
164 | |||
165 | let mut exclude = vec![pkg_root.join(".git")]; | ||
166 | if is_member { | ||
167 | exclude.push(pkg_root.join("target")); | ||
168 | } else { | ||
169 | exclude.push(pkg_root.join("tests")); | ||
170 | exclude.push(pkg_root.join("examples")); | ||
171 | exclude.push(pkg_root.join("benches")); | ||
172 | } | ||
173 | PackageRoot { is_member, include, exclude } | ||
174 | }) | ||
175 | .chain(sysroot.crates().map(|krate| PackageRoot { | ||
176 | is_member: false, | ||
177 | include: vec![sysroot[krate].root_dir().to_path_buf()], | ||
178 | exclude: Vec::new(), | ||
179 | })); | ||
180 | if let Some(rustc_packages) = rustc { | ||
181 | roots | ||
182 | .chain(rustc_packages.packages().map(|krate| PackageRoot { | ||
183 | is_member: false, | ||
184 | include: vec![rustc_packages[krate].root().to_path_buf()], | ||
185 | exclude: Vec::new(), | ||
186 | })) | ||
187 | .collect() | ||
188 | } else { | ||
189 | roots.collect() | ||
190 | } | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> { | ||
196 | match self { | ||
197 | ProjectWorkspace::Json { project, sysroot: _ } => project | ||
198 | .crates() | ||
199 | .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) | ||
200 | .cloned() | ||
201 | .collect(), | ||
202 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo | ||
203 | .packages() | ||
204 | .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) | ||
205 | .cloned() | ||
206 | .collect(), | ||
207 | } | ||
208 | } | ||
209 | |||
210 | pub fn n_packages(&self) -> usize { | ||
211 | match self { | ||
212 | ProjectWorkspace::Json { project, .. } => project.n_crates(), | ||
213 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { | ||
214 | let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len()); | ||
215 | cargo.packages().len() + sysroot.crates().len() + rustc_package_len | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
220 | pub fn to_crate_graph( | ||
221 | &self, | ||
222 | target: Option<&str>, | ||
223 | proc_macro_client: &ProcMacroClient, | ||
224 | load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, | ||
225 | ) -> CrateGraph { | ||
226 | let mut crate_graph = CrateGraph::default(); | ||
227 | match self { | ||
228 | ProjectWorkspace::Json { project, sysroot } => { | ||
229 | let sysroot_dps = sysroot | ||
230 | .as_ref() | ||
231 | .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); | ||
232 | |||
233 | let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default(); | ||
234 | let crates: FxHashMap<_, _> = project | ||
235 | .crates() | ||
236 | .filter_map(|(crate_id, krate)| { | ||
237 | let file_path = &krate.root_module; | ||
238 | let file_id = match load(&file_path) { | ||
239 | Some(id) => id, | ||
240 | None => { | ||
241 | log::error!("failed to load crate root {}", file_path.display()); | ||
242 | return None; | ||
243 | } | ||
244 | }; | ||
245 | |||
246 | let env = krate.env.clone().into_iter().collect(); | ||
247 | let proc_macro = krate | ||
248 | .proc_macro_dylib_path | ||
249 | .clone() | ||
250 | .map(|it| proc_macro_client.by_dylib_path(&it)); | ||
251 | |||
252 | let target = krate.target.as_deref().or(target); | ||
253 | let target_cfgs = cfg_cache | ||
254 | .entry(target) | ||
255 | .or_insert_with(|| get_rustc_cfg_options(target)); | ||
256 | |||
257 | let mut cfg_options = CfgOptions::default(); | ||
258 | cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned()); | ||
259 | |||
260 | Some(( | ||
261 | crate_id, | ||
262 | crate_graph.add_crate_root( | ||
263 | file_id, | ||
264 | krate.edition, | ||
265 | krate.display_name.clone(), | ||
266 | cfg_options, | ||
267 | env, | ||
268 | proc_macro.unwrap_or_default(), | ||
269 | ), | ||
270 | )) | ||
271 | }) | ||
272 | .collect(); | ||
273 | |||
274 | for (from, krate) in project.crates() { | ||
275 | if let Some(&from) = crates.get(&from) { | ||
276 | if let Some((public_deps, _proc_macro)) = &sysroot_dps { | ||
277 | for (name, to) in public_deps.iter() { | ||
278 | if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) { | ||
279 | log::error!("cyclic dependency on {} for {:?}", name, from) | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | |||
284 | for dep in &krate.deps { | ||
285 | let to_crate_id = dep.crate_id; | ||
286 | if let Some(&to) = crates.get(&to_crate_id) { | ||
287 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) { | ||
288 | log::error!("cyclic dependency {:?} -> {:?}", from, to); | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | } | ||
294 | } | ||
295 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { | ||
296 | let (public_deps, libproc_macro) = | ||
297 | sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load); | ||
298 | |||
299 | let mut cfg_options = CfgOptions::default(); | ||
300 | cfg_options.extend(get_rustc_cfg_options(target)); | ||
301 | |||
302 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
303 | |||
304 | // Add test cfg for non-sysroot crates | ||
305 | cfg_options.insert_atom("test".into()); | ||
306 | cfg_options.insert_atom("debug_assertions".into()); | ||
307 | |||
308 | let mut pkg_crates = FxHashMap::default(); | ||
309 | |||
310 | // Next, create crates for each package, target pair | ||
311 | for pkg in cargo.packages() { | ||
312 | let mut lib_tgt = None; | ||
313 | for &tgt in cargo[pkg].targets.iter() { | ||
314 | if let Some(crate_id) = add_target_crate_root( | ||
315 | &mut crate_graph, | ||
316 | &cargo[pkg], | ||
317 | &cargo[tgt], | ||
318 | &cfg_options, | ||
319 | proc_macro_client, | ||
320 | load, | ||
321 | ) { | ||
322 | if cargo[tgt].kind == TargetKind::Lib { | ||
323 | lib_tgt = Some((crate_id, cargo[tgt].name.clone())); | ||
324 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
325 | } | ||
326 | if cargo[tgt].is_proc_macro { | ||
327 | if let Some(proc_macro) = libproc_macro { | ||
328 | if let Err(_) = crate_graph.add_dep( | ||
329 | crate_id, | ||
330 | CrateName::new("proc_macro").unwrap(), | ||
331 | proc_macro, | ||
332 | ) { | ||
333 | log::error!( | ||
334 | "cyclic dependency on proc_macro for {}", | ||
335 | &cargo[pkg].name | ||
336 | ) | ||
337 | } | ||
338 | } | ||
339 | } | ||
340 | |||
341 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
342 | } | ||
343 | } | ||
344 | |||
345 | // Set deps to the core, std and to the lib target of the current package | ||
346 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
347 | if let Some((to, name)) = lib_tgt.clone() { | ||
348 | // For root projects with dashes in their name, | ||
349 | // cargo metadata does not do any normalization, | ||
350 | // so we do it ourselves currently | ||
351 | let name = CrateName::normalize_dashes(&name); | ||
352 | if to != from && crate_graph.add_dep(from, name, to).is_err() { | ||
353 | log::error!( | ||
354 | "cyclic dependency between targets of {}", | ||
355 | &cargo[pkg].name | ||
356 | ) | ||
357 | } | ||
358 | } | ||
359 | for (name, krate) in public_deps.iter() { | ||
360 | if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) { | ||
361 | log::error!( | ||
362 | "cyclic dependency on {} for {}", | ||
363 | name, | ||
364 | &cargo[pkg].name | ||
365 | ) | ||
366 | } | ||
367 | } | ||
368 | } | ||
369 | } | ||
370 | |||
371 | // Now add a dep edge from all targets of upstream to the lib | ||
372 | // target of downstream. | ||
373 | for pkg in cargo.packages() { | ||
374 | for dep in cargo[pkg].dependencies.iter() { | ||
375 | let name = CrateName::new(&dep.name).unwrap(); | ||
376 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
377 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
378 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
379 | log::error!( | ||
380 | "cyclic dependency {} -> {}", | ||
381 | &cargo[pkg].name, | ||
382 | &cargo[dep.pkg].name | ||
383 | ) | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | } | ||
388 | } | ||
389 | |||
390 | let mut rustc_pkg_crates = FxHashMap::default(); | ||
391 | |||
392 | // If the user provided a path to rustc sources, we add all the rustc_private crates | ||
393 | // and create dependencies on them for the crates in the current workspace | ||
394 | if let Some(rustc_workspace) = rustc { | ||
395 | for pkg in rustc_workspace.packages() { | ||
396 | for &tgt in rustc_workspace[pkg].targets.iter() { | ||
397 | if rustc_workspace[tgt].kind != TargetKind::Lib { | ||
398 | continue; | ||
399 | } | ||
400 | // Exclude alloc / core / std | ||
401 | if rustc_workspace[tgt] | ||
402 | .root | ||
403 | .components() | ||
404 | .any(|c| c == Component::Normal("library".as_ref())) | ||
405 | { | ||
406 | continue; | ||
407 | } | ||
408 | |||
409 | if let Some(crate_id) = add_target_crate_root( | ||
410 | &mut crate_graph, | ||
411 | &rustc_workspace[pkg], | ||
412 | &rustc_workspace[tgt], | ||
413 | &cfg_options, | ||
414 | proc_macro_client, | ||
415 | load, | ||
416 | ) { | ||
417 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
418 | // Add dependencies on the core / std / alloc for rustc | ||
419 | for (name, krate) in public_deps.iter() { | ||
420 | if let Err(_) = | ||
421 | crate_graph.add_dep(crate_id, name.clone(), *krate) | ||
422 | { | ||
423 | log::error!( | ||
424 | "cyclic dependency on {} for {}", | ||
425 | name, | ||
426 | &cargo[pkg].name | ||
427 | ) | ||
428 | } | ||
429 | } | ||
430 | rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
431 | } | ||
432 | } | ||
433 | } | ||
434 | // Now add a dep edge from all targets of upstream to the lib | ||
435 | // target of downstream. | ||
436 | for pkg in rustc_workspace.packages() { | ||
437 | for dep in rustc_workspace[pkg].dependencies.iter() { | ||
438 | let name = CrateName::new(&dep.name).unwrap(); | ||
439 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
440 | for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() { | ||
441 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
442 | log::error!( | ||
443 | "cyclic dependency {} -> {}", | ||
444 | &rustc_workspace[pkg].name, | ||
445 | &rustc_workspace[dep.pkg].name | ||
446 | ) | ||
447 | } | ||
448 | } | ||
449 | } | ||
450 | } | ||
451 | } | ||
452 | |||
453 | // Add dependencies for all the crates of the current workspace to rustc_private libraries | ||
454 | for dep in rustc_workspace.packages() { | ||
455 | let name = CrateName::normalize_dashes(&rustc_workspace[dep].name); | ||
456 | |||
457 | if let Some(&to) = pkg_to_lib_crate.get(&dep) { | ||
458 | for pkg in cargo.packages() { | ||
459 | if !cargo[pkg].is_member { | ||
460 | continue; | ||
461 | } | ||
462 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
463 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
464 | log::error!( | ||
465 | "cyclic dependency {} -> {}", | ||
466 | &cargo[pkg].name, | ||
467 | &rustc_workspace[dep].name | ||
468 | ) | ||
469 | } | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | } | ||
474 | } | ||
475 | } | ||
476 | } | ||
477 | if crate_graph.patch_cfg_if() { | ||
478 | log::debug!("Patched std to depend on cfg-if") | ||
479 | } else { | ||
480 | log::debug!("Did not patch std to depend on cfg-if") | ||
481 | } | ||
482 | crate_graph | ||
483 | } | ||
484 | } | ||
485 | |||
486 | fn add_target_crate_root( | ||
487 | crate_graph: &mut CrateGraph, | ||
488 | pkg: &cargo_workspace::PackageData, | ||
489 | tgt: &cargo_workspace::TargetData, | ||
490 | cfg_options: &CfgOptions, | ||
491 | proc_macro_client: &ProcMacroClient, | ||
492 | load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, | ||
493 | ) -> Option<CrateId> { | ||
494 | let root = tgt.root.as_path(); | ||
495 | if let Some(file_id) = load(root) { | ||
496 | let edition = pkg.edition; | ||
497 | let cfg_options = { | ||
498 | let mut opts = cfg_options.clone(); | ||
499 | for feature in pkg.features.iter() { | ||
500 | opts.insert_key_value("feature".into(), feature.into()); | ||
501 | } | ||
502 | opts.extend(pkg.cfgs.iter().cloned()); | ||
503 | opts | ||
504 | }; | ||
505 | let mut env = Env::default(); | ||
506 | if let Some(out_dir) = &pkg.out_dir { | ||
507 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() | ||
508 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { | ||
509 | env.set("OUT_DIR", out_dir); | ||
510 | } | ||
511 | } | ||
512 | let proc_macro = pkg | ||
513 | .proc_macro_dylib_path | ||
514 | .as_ref() | ||
515 | .map(|it| proc_macro_client.by_dylib_path(&it)) | ||
516 | .unwrap_or_default(); | ||
517 | |||
518 | let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone()); | ||
519 | let crate_id = crate_graph.add_crate_root( | ||
520 | file_id, | ||
521 | edition, | ||
522 | Some(display_name), | ||
523 | cfg_options, | ||
524 | env, | ||
525 | proc_macro.clone(), | ||
526 | ); | ||
527 | |||
528 | return Some(crate_id); | ||
529 | } | ||
530 | None | ||
531 | } | ||
532 | fn sysroot_to_crate_graph( | ||
533 | crate_graph: &mut CrateGraph, | ||
534 | sysroot: &Sysroot, | ||
535 | target: Option<&str>, | ||
536 | load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, | ||
537 | ) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) { | ||
538 | let mut cfg_options = CfgOptions::default(); | ||
539 | cfg_options.extend(get_rustc_cfg_options(target)); | ||
540 | let sysroot_crates: FxHashMap<_, _> = sysroot | ||
541 | .crates() | ||
542 | .filter_map(|krate| { | ||
543 | let file_id = load(&sysroot[krate].root)?; | ||
544 | |||
545 | let env = Env::default(); | ||
546 | let proc_macro = vec![]; | ||
547 | let name = CrateName::new(&sysroot[krate].name) | ||
548 | .expect("Sysroot crates' names do not contain dashes"); | ||
549 | let crate_id = crate_graph.add_crate_root( | ||
550 | file_id, | ||
551 | Edition::Edition2018, | ||
552 | Some(name.into()), | ||
553 | cfg_options.clone(), | ||
554 | env, | ||
555 | proc_macro, | ||
556 | ); | ||
557 | Some((krate, crate_id)) | ||
558 | }) | ||
559 | .collect(); | ||
560 | |||
561 | for from in sysroot.crates() { | ||
562 | for &to in sysroot[from].deps.iter() { | ||
563 | let name = CrateName::new(&sysroot[to].name).unwrap(); | ||
564 | if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) { | ||
565 | if let Err(_) = crate_graph.add_dep(from, name, to) { | ||
566 | log::error!("cyclic dependency between sysroot crates") | ||
567 | } | ||
568 | } | ||
569 | } | ||
570 | } | ||
571 | |||
572 | let public_deps = sysroot | ||
573 | .public_deps() | ||
574 | .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx])) | ||
575 | .collect::<Vec<_>>(); | ||
576 | |||
577 | let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied()); | ||
578 | (public_deps, libproc_macro) | ||
579 | } | ||
580 | |||
581 | fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> { | ||
582 | let mut res = Vec::new(); | ||
583 | |||
584 | // Some nightly-only cfgs, which are required for stdlib | ||
585 | res.push(CfgFlag::Atom("target_thread_local".into())); | ||
586 | for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() { | ||
587 | for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() { | ||
588 | res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() }); | ||
589 | } | ||
590 | } | ||
591 | |||
592 | let rustc_cfgs = { | ||
593 | let mut cmd = Command::new(toolchain::rustc()); | ||
594 | cmd.args(&["--print", "cfg", "-O"]); | ||
595 | if let Some(target) = target { | ||
596 | cmd.args(&["--target", target]); | ||
597 | } | ||
598 | utf8_stdout(cmd) | ||
599 | }; | ||
600 | |||
601 | match rustc_cfgs { | ||
602 | Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())), | ||
603 | Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), | ||
604 | } | ||
605 | |||
606 | res | ||
607 | } | ||