aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model/src/workspace.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/project_model/src/workspace.rs')
-rw-r--r--crates/project_model/src/workspace.rs607
1 files changed, 607 insertions, 0 deletions
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 @@
1use std::{fmt, fs, path::Component, process::Command};
2
3use anyhow::{Context, Result};
4use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
5use cfg::CfgOptions;
6use paths::{AbsPath, AbsPathBuf};
7use proc_macro_api::ProcMacroClient;
8use rustc_hash::{FxHashMap, FxHashSet};
9
10use 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)]
19pub 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)]
27pub 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
34impl 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
58impl 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
486fn 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}
532fn 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
581fn 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}