aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model/src/lib.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-11-13 15:44:48 +0000
committerAleksey Kladov <[email protected]>2020-11-13 15:44:48 +0000
commitaeda30e301d74e40fc1eb992fad581afb627126f (patch)
tree0b3a116ab450e5a1eae24bde65ed683e75f9555a /crates/project_model/src/lib.rs
parentf0f13d8cfa3a9d6d3968be1cc852eb3120c94653 (diff)
Move tricky workspace logic to a separate module
Diffstat (limited to 'crates/project_model/src/lib.rs')
-rw-r--r--crates/project_model/src/lib.rs606
1 files changed, 4 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;
4mod project_json; 4mod project_json;
5mod sysroot; 5mod sysroot;
6mod cfg_flag; 6mod cfg_flag;
7mod workspace;
7 8
8use std::{ 9use 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
16use anyhow::{bail, Context, Result}; 15use anyhow::{bail, Context, Result};
17use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
18use cfg::CfgOptions;
19use paths::{AbsPath, AbsPathBuf}; 16use paths::{AbsPath, AbsPathBuf};
20use rustc_hash::{FxHashMap, FxHashSet}; 17use rustc_hash::FxHashSet;
21
22use crate::cfg_flag::CfgFlag;
23 18
24pub use crate::{ 19pub 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
30pub use proc_macro_api::ProcMacroClient; 26pub use proc_macro_api::ProcMacroClient;
31 27
32#[derive(Clone, Eq, PartialEq)]
33pub 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
40impl 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)]
68pub 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)]
76pub enum ProjectManifest { 29pub enum ProjectManifest {
77 ProjectJson(AbsPathBuf), 30 ProjectJson(AbsPathBuf),
@@ -158,462 +111,6 @@ impl ProjectManifest {
158 } 111 }
159} 112}
160 113
161impl 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
589fn 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
617fn utf8_stdout(mut cmd: Command) -> Result<String> { 114fn 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
631fn 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}
677fn 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}