]> git.lizzy.rs Git - rust.git/blob - crates/project-model/src/workspace.rs
internal: Don't log default build script outputs
[rust.git] / crates / project-model / src / workspace.rs
1 //! Handles lowering of build-system specific workspace information (`cargo
2 //! metadata` or `rust-project.json`) into representation stored in the salsa
3 //! database -- `CrateGraph`.
4
5 use std::{collections::VecDeque, fmt, fs, process::Command};
6
7 use anyhow::{format_err, Context, Result};
8 use base_db::{
9     CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
10     FileId, LangCrateOrigin, ProcMacro,
11 };
12 use cfg::{CfgDiff, CfgOptions};
13 use paths::{AbsPath, AbsPathBuf};
14 use rustc_hash::{FxHashMap, FxHashSet};
15 use stdx::always;
16
17 use crate::{
18     build_scripts::BuildScriptOutput,
19     cargo_workspace::{DepKind, PackageData, RustcSource},
20     cfg_flag::CfgFlag,
21     rustc_cfg,
22     sysroot::SysrootCrate,
23     utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, ProjectJson, ProjectManifest, Sysroot,
24     TargetKind, WorkspaceBuildScripts,
25 };
26
27 /// A set of cfg-overrides per crate.
28 ///
29 /// `Wildcard(..)` is useful e.g. disabling `#[cfg(test)]` on all crates,
30 /// without having to first obtain a list of all crates.
31 #[derive(Debug, Clone, Eq, PartialEq)]
32 pub enum CfgOverrides {
33     /// A single global set of overrides matching all crates.
34     Wildcard(CfgDiff),
35     /// A set of overrides matching specific crates.
36     Selective(FxHashMap<String, CfgDiff>),
37 }
38
39 impl Default for CfgOverrides {
40     fn default() -> Self {
41         Self::Selective(FxHashMap::default())
42     }
43 }
44
45 impl CfgOverrides {
46     pub fn len(&self) -> usize {
47         match self {
48             CfgOverrides::Wildcard(_) => 1,
49             CfgOverrides::Selective(hash_map) => hash_map.len(),
50         }
51     }
52 }
53
54 /// `PackageRoot` describes a package root folder.
55 /// Which may be an external dependency, or a member of
56 /// the current workspace.
57 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
58 pub struct PackageRoot {
59     /// Is from the local filesystem and may be edited
60     pub is_local: bool,
61     pub include: Vec<AbsPathBuf>,
62     pub exclude: Vec<AbsPathBuf>,
63 }
64
65 #[derive(Clone, Eq, PartialEq)]
66 pub enum ProjectWorkspace {
67     /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
68     Cargo {
69         cargo: CargoWorkspace,
70         build_scripts: WorkspaceBuildScripts,
71         sysroot: Option<Sysroot>,
72         rustc: Option<CargoWorkspace>,
73         /// Holds cfg flags for the current target. We get those by running
74         /// `rustc --print cfg`.
75         ///
76         /// FIXME: make this a per-crate map, as, eg, build.rs might have a
77         /// different target.
78         rustc_cfg: Vec<CfgFlag>,
79         cfg_overrides: CfgOverrides,
80     },
81     /// Project workspace was manually specified using a `rust-project.json` file.
82     Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
83
84     // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
85     // That's not the end user experience we should strive for.
86     // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
87     // That needs some changes on the salsa-level though.
88     // In particular, we should split the unified CrateGraph (which currently has maximal durability) into proper crate graph, and a set of ad hoc roots (with minimal durability).
89     // Then, we need to hide the graph behind the queries such that most queries look only at the proper crate graph, and fall back to ad hoc roots only if there's no results.
90     // After this, we should be able to tweak the logic in reload.rs to add newly opened files, which don't belong to any existing crates, to the set of the detached files.
91     // //
92     /// Project with a set of disjoint files, not belonging to any particular workspace.
93     /// Backed by basic sysroot crates for basic completion and highlighting.
94     DetachedFiles { files: Vec<AbsPathBuf>, sysroot: Sysroot, rustc_cfg: Vec<CfgFlag> },
95 }
96
97 impl fmt::Debug for ProjectWorkspace {
98     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99         // Make sure this isn't too verbose.
100         match self {
101             ProjectWorkspace::Cargo {
102                 cargo,
103                 build_scripts: _,
104                 sysroot,
105                 rustc,
106                 rustc_cfg,
107                 cfg_overrides,
108             } => f
109                 .debug_struct("Cargo")
110                 .field("root", &cargo.workspace_root().file_name())
111                 .field("n_packages", &cargo.packages().len())
112                 .field("sysroot", &sysroot.is_some())
113                 .field(
114                     "n_rustc_compiler_crates",
115                     &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
116                 )
117                 .field("n_rustc_cfg", &rustc_cfg.len())
118                 .field("n_cfg_overrides", &cfg_overrides.len())
119                 .finish(),
120             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
121                 let mut debug_struct = f.debug_struct("Json");
122                 debug_struct.field("n_crates", &project.n_crates());
123                 if let Some(sysroot) = sysroot {
124                     debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
125                 }
126                 debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
127                 debug_struct.finish()
128             }
129             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
130                 .debug_struct("DetachedFiles")
131                 .field("n_files", &files.len())
132                 .field("n_sysroot_crates", &sysroot.crates().len())
133                 .field("n_rustc_cfg", &rustc_cfg.len())
134                 .finish(),
135         }
136     }
137 }
138
139 impl ProjectWorkspace {
140     pub fn load(
141         manifest: ProjectManifest,
142         config: &CargoConfig,
143         progress: &dyn Fn(String),
144     ) -> Result<ProjectWorkspace> {
145         let res = match manifest {
146             ProjectManifest::ProjectJson(project_json) => {
147                 let file = fs::read_to_string(&project_json).with_context(|| {
148                     format!("Failed to read json file {}", project_json.display())
149                 })?;
150                 let data = serde_json::from_str(&file).with_context(|| {
151                     format!("Failed to deserialize json file {}", project_json.display())
152                 })?;
153                 let project_location = project_json.parent().to_path_buf();
154                 let project_json = ProjectJson::new(&project_location, data);
155                 ProjectWorkspace::load_inline(project_json, config.target.as_deref())?
156             }
157             ProjectManifest::CargoToml(cargo_toml) => {
158                 let cargo_version = utf8_stdout({
159                     let mut cmd = Command::new(toolchain::cargo());
160                     cmd.arg("--version");
161                     cmd
162                 })?;
163
164                 let meta = CargoWorkspace::fetch_metadata(
165                     &cargo_toml,
166                     cargo_toml.parent(),
167                     config,
168                     progress,
169                 )
170                 .with_context(|| {
171                     format!(
172                         "Failed to read Cargo metadata from Cargo.toml file {}, {}",
173                         cargo_toml.display(),
174                         cargo_version
175                     )
176                 })?;
177                 let cargo = CargoWorkspace::new(meta);
178
179                 let sysroot = if config.no_sysroot {
180                     None
181                 } else {
182                     Some(Sysroot::discover(cargo_toml.parent()).with_context(|| {
183                         format!(
184                             "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
185                             cargo_toml.display()
186                         )
187                     })?)
188                 };
189
190                 let rustc_dir = match &config.rustc_source {
191                     Some(RustcSource::Path(path)) => ManifestPath::try_from(path.clone()).ok(),
192                     Some(RustcSource::Discover) => Sysroot::discover_rustc(&cargo_toml),
193                     None => None,
194                 };
195
196                 let rustc = match rustc_dir {
197                     Some(rustc_dir) => Some({
198                         let meta = CargoWorkspace::fetch_metadata(
199                             &rustc_dir,
200                             cargo_toml.parent(),
201                             config,
202                             progress,
203                         )
204                         .with_context(|| {
205                             "Failed to read Cargo metadata for Rust sources".to_string()
206                         })?;
207                         CargoWorkspace::new(meta)
208                     }),
209                     None => None,
210                 };
211
212                 let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
213
214                 let cfg_overrides = config.cfg_overrides();
215                 ProjectWorkspace::Cargo {
216                     cargo,
217                     build_scripts: WorkspaceBuildScripts::default(),
218                     sysroot,
219                     rustc,
220                     rustc_cfg,
221                     cfg_overrides,
222                 }
223             }
224         };
225
226         Ok(res)
227     }
228
229     pub fn load_inline(
230         project_json: ProjectJson,
231         target: Option<&str>,
232     ) -> Result<ProjectWorkspace> {
233         let sysroot = match &project_json.sysroot_src {
234             Some(path) => Some(Sysroot::load(path.clone())?),
235             None => None,
236         };
237         let rustc_cfg = rustc_cfg::get(None, target);
238         Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
239     }
240
241     pub fn load_detached_files(detached_files: Vec<AbsPathBuf>) -> Result<ProjectWorkspace> {
242         let sysroot = Sysroot::discover(
243             detached_files
244                 .first()
245                 .and_then(|it| it.parent())
246                 .ok_or_else(|| format_err!("No detached files to load"))?,
247         )?;
248         let rustc_cfg = rustc_cfg::get(None, None);
249         Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
250     }
251
252     pub fn run_build_scripts(
253         &self,
254         config: &CargoConfig,
255         progress: &dyn Fn(String),
256     ) -> Result<WorkspaceBuildScripts> {
257         match self {
258             ProjectWorkspace::Cargo { cargo, .. } => {
259                 WorkspaceBuildScripts::run(config, cargo, progress).with_context(|| {
260                     format!("Failed to run build scripts for {}", &cargo.workspace_root().display())
261                 })
262             }
263             ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
264                 Ok(WorkspaceBuildScripts::default())
265             }
266         }
267     }
268
269     pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
270         match self {
271             ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,
272             _ => {
273                 always!(bs == WorkspaceBuildScripts::default());
274             }
275         }
276     }
277
278     /// Returns the roots for the current `ProjectWorkspace`
279     /// The return type contains the path and whether or not
280     /// the root is a member of the current workspace
281     pub fn to_roots(&self) -> Vec<PackageRoot> {
282         match self {
283             ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
284                 .crates()
285                 .map(|(_, krate)| PackageRoot {
286                     is_local: krate.is_workspace_member,
287                     include: krate.include.clone(),
288                     exclude: krate.exclude.clone(),
289                 })
290                 .collect::<FxHashSet<_>>()
291                 .into_iter()
292                 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
293                     sysroot.crates().map(move |krate| PackageRoot {
294                         is_local: false,
295                         include: vec![sysroot[krate].root.parent().to_path_buf()],
296                         exclude: Vec::new(),
297                     })
298                 }))
299                 .collect::<Vec<_>>(),
300             ProjectWorkspace::Cargo {
301                 cargo,
302                 sysroot,
303                 rustc,
304                 rustc_cfg: _,
305                 cfg_overrides: _,
306                 build_scripts,
307             } => {
308                 cargo
309                     .packages()
310                     .map(|pkg| {
311                         let is_local = cargo[pkg].is_local;
312                         let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
313
314                         let mut include = vec![pkg_root.clone()];
315                         let out_dir =
316                             build_scripts.get_output(pkg).and_then(|it| it.out_dir.clone());
317                         include.extend(out_dir);
318
319                         // In case target's path is manually set in Cargo.toml to be
320                         // outside the package root, add its parent as an extra include.
321                         // An example of this situation would look like this:
322                         //
323                         // ```toml
324                         // [lib]
325                         // path = "../../src/lib.rs"
326                         // ```
327                         let extra_targets = cargo[pkg]
328                             .targets
329                             .iter()
330                             .filter(|&&tgt| cargo[tgt].kind == TargetKind::Lib)
331                             .filter_map(|&tgt| cargo[tgt].root.parent())
332                             .map(|tgt| tgt.normalize().to_path_buf())
333                             .filter(|path| !path.starts_with(&pkg_root));
334                         include.extend(extra_targets);
335
336                         let mut exclude = vec![pkg_root.join(".git")];
337                         if is_local {
338                             exclude.push(pkg_root.join("target"));
339                         } else {
340                             exclude.push(pkg_root.join("tests"));
341                             exclude.push(pkg_root.join("examples"));
342                             exclude.push(pkg_root.join("benches"));
343                         }
344                         PackageRoot { is_local, include, exclude }
345                     })
346                     .chain(sysroot.iter().map(|sysroot| PackageRoot {
347                         is_local: false,
348                         include: vec![sysroot.root().to_path_buf()],
349                         exclude: Vec::new(),
350                     }))
351                     .chain(rustc.iter().flat_map(|rustc| {
352                         rustc.packages().map(move |krate| PackageRoot {
353                             is_local: false,
354                             include: vec![rustc[krate].manifest.parent().to_path_buf()],
355                             exclude: Vec::new(),
356                         })
357                     }))
358                     .collect()
359             }
360             ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
361                 .iter()
362                 .map(|detached_file| PackageRoot {
363                     is_local: true,
364                     include: vec![detached_file.clone()],
365                     exclude: Vec::new(),
366                 })
367                 .chain(sysroot.crates().map(|krate| PackageRoot {
368                     is_local: false,
369                     include: vec![sysroot[krate].root.parent().to_path_buf()],
370                     exclude: Vec::new(),
371                 }))
372                 .collect(),
373         }
374     }
375
376     pub fn n_packages(&self) -> usize {
377         match self {
378             ProjectWorkspace::Json { project, .. } => project.n_crates(),
379             ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
380                 let rustc_package_len = rustc.as_ref().map_or(0, |it| it.packages().len());
381                 let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
382                 cargo.packages().len() + sysroot_package_len + rustc_package_len
383             }
384             ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
385                 sysroot.crates().len() + files.len()
386             }
387         }
388     }
389
390     pub fn to_crate_graph(
391         &self,
392         load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
393         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
394     ) -> CrateGraph {
395         let _p = profile::span("ProjectWorkspace::to_crate_graph");
396
397         let mut crate_graph = match self {
398             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
399                 rustc_cfg.clone(),
400                 load_proc_macro,
401                 load,
402                 project,
403                 sysroot,
404             ),
405             ProjectWorkspace::Cargo {
406                 cargo,
407                 sysroot,
408                 rustc,
409                 rustc_cfg,
410                 cfg_overrides,
411                 build_scripts,
412             } => cargo_to_crate_graph(
413                 rustc_cfg.clone(),
414                 cfg_overrides,
415                 load_proc_macro,
416                 load,
417                 cargo,
418                 build_scripts,
419                 sysroot.as_ref(),
420                 rustc,
421             ),
422             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
423                 detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
424             }
425         };
426         if crate_graph.patch_cfg_if() {
427             tracing::debug!("Patched std to depend on cfg-if")
428         } else {
429             tracing::debug!("Did not patch std to depend on cfg-if")
430         }
431         crate_graph
432     }
433 }
434
435 fn project_json_to_crate_graph(
436     rustc_cfg: Vec<CfgFlag>,
437     load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
438     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
439     project: &ProjectJson,
440     sysroot: &Option<Sysroot>,
441 ) -> CrateGraph {
442     let mut crate_graph = CrateGraph::default();
443     let sysroot_deps = sysroot
444         .as_ref()
445         .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load));
446
447     let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
448     let crates: FxHashMap<CrateId, CrateId> = project
449         .crates()
450         .filter_map(|(crate_id, krate)| {
451             let file_path = &krate.root_module;
452             let file_id = load(file_path)?;
453             Some((crate_id, krate, file_id))
454         })
455         .map(|(crate_id, krate, file_id)| {
456             let env = krate.env.clone().into_iter().collect();
457             let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| {
458                 load_proc_macro(
459                     krate.display_name.as_ref().map(|it| it.canonical_name()).unwrap_or(""),
460                     &it,
461                 )
462             });
463
464             let target_cfgs = match krate.target.as_deref() {
465                 Some(target) => {
466                     cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(None, Some(target)))
467                 }
468                 None => &rustc_cfg,
469             };
470
471             let mut cfg_options = CfgOptions::default();
472             cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
473             (
474                 crate_id,
475                 crate_graph.add_crate_root(
476                     file_id,
477                     krate.edition,
478                     krate.display_name.clone(),
479                     krate.version.clone(),
480                     cfg_options.clone(),
481                     cfg_options,
482                     env,
483                     proc_macro.unwrap_or_default(),
484                     krate.is_proc_macro,
485                     if krate.display_name.is_some() {
486                         CrateOrigin::CratesIo { repo: krate.repository.clone() }
487                     } else {
488                         CrateOrigin::CratesIo { repo: None }
489                     },
490                 ),
491             )
492         })
493         .collect();
494
495     for (from, krate) in project.crates() {
496         if let Some(&from) = crates.get(&from) {
497             if let Some((public_deps, libproc_macro)) = &sysroot_deps {
498                 public_deps.add(from, &mut crate_graph);
499                 if krate.is_proc_macro {
500                     if let Some(proc_macro) = libproc_macro {
501                         add_dep(
502                             &mut crate_graph,
503                             from,
504                             CrateName::new("proc_macro").unwrap(),
505                             *proc_macro,
506                         );
507                     }
508                 }
509             }
510
511             for dep in &krate.deps {
512                 if let Some(&to) = crates.get(&dep.crate_id) {
513                     add_dep(&mut crate_graph, from, dep.name.clone(), to)
514                 }
515             }
516         }
517     }
518     crate_graph
519 }
520
521 fn cargo_to_crate_graph(
522     rustc_cfg: Vec<CfgFlag>,
523     override_cfg: &CfgOverrides,
524     load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
525     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
526     cargo: &CargoWorkspace,
527     build_scripts: &WorkspaceBuildScripts,
528     sysroot: Option<&Sysroot>,
529     rustc: &Option<CargoWorkspace>,
530 ) -> CrateGraph {
531     let _p = profile::span("cargo_to_crate_graph");
532     let mut crate_graph = CrateGraph::default();
533     let (public_deps, libproc_macro) = match sysroot {
534         Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
535         None => (SysrootPublicDeps::default(), None),
536     };
537
538     let mut cfg_options = CfgOptions::default();
539     cfg_options.extend(rustc_cfg);
540
541     let mut pkg_to_lib_crate = FxHashMap::default();
542
543     // Add test cfg for non-sysroot crates
544     cfg_options.insert_atom("test".into());
545     cfg_options.insert_atom("debug_assertions".into());
546
547     let mut pkg_crates = FxHashMap::default();
548     // Does any crate signal to rust-analyzer that they need the rustc_private crates?
549     let mut has_private = false;
550     // Next, create crates for each package, target pair
551     for pkg in cargo.packages() {
552         let mut cfg_options = &cfg_options;
553         let mut replaced_cfg_options;
554
555         let overrides = match override_cfg {
556             CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
557             CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
558         };
559
560         if let Some(overrides) = overrides {
561             // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
562             // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
563             // working on rust-lang/rust as that's the only time it appears outside sysroot).
564             //
565             // A more ideal solution might be to reanalyze crates based on where the cursor is and
566             // figure out the set of cfgs that would have to apply to make it active.
567
568             replaced_cfg_options = cfg_options.clone();
569             replaced_cfg_options.apply_diff(overrides.clone());
570             cfg_options = &replaced_cfg_options;
571         };
572
573         has_private |= cargo[pkg].metadata.rustc_private;
574         let mut lib_tgt = None;
575         for &tgt in cargo[pkg].targets.iter() {
576             if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
577                 // For non-workspace-members, Cargo does not resolve dev-dependencies, so we don't
578                 // add any targets except the library target, since those will not work correctly if
579                 // they use dev-dependencies.
580                 // In fact, they can break quite badly if multiple client workspaces get merged:
581                 // https://github.com/rust-analyzer/rust-analyzer/issues/11300
582                 continue;
583             }
584
585             if let Some(file_id) = load(&cargo[tgt].root) {
586                 let crate_id = add_target_crate_root(
587                     &mut crate_graph,
588                     &cargo[pkg],
589                     build_scripts.get_output(pkg),
590                     cfg_options,
591                     &mut |path| load_proc_macro(&cargo[tgt].name, path),
592                     file_id,
593                     &cargo[tgt].name,
594                     cargo[tgt].is_proc_macro,
595                 );
596                 if cargo[tgt].kind == TargetKind::Lib {
597                     lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
598                     pkg_to_lib_crate.insert(pkg, crate_id);
599                 }
600                 if let Some(proc_macro) = libproc_macro {
601                     add_dep_with_prelude(
602                         &mut crate_graph,
603                         crate_id,
604                         CrateName::new("proc_macro").unwrap(),
605                         proc_macro,
606                         cargo[tgt].is_proc_macro,
607                     );
608                 }
609
610                 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
611             }
612         }
613
614         // Set deps to the core, std and to the lib target of the current package
615         for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
616             // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
617             public_deps.add(*from, &mut crate_graph);
618
619             if let Some((to, name)) = lib_tgt.clone() {
620                 if to != *from && *kind != TargetKind::BuildScript {
621                     // (build script can not depend on its library target)
622
623                     // For root projects with dashes in their name,
624                     // cargo metadata does not do any normalization,
625                     // so we do it ourselves currently
626                     let name = CrateName::normalize_dashes(&name);
627                     add_dep(&mut crate_graph, *from, name, to);
628                 }
629             }
630         }
631     }
632
633     // Now add a dep edge from all targets of upstream to the lib
634     // target of downstream.
635     for pkg in cargo.packages() {
636         for dep in cargo[pkg].dependencies.iter() {
637             let name = CrateName::new(&dep.name).unwrap();
638             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
639                 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
640                     if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
641                         // Only build scripts may depend on build dependencies.
642                         continue;
643                     }
644                     if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
645                         // Build scripts may only depend on build dependencies.
646                         continue;
647                     }
648
649                     add_dep(&mut crate_graph, *from, name.clone(), to)
650                 }
651             }
652         }
653     }
654
655     if has_private {
656         // If the user provided a path to rustc sources, we add all the rustc_private crates
657         // and create dependencies on them for the crates which opt-in to that
658         if let Some(rustc_workspace) = rustc {
659             handle_rustc_crates(
660                 rustc_workspace,
661                 load,
662                 &mut crate_graph,
663                 &cfg_options,
664                 override_cfg,
665                 load_proc_macro,
666                 &mut pkg_to_lib_crate,
667                 &public_deps,
668                 cargo,
669                 &pkg_crates,
670             );
671         }
672     }
673     crate_graph
674 }
675
676 fn detached_files_to_crate_graph(
677     rustc_cfg: Vec<CfgFlag>,
678     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
679     detached_files: &[AbsPathBuf],
680     sysroot: &Sysroot,
681 ) -> CrateGraph {
682     let _p = profile::span("detached_files_to_crate_graph");
683     let mut crate_graph = CrateGraph::default();
684     let (public_deps, _libproc_macro) =
685         sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
686
687     let mut cfg_options = CfgOptions::default();
688     cfg_options.extend(rustc_cfg);
689
690     for detached_file in detached_files {
691         let file_id = match load(detached_file) {
692             Some(file_id) => file_id,
693             None => {
694                 tracing::error!("Failed to load detached file {:?}", detached_file);
695                 continue;
696             }
697         };
698         let display_name = detached_file
699             .file_stem()
700             .and_then(|os_str| os_str.to_str())
701             .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
702         let detached_file_crate = crate_graph.add_crate_root(
703             file_id,
704             Edition::CURRENT,
705             display_name,
706             None,
707             cfg_options.clone(),
708             cfg_options.clone(),
709             Env::default(),
710             Vec::new(),
711             false,
712             CrateOrigin::CratesIo { repo: None },
713         );
714
715         public_deps.add(detached_file_crate, &mut crate_graph);
716     }
717     crate_graph
718 }
719
720 fn handle_rustc_crates(
721     rustc_workspace: &CargoWorkspace,
722     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
723     crate_graph: &mut CrateGraph,
724     cfg_options: &CfgOptions,
725     override_cfg: &CfgOverrides,
726     load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
727     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
728     public_deps: &SysrootPublicDeps,
729     cargo: &CargoWorkspace,
730     pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
731 ) {
732     let mut rustc_pkg_crates = FxHashMap::default();
733     // The root package of the rustc-dev component is rustc_driver, so we match that
734     let root_pkg =
735         rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver");
736     // The rustc workspace might be incomplete (such as if rustc-dev is not
737     // installed for the current toolchain) and `rustcSource` is set to discover.
738     if let Some(root_pkg) = root_pkg {
739         // Iterate through every crate in the dependency subtree of rustc_driver using BFS
740         let mut queue = VecDeque::new();
741         queue.push_back(root_pkg);
742         while let Some(pkg) = queue.pop_front() {
743             // Don't duplicate packages if they are dependended on a diamond pattern
744             // N.B. if this line is ommitted, we try to analyse over 4_800_000 crates
745             // which is not ideal
746             if rustc_pkg_crates.contains_key(&pkg) {
747                 continue;
748             }
749             for dep in &rustc_workspace[pkg].dependencies {
750                 queue.push_back(dep.pkg);
751             }
752
753             let mut cfg_options = cfg_options;
754             let mut replaced_cfg_options;
755
756             let overrides = match override_cfg {
757                 CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
758                 CfgOverrides::Selective(cfg_overrides) => {
759                     cfg_overrides.get(&rustc_workspace[pkg].name)
760                 }
761             };
762
763             if let Some(overrides) = overrides {
764                 // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
765                 // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
766                 // working on rust-lang/rust as that's the only time it appears outside sysroot).
767                 //
768                 // A more ideal solution might be to reanalyze crates based on where the cursor is and
769                 // figure out the set of cfgs that would have to apply to make it active.
770
771                 replaced_cfg_options = cfg_options.clone();
772                 replaced_cfg_options.apply_diff(overrides.clone());
773                 cfg_options = &replaced_cfg_options;
774             };
775
776             for &tgt in rustc_workspace[pkg].targets.iter() {
777                 if rustc_workspace[tgt].kind != TargetKind::Lib {
778                     continue;
779                 }
780                 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
781                     let crate_id = add_target_crate_root(
782                         crate_graph,
783                         &rustc_workspace[pkg],
784                         None,
785                         cfg_options,
786                         &mut |path| load_proc_macro(&rustc_workspace[tgt].name, path),
787                         file_id,
788                         &rustc_workspace[tgt].name,
789                         rustc_workspace[tgt].is_proc_macro,
790                     );
791                     pkg_to_lib_crate.insert(pkg, crate_id);
792                     // Add dependencies on core / std / alloc for this crate
793                     public_deps.add(crate_id, crate_graph);
794                     rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
795                 }
796             }
797         }
798     }
799     // Now add a dep edge from all targets of upstream to the lib
800     // target of downstream.
801     for pkg in rustc_pkg_crates.keys().copied() {
802         for dep in rustc_workspace[pkg].dependencies.iter() {
803             let name = CrateName::new(&dep.name).unwrap();
804             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
805                 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
806                     add_dep(crate_graph, from, name.clone(), to);
807                 }
808             }
809         }
810     }
811     // Add a dependency on the rustc_private crates for all targets of each package
812     // which opts in
813     for dep in rustc_workspace.packages() {
814         let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
815
816         if let Some(&to) = pkg_to_lib_crate.get(&dep) {
817             for pkg in cargo.packages() {
818                 let package = &cargo[pkg];
819                 if !package.metadata.rustc_private {
820                     continue;
821                 }
822                 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
823                     // Avoid creating duplicate dependencies
824                     // This avoids the situation where `from` depends on e.g. `arrayvec`, but
825                     // `rust_analyzer` thinks that it should use the one from the `rustcSource`
826                     // instead of the one from `crates.io`
827                     if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
828                         add_dep(crate_graph, *from, name.clone(), to);
829                     }
830                 }
831             }
832         }
833     }
834 }
835
836 fn add_target_crate_root(
837     crate_graph: &mut CrateGraph,
838     pkg: &PackageData,
839     build_data: Option<&BuildScriptOutput>,
840     cfg_options: &CfgOptions,
841     load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
842     file_id: FileId,
843     cargo_name: &str,
844     is_proc_macro: bool,
845 ) -> CrateId {
846     let edition = pkg.edition;
847     let cfg_options = {
848         let mut opts = cfg_options.clone();
849         for feature in pkg.active_features.iter() {
850             opts.insert_key_value("feature".into(), feature.into());
851         }
852         if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
853             opts.extend(cfgs.iter().cloned());
854         }
855         opts
856     };
857
858     let mut env = Env::default();
859     inject_cargo_env(pkg, &mut env);
860
861     if let Some(envs) = build_data.map(|it| &it.envs) {
862         for (k, v) in envs {
863             env.set(k, v.clone());
864         }
865     }
866
867     let proc_macro = build_data
868         .as_ref()
869         .and_then(|it| it.proc_macro_dylib_path.as_ref())
870         .map(|it| load_proc_macro(it))
871         .unwrap_or_default();
872
873     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
874     let mut potential_cfg_options = cfg_options.clone();
875     potential_cfg_options.extend(
876         pkg.features
877             .iter()
878             .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
879     );
880     crate_graph.add_crate_root(
881         file_id,
882         edition,
883         Some(display_name),
884         Some(pkg.version.to_string()),
885         cfg_options,
886         potential_cfg_options,
887         env,
888         proc_macro,
889         is_proc_macro,
890         CrateOrigin::CratesIo { repo: pkg.repository.clone() },
891     )
892 }
893
894 #[derive(Default)]
895 struct SysrootPublicDeps {
896     deps: Vec<(CrateName, CrateId, bool)>,
897 }
898
899 impl SysrootPublicDeps {
900     /// Makes `from` depend on the public sysroot crates.
901     fn add(&self, from: CrateId, crate_graph: &mut CrateGraph) {
902         for (name, krate, prelude) in &self.deps {
903             add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
904         }
905     }
906 }
907
908 fn sysroot_to_crate_graph(
909     crate_graph: &mut CrateGraph,
910     sysroot: &Sysroot,
911     rustc_cfg: Vec<CfgFlag>,
912     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
913 ) -> (SysrootPublicDeps, Option<CrateId>) {
914     let _p = profile::span("sysroot_to_crate_graph");
915     let mut cfg_options = CfgOptions::default();
916     cfg_options.extend(rustc_cfg);
917     let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
918         .crates()
919         .filter_map(|krate| {
920             let file_id = load(&sysroot[krate].root)?;
921
922             let env = Env::default();
923             let proc_macro = vec![];
924             let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
925             let crate_id = crate_graph.add_crate_root(
926                 file_id,
927                 Edition::CURRENT,
928                 Some(display_name),
929                 None,
930                 cfg_options.clone(),
931                 cfg_options.clone(),
932                 env,
933                 proc_macro,
934                 false,
935                 CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
936             );
937             Some((krate, crate_id))
938         })
939         .collect();
940
941     for from in sysroot.crates() {
942         for &to in sysroot[from].deps.iter() {
943             let name = CrateName::new(&sysroot[to].name).unwrap();
944             if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
945                 add_dep(crate_graph, from, name, to);
946             }
947         }
948     }
949
950     let public_deps = SysrootPublicDeps {
951         deps: sysroot
952             .public_deps()
953             .map(|(name, idx, prelude)| {
954                 (CrateName::new(name).unwrap(), sysroot_crates[&idx], prelude)
955             })
956             .collect::<Vec<_>>(),
957     };
958
959     let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
960     (public_deps, libproc_macro)
961 }
962
963 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
964     add_dep_inner(graph, from, Dependency::new(name, to))
965 }
966
967 fn add_dep_with_prelude(
968     graph: &mut CrateGraph,
969     from: CrateId,
970     name: CrateName,
971     to: CrateId,
972     prelude: bool,
973 ) {
974     add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
975 }
976
977 fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
978     if let Err(err) = graph.add_dep(from, dep) {
979         tracing::error!("{}", err)
980     }
981 }
982
983 /// Recreates the compile-time environment variables that Cargo sets.
984 ///
985 /// Should be synced with
986 /// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
987 ///
988 /// FIXME: ask Cargo to provide this data instead of re-deriving.
989 fn inject_cargo_env(package: &PackageData, env: &mut Env) {
990     // FIXME: Missing variables:
991     // CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
992
993     let manifest_dir = package.manifest.parent();
994     env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned());
995
996     // Not always right, but works for common cases.
997     env.set("CARGO", "cargo".into());
998
999     env.set("CARGO_PKG_VERSION", package.version.to_string());
1000     env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string());
1001     env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string());
1002     env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string());
1003     env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string());
1004
1005     env.set("CARGO_PKG_AUTHORS", String::new());
1006
1007     env.set("CARGO_PKG_NAME", package.name.clone());
1008     // FIXME: This isn't really correct (a package can have many crates with different names), but
1009     // it's better than leaving the variable unset.
1010     env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string());
1011     env.set("CARGO_PKG_DESCRIPTION", String::new());
1012     env.set("CARGO_PKG_HOMEPAGE", String::new());
1013     env.set("CARGO_PKG_REPOSITORY", String::new());
1014     env.set("CARGO_PKG_LICENSE", String::new());
1015
1016     env.set("CARGO_PKG_LICENSE_FILE", String::new());
1017 }