aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/project_model/src/cargo_workspace.rs20
-rw-r--r--crates/project_model/src/lib.rs606
-rw-r--r--crates/project_model/src/workspace.rs590
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs15
-rw-r--r--crates/rust-analyzer/src/handlers.rs22
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs14
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/reload.rs7
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/support.rs8
10 files changed, 653 insertions, 631 deletions
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index 608a031d4..540b57ae4 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -65,6 +65,10 @@ pub struct CargoConfig {
65 /// rustc target 65 /// rustc target
66 pub target: Option<String>, 66 pub target: Option<String>,
67 67
68 /// Don't load sysroot crates (`std`, `core` & friends). Might be useful
69 /// when debugging isolated issues.
70 pub no_sysroot: bool,
71
68 /// rustc private crate source 72 /// rustc private crate source
69 pub rustc_source: Option<AbsPathBuf>, 73 pub rustc_source: Option<AbsPathBuf>,
70} 74}
@@ -140,27 +144,27 @@ impl PackageData {
140impl CargoWorkspace { 144impl CargoWorkspace {
141 pub fn from_cargo_metadata( 145 pub fn from_cargo_metadata(
142 cargo_toml: &AbsPath, 146 cargo_toml: &AbsPath,
143 cargo_features: &CargoConfig, 147 config: &CargoConfig,
144 ) -> Result<CargoWorkspace> { 148 ) -> Result<CargoWorkspace> {
145 let mut meta = MetadataCommand::new(); 149 let mut meta = MetadataCommand::new();
146 meta.cargo_path(toolchain::cargo()); 150 meta.cargo_path(toolchain::cargo());
147 meta.manifest_path(cargo_toml.to_path_buf()); 151 meta.manifest_path(cargo_toml.to_path_buf());
148 if cargo_features.all_features { 152 if config.all_features {
149 meta.features(CargoOpt::AllFeatures); 153 meta.features(CargoOpt::AllFeatures);
150 } else { 154 } else {
151 if cargo_features.no_default_features { 155 if config.no_default_features {
152 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 156 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
153 // https://github.com/oli-obk/cargo_metadata/issues/79 157 // https://github.com/oli-obk/cargo_metadata/issues/79
154 meta.features(CargoOpt::NoDefaultFeatures); 158 meta.features(CargoOpt::NoDefaultFeatures);
155 } 159 }
156 if !cargo_features.features.is_empty() { 160 if !config.features.is_empty() {
157 meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); 161 meta.features(CargoOpt::SomeFeatures(config.features.clone()));
158 } 162 }
159 } 163 }
160 if let Some(parent) = cargo_toml.parent() { 164 if let Some(parent) = cargo_toml.parent() {
161 meta.current_dir(parent.to_path_buf()); 165 meta.current_dir(parent.to_path_buf());
162 } 166 }
163 if let Some(target) = cargo_features.target.as_ref() { 167 if let Some(target) = config.target.as_ref() {
164 meta.other_options(vec![String::from("--filter-platform"), target.clone()]); 168 meta.other_options(vec![String::from("--filter-platform"), target.clone()]);
165 } 169 }
166 let mut meta = meta.exec().with_context(|| { 170 let mut meta = meta.exec().with_context(|| {
@@ -170,8 +174,8 @@ impl CargoWorkspace {
170 let mut out_dir_by_id = FxHashMap::default(); 174 let mut out_dir_by_id = FxHashMap::default();
171 let mut cfgs = FxHashMap::default(); 175 let mut cfgs = FxHashMap::default();
172 let mut proc_macro_dylib_paths = FxHashMap::default(); 176 let mut proc_macro_dylib_paths = FxHashMap::default();
173 if cargo_features.load_out_dirs_from_check { 177 if config.load_out_dirs_from_check {
174 let resources = load_extern_resources(cargo_toml, cargo_features)?; 178 let resources = load_extern_resources(cargo_toml, config)?;
175 out_dir_by_id = resources.out_dirs; 179 out_dir_by_id = resources.out_dirs;
176 cfgs = resources.cfgs; 180 cfgs = resources.cfgs;
177 proc_macro_dylib_paths = resources.proc_dylib_paths; 181 proc_macro_dylib_paths = resources.proc_dylib_paths;
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}
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
new file mode 100644
index 000000000..9ebb0a811
--- /dev/null
+++ b/crates/project_model/src/workspace.rs
@@ -0,0 +1,590 @@
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
5use std::{fmt, fs, path::Component, process::Command};
6
7use anyhow::{Context, Result};
8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
9use cfg::CfgOptions;
10use paths::{AbsPath, AbsPathBuf};
11use proc_macro_api::ProcMacroClient;
12use rustc_hash::{FxHashMap, FxHashSet};
13
14use crate::{
15 cargo_workspace, cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoWorkspace, ProjectJson,
16 ProjectManifest, Sysroot, TargetKind,
17};
18
19/// `PackageRoot` describes a package root folder.
20/// Which may be an external dependency, or a member of
21/// the current workspace.
22#[derive(Debug, Clone, Eq, PartialEq, Hash)]
23pub struct PackageRoot {
24 /// Is a member of the current workspace
25 pub is_member: bool,
26 pub include: Vec<AbsPathBuf>,
27 pub exclude: Vec<AbsPathBuf>,
28}
29
30#[derive(Clone, Eq, PartialEq)]
31pub enum ProjectWorkspace {
32 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
33 Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
34 /// Project workspace was manually specified using a `rust-project.json` file.
35 Json { project: ProjectJson, sysroot: Option<Sysroot> },
36}
37
38impl fmt::Debug for ProjectWorkspace {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
42 .debug_struct("Cargo")
43 .field("n_packages", &cargo.packages().len())
44 .field("n_sysroot_crates", &sysroot.crates().len())
45 .field(
46 "n_rustc_compiler_crates",
47 &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
48 )
49 .finish(),
50 ProjectWorkspace::Json { project, sysroot } => {
51 let mut debug_struct = f.debug_struct("Json");
52 debug_struct.field("n_crates", &project.n_crates());
53 if let Some(sysroot) = sysroot {
54 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
55 }
56 debug_struct.finish()
57 }
58 }
59 }
60}
61
62impl ProjectWorkspace {
63 pub fn load(manifest: ProjectManifest, config: &CargoConfig) -> 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, config).with_context(
88 || {
89 format!(
90 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
91 cargo_toml.display(),
92 cargo_version
93 )
94 },
95 )?;
96 let sysroot = if config.no_sysroot {
97 Sysroot::default()
98 } else {
99 Sysroot::discover(&cargo_toml).with_context(|| {
100 format!(
101 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
102 cargo_toml.display()
103 )
104 })?
105 };
106
107 let rustc = if let Some(rustc_dir) = &config.rustc_source {
108 Some(CargoWorkspace::from_cargo_metadata(&rustc_dir, config).with_context(
109 || format!("Failed to read Cargo metadata for Rust sources"),
110 )?)
111 } else {
112 None
113 };
114
115 ProjectWorkspace::Cargo { cargo, sysroot, rustc }
116 }
117 };
118
119 Ok(res)
120 }
121
122 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
123 let sysroot = match &project_json.sysroot_src {
124 Some(path) => Some(Sysroot::load(path)?),
125 None => None,
126 };
127
128 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
129 }
130
131 /// Returns the roots for the current `ProjectWorkspace`
132 /// The return type contains the path and whether or not
133 /// the root is a member of the current workspace
134 pub fn to_roots(&self) -> Vec<PackageRoot> {
135 match self {
136 ProjectWorkspace::Json { project, sysroot } => project
137 .crates()
138 .map(|(_, krate)| PackageRoot {
139 is_member: krate.is_workspace_member,
140 include: krate.include.clone(),
141 exclude: krate.exclude.clone(),
142 })
143 .collect::<FxHashSet<_>>()
144 .into_iter()
145 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
146 sysroot.crates().map(move |krate| PackageRoot {
147 is_member: false,
148 include: vec![sysroot[krate].root_dir().to_path_buf()],
149 exclude: Vec::new(),
150 })
151 }))
152 .collect::<Vec<_>>(),
153 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
154 let roots = cargo
155 .packages()
156 .map(|pkg| {
157 let is_member = cargo[pkg].is_member;
158 let pkg_root = cargo[pkg].root().to_path_buf();
159
160 let mut include = vec![pkg_root.clone()];
161 include.extend(cargo[pkg].out_dir.clone());
162
163 let mut exclude = vec![pkg_root.join(".git")];
164 if is_member {
165 exclude.push(pkg_root.join("target"));
166 } else {
167 exclude.push(pkg_root.join("tests"));
168 exclude.push(pkg_root.join("examples"));
169 exclude.push(pkg_root.join("benches"));
170 }
171 PackageRoot { is_member, include, exclude }
172 })
173 .chain(sysroot.crates().map(|krate| PackageRoot {
174 is_member: false,
175 include: vec![sysroot[krate].root_dir().to_path_buf()],
176 exclude: Vec::new(),
177 }));
178 if let Some(rustc_packages) = rustc {
179 roots
180 .chain(rustc_packages.packages().map(|krate| PackageRoot {
181 is_member: false,
182 include: vec![rustc_packages[krate].root().to_path_buf()],
183 exclude: Vec::new(),
184 }))
185 .collect()
186 } else {
187 roots.collect()
188 }
189 }
190 }
191 }
192
193 pub fn n_packages(&self) -> usize {
194 match self {
195 ProjectWorkspace::Json { project, .. } => project.n_crates(),
196 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
197 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
198 cargo.packages().len() + sysroot.crates().len() + rustc_package_len
199 }
200 }
201 }
202
203 pub fn to_crate_graph(
204 &self,
205 target: Option<&str>,
206 proc_macro_client: &ProcMacroClient,
207 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
208 ) -> CrateGraph {
209 let mut crate_graph = CrateGraph::default();
210 match self {
211 ProjectWorkspace::Json { project, sysroot } => {
212 let sysroot_dps = sysroot
213 .as_ref()
214 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
215
216 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
217 let crates: FxHashMap<_, _> = project
218 .crates()
219 .filter_map(|(crate_id, krate)| {
220 let file_path = &krate.root_module;
221 let file_id = match load(&file_path) {
222 Some(id) => id,
223 None => {
224 log::error!("failed to load crate root {}", file_path.display());
225 return None;
226 }
227 };
228
229 let env = krate.env.clone().into_iter().collect();
230 let proc_macro = krate
231 .proc_macro_dylib_path
232 .clone()
233 .map(|it| proc_macro_client.by_dylib_path(&it));
234
235 let target = krate.target.as_deref().or(target);
236 let target_cfgs = cfg_cache
237 .entry(target)
238 .or_insert_with(|| get_rustc_cfg_options(target));
239
240 let mut cfg_options = CfgOptions::default();
241 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
242
243 Some((
244 crate_id,
245 crate_graph.add_crate_root(
246 file_id,
247 krate.edition,
248 krate.display_name.clone(),
249 cfg_options,
250 env,
251 proc_macro.unwrap_or_default(),
252 ),
253 ))
254 })
255 .collect();
256
257 for (from, krate) in project.crates() {
258 if let Some(&from) = crates.get(&from) {
259 if let Some((public_deps, _proc_macro)) = &sysroot_dps {
260 for (name, to) in public_deps.iter() {
261 if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) {
262 log::error!("cyclic dependency on {} for {:?}", name, from)
263 }
264 }
265 }
266
267 for dep in &krate.deps {
268 let to_crate_id = dep.crate_id;
269 if let Some(&to) = crates.get(&to_crate_id) {
270 if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
271 log::error!("cyclic dependency {:?} -> {:?}", from, to);
272 }
273 }
274 }
275 }
276 }
277 }
278 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
279 let (public_deps, libproc_macro) =
280 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
281
282 let mut cfg_options = CfgOptions::default();
283 cfg_options.extend(get_rustc_cfg_options(target));
284
285 let mut pkg_to_lib_crate = FxHashMap::default();
286
287 // Add test cfg for non-sysroot crates
288 cfg_options.insert_atom("test".into());
289 cfg_options.insert_atom("debug_assertions".into());
290
291 let mut pkg_crates = FxHashMap::default();
292
293 // Next, create crates for each package, target pair
294 for pkg in cargo.packages() {
295 let mut lib_tgt = None;
296 for &tgt in cargo[pkg].targets.iter() {
297 if let Some(crate_id) = add_target_crate_root(
298 &mut crate_graph,
299 &cargo[pkg],
300 &cargo[tgt],
301 &cfg_options,
302 proc_macro_client,
303 load,
304 ) {
305 if cargo[tgt].kind == TargetKind::Lib {
306 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
307 pkg_to_lib_crate.insert(pkg, crate_id);
308 }
309 if cargo[tgt].is_proc_macro {
310 if let Some(proc_macro) = libproc_macro {
311 if let Err(_) = crate_graph.add_dep(
312 crate_id,
313 CrateName::new("proc_macro").unwrap(),
314 proc_macro,
315 ) {
316 log::error!(
317 "cyclic dependency on proc_macro for {}",
318 &cargo[pkg].name
319 )
320 }
321 }
322 }
323
324 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
325 }
326 }
327
328 // Set deps to the core, std and to the lib target of the current package
329 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
330 if let Some((to, name)) = lib_tgt.clone() {
331 // For root projects with dashes in their name,
332 // cargo metadata does not do any normalization,
333 // so we do it ourselves currently
334 let name = CrateName::normalize_dashes(&name);
335 if to != from && crate_graph.add_dep(from, name, to).is_err() {
336 log::error!(
337 "cyclic dependency between targets of {}",
338 &cargo[pkg].name
339 )
340 }
341 }
342 for (name, krate) in public_deps.iter() {
343 if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) {
344 log::error!(
345 "cyclic dependency on {} for {}",
346 name,
347 &cargo[pkg].name
348 )
349 }
350 }
351 }
352 }
353
354 // Now add a dep edge from all targets of upstream to the lib
355 // target of downstream.
356 for pkg in cargo.packages() {
357 for dep in cargo[pkg].dependencies.iter() {
358 let name = CrateName::new(&dep.name).unwrap();
359 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
360 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
361 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
362 log::error!(
363 "cyclic dependency {} -> {}",
364 &cargo[pkg].name,
365 &cargo[dep.pkg].name
366 )
367 }
368 }
369 }
370 }
371 }
372
373 let mut rustc_pkg_crates = FxHashMap::default();
374
375 // If the user provided a path to rustc sources, we add all the rustc_private crates
376 // and create dependencies on them for the crates in the current workspace
377 if let Some(rustc_workspace) = rustc {
378 for pkg in rustc_workspace.packages() {
379 for &tgt in rustc_workspace[pkg].targets.iter() {
380 if rustc_workspace[tgt].kind != TargetKind::Lib {
381 continue;
382 }
383 // Exclude alloc / core / std
384 if rustc_workspace[tgt]
385 .root
386 .components()
387 .any(|c| c == Component::Normal("library".as_ref()))
388 {
389 continue;
390 }
391
392 if let Some(crate_id) = add_target_crate_root(
393 &mut crate_graph,
394 &rustc_workspace[pkg],
395 &rustc_workspace[tgt],
396 &cfg_options,
397 proc_macro_client,
398 load,
399 ) {
400 pkg_to_lib_crate.insert(pkg, crate_id);
401 // Add dependencies on the core / std / alloc for rustc
402 for (name, krate) in public_deps.iter() {
403 if let Err(_) =
404 crate_graph.add_dep(crate_id, name.clone(), *krate)
405 {
406 log::error!(
407 "cyclic dependency on {} for {}",
408 name,
409 &cargo[pkg].name
410 )
411 }
412 }
413 rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
414 }
415 }
416 }
417 // Now add a dep edge from all targets of upstream to the lib
418 // target of downstream.
419 for pkg in rustc_workspace.packages() {
420 for dep in rustc_workspace[pkg].dependencies.iter() {
421 let name = CrateName::new(&dep.name).unwrap();
422 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
423 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
424 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
425 log::error!(
426 "cyclic dependency {} -> {}",
427 &rustc_workspace[pkg].name,
428 &rustc_workspace[dep.pkg].name
429 )
430 }
431 }
432 }
433 }
434 }
435
436 // Add dependencies for all the crates of the current workspace to rustc_private libraries
437 for dep in rustc_workspace.packages() {
438 let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
439
440 if let Some(&to) = pkg_to_lib_crate.get(&dep) {
441 for pkg in cargo.packages() {
442 if !cargo[pkg].is_member {
443 continue;
444 }
445 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
446 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
447 log::error!(
448 "cyclic dependency {} -> {}",
449 &cargo[pkg].name,
450 &rustc_workspace[dep].name
451 )
452 }
453 }
454 }
455 }
456 }
457 }
458 }
459 }
460 if crate_graph.patch_cfg_if() {
461 log::debug!("Patched std to depend on cfg-if")
462 } else {
463 log::debug!("Did not patch std to depend on cfg-if")
464 }
465 crate_graph
466 }
467}
468
469fn add_target_crate_root(
470 crate_graph: &mut CrateGraph,
471 pkg: &cargo_workspace::PackageData,
472 tgt: &cargo_workspace::TargetData,
473 cfg_options: &CfgOptions,
474 proc_macro_client: &ProcMacroClient,
475 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
476) -> Option<CrateId> {
477 let root = tgt.root.as_path();
478 if let Some(file_id) = load(root) {
479 let edition = pkg.edition;
480 let cfg_options = {
481 let mut opts = cfg_options.clone();
482 for feature in pkg.features.iter() {
483 opts.insert_key_value("feature".into(), feature.into());
484 }
485 opts.extend(pkg.cfgs.iter().cloned());
486 opts
487 };
488 let mut env = Env::default();
489 if let Some(out_dir) = &pkg.out_dir {
490 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
491 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
492 env.set("OUT_DIR", out_dir);
493 }
494 }
495 let proc_macro = pkg
496 .proc_macro_dylib_path
497 .as_ref()
498 .map(|it| proc_macro_client.by_dylib_path(&it))
499 .unwrap_or_default();
500
501 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
502 let crate_id = crate_graph.add_crate_root(
503 file_id,
504 edition,
505 Some(display_name),
506 cfg_options,
507 env,
508 proc_macro.clone(),
509 );
510
511 return Some(crate_id);
512 }
513 None
514}
515fn sysroot_to_crate_graph(
516 crate_graph: &mut CrateGraph,
517 sysroot: &Sysroot,
518 target: Option<&str>,
519 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
520) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
521 let mut cfg_options = CfgOptions::default();
522 cfg_options.extend(get_rustc_cfg_options(target));
523 let sysroot_crates: FxHashMap<_, _> = sysroot
524 .crates()
525 .filter_map(|krate| {
526 let file_id = load(&sysroot[krate].root)?;
527
528 let env = Env::default();
529 let proc_macro = vec![];
530 let name = CrateName::new(&sysroot[krate].name)
531 .expect("Sysroot crates' names do not contain dashes");
532 let crate_id = crate_graph.add_crate_root(
533 file_id,
534 Edition::Edition2018,
535 Some(name.into()),
536 cfg_options.clone(),
537 env,
538 proc_macro,
539 );
540 Some((krate, crate_id))
541 })
542 .collect();
543
544 for from in sysroot.crates() {
545 for &to in sysroot[from].deps.iter() {
546 let name = CrateName::new(&sysroot[to].name).unwrap();
547 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
548 if let Err(_) = crate_graph.add_dep(from, name, to) {
549 log::error!("cyclic dependency between sysroot crates")
550 }
551 }
552 }
553 }
554
555 let public_deps = sysroot
556 .public_deps()
557 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
558 .collect::<Vec<_>>();
559
560 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
561 (public_deps, libproc_macro)
562}
563
564fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
565 let mut res = Vec::new();
566
567 // Some nightly-only cfgs, which are required for stdlib
568 res.push(CfgFlag::Atom("target_thread_local".into()));
569 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
570 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
571 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
572 }
573 }
574
575 let rustc_cfgs = {
576 let mut cmd = Command::new(toolchain::rustc());
577 cmd.args(&["--print", "cfg", "-O"]);
578 if let Some(target) = target {
579 cmd.args(&["--target", target]);
580 }
581 utf8_stdout(cmd)
582 };
583
584 match rustc_cfgs {
585 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
586 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
587 }
588
589 res
590}
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index ab1e2ab92..76526c66c 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -21,7 +21,6 @@ pub fn load_cargo(
21 let ws = ProjectWorkspace::load( 21 let ws = ProjectWorkspace::load(
22 root, 22 root,
23 &CargoConfig { load_out_dirs_from_check, ..Default::default() }, 23 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
24 true,
25 )?; 24 )?;
26 25
27 let (sender, receiver) = unbounded(); 26 let (sender, receiver) = unbounded();
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 74a021dbf..d16796590 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -49,7 +49,6 @@ pub struct Config {
49 pub hover: HoverConfig, 49 pub hover: HoverConfig,
50 pub semantic_tokens_refresh: bool, 50 pub semantic_tokens_refresh: bool,
51 51
52 pub with_sysroot: bool,
53 pub linked_projects: Vec<LinkedProject>, 52 pub linked_projects: Vec<LinkedProject>,
54 pub root_path: AbsPathBuf, 53 pub root_path: AbsPathBuf,
55} 54}
@@ -155,7 +154,6 @@ impl Config {
155 Config { 154 Config {
156 client_caps: ClientCapsConfig::default(), 155 client_caps: ClientCapsConfig::default(),
157 156
158 with_sysroot: true,
159 publish_diagnostics: true, 157 publish_diagnostics: true,
160 diagnostics: DiagnosticsConfig::default(), 158 diagnostics: DiagnosticsConfig::default(),
161 diagnostics_map: DiagnosticsMapConfig::default(), 159 diagnostics_map: DiagnosticsMapConfig::default(),
@@ -209,7 +207,6 @@ impl Config {
209 207
210 let data = ConfigData::from_json(json); 208 let data = ConfigData::from_json(json);
211 209
212 self.with_sysroot = data.withSysroot;
213 self.publish_diagnostics = data.diagnostics_enable; 210 self.publish_diagnostics = data.diagnostics_enable;
214 self.diagnostics = DiagnosticsConfig { 211 self.diagnostics = DiagnosticsConfig {
215 disable_experimental: !data.diagnostics_enableExperimental, 212 disable_experimental: !data.diagnostics_enableExperimental,
@@ -246,6 +243,7 @@ impl Config {
246 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck, 243 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
247 target: data.cargo_target.clone(), 244 target: data.cargo_target.clone(),
248 rustc_source: rustc_source, 245 rustc_source: rustc_source,
246 no_sysroot: data.cargo_noSysroot,
249 }; 247 };
250 self.runnables = RunnablesConfig { 248 self.runnables = RunnablesConfig {
251 override_cargo: data.runnables_overrideCargo, 249 override_cargo: data.runnables_overrideCargo,
@@ -398,13 +396,10 @@ impl Config {
398 } 396 }
399 397
400 if let Some(code_action) = &doc_caps.code_action { 398 if let Some(code_action) = &doc_caps.code_action {
401 match (code_action.data_support, &code_action.resolve_support) { 399 if let Some(resolve_support) = &code_action.resolve_support {
402 (Some(true), Some(resolve_support)) => { 400 if resolve_support.properties.iter().any(|it| it == "edit") {
403 if resolve_support.properties.iter().any(|it| it == "edit") { 401 self.client_caps.code_action_resolve = true;
404 self.client_caps.code_action_resolve = true;
405 }
406 } 402 }
407 _ => (),
408 } 403 }
409 } 404 }
410 } 405 }
@@ -495,6 +490,7 @@ config_data! {
495 cargo_loadOutDirsFromCheck: bool = false, 490 cargo_loadOutDirsFromCheck: bool = false,
496 cargo_noDefaultFeatures: bool = false, 491 cargo_noDefaultFeatures: bool = false,
497 cargo_target: Option<String> = None, 492 cargo_target: Option<String> = None,
493 cargo_noSysroot: bool = false,
498 494
499 checkOnSave_enable: bool = true, 495 checkOnSave_enable: bool = true,
500 checkOnSave_allFeatures: Option<bool> = None, 496 checkOnSave_allFeatures: Option<bool> = None,
@@ -547,7 +543,6 @@ config_data! {
547 rustfmt_extraArgs: Vec<String> = Vec::new(), 543 rustfmt_extraArgs: Vec<String> = Vec::new(),
548 rustfmt_overrideCommand: Option<Vec<String>> = None, 544 rustfmt_overrideCommand: Option<Vec<String>> = None,
549 545
550 withSysroot: bool = true,
551 rustcSource : Option<String> = None, 546 rustcSource : Option<String> = None,
552 } 547 }
553} 548}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 95659b0db..782797e85 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1322,6 +1322,28 @@ pub(crate) fn handle_open_docs(
1322 Ok(remote.and_then(|remote| Url::parse(&remote).ok())) 1322 Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1323} 1323}
1324 1324
1325pub(crate) fn handle_open_cargo_toml(
1326 snap: GlobalStateSnapshot,
1327 params: lsp_ext::OpenCargoTomlParams,
1328) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
1329 let _p = profile::span("handle_open_cargo_toml");
1330 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1331 let maybe_cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
1332 if maybe_cargo_spec.is_none() {
1333 return Ok(None);
1334 }
1335
1336 let cargo_spec = maybe_cargo_spec.unwrap();
1337 let cargo_toml_path = cargo_spec.workspace_root.join("Cargo.toml");
1338 if !cargo_toml_path.exists() {
1339 return Ok(None);
1340 }
1341 let cargo_toml_url = to_proto::url_from_abs_path(&cargo_toml_path);
1342 let cargo_toml_location = Location::new(cargo_toml_url, Range::default());
1343 let res = lsp_types::GotoDefinitionResponse::from(cargo_toml_location);
1344 Ok(Some(res))
1345}
1346
1325fn implementation_title(count: usize) -> String { 1347fn implementation_title(count: usize) -> String {
1326 if count == 1 { 1348 if count == 1 {
1327 "1 implementation".into() 1349 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index a7c3028e4..a5c65fa3e 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -354,3 +354,17 @@ impl Request for ExternalDocs {
354 type Result = Option<lsp_types::Url>; 354 type Result = Option<lsp_types::Url>;
355 const METHOD: &'static str = "experimental/externalDocs"; 355 const METHOD: &'static str = "experimental/externalDocs";
356} 356}
357
358pub enum OpenCargoToml {}
359
360impl Request for OpenCargoToml {
361 type Params = OpenCargoTomlParams;
362 type Result = Option<lsp_types::GotoDefinitionResponse>;
363 const METHOD: &'static str = "experimental/openCargoToml";
364}
365
366#[derive(Serialize, Deserialize, Debug)]
367#[serde(rename_all = "camelCase")]
368pub struct OpenCargoTomlParams {
369 pub text_document: TextDocumentIdentifier,
370}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 6e6cac42e..68a53bbcb 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -438,6 +438,7 @@ impl GlobalState {
438 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve) 438 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover) 439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) 440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
441 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
441 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) 442 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
442 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) 443 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
443 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) 444 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 11c8d0e5f..fa6e09f42 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -96,17 +96,12 @@ impl GlobalState {
96 self.task_pool.handle.spawn({ 96 self.task_pool.handle.spawn({
97 let linked_projects = self.config.linked_projects.clone(); 97 let linked_projects = self.config.linked_projects.clone();
98 let cargo_config = self.config.cargo.clone(); 98 let cargo_config = self.config.cargo.clone();
99 let with_sysroot = self.config.with_sysroot.clone();
100 move || { 99 move || {
101 let workspaces = linked_projects 100 let workspaces = linked_projects
102 .iter() 101 .iter()
103 .map(|project| match project { 102 .map(|project| match project {
104 LinkedProject::ProjectManifest(manifest) => { 103 LinkedProject::ProjectManifest(manifest) => {
105 project_model::ProjectWorkspace::load( 104 project_model::ProjectWorkspace::load(manifest.clone(), &cargo_config)
106 manifest.clone(),
107 &cargo_config,
108 with_sysroot,
109 )
110 } 105 }
111 LinkedProject::InlineJsonProject(it) => { 106 LinkedProject::InlineJsonProject(it) => {
112 project_model::ProjectWorkspace::load_inline(it.clone()) 107 project_model::ProjectWorkspace::load_inline(it.clone())
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index fe9362bc0..b210b98f0 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
@@ -12,7 +12,7 @@ use lsp_types::{
12 notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress, 12 notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress,
13}; 13};
14use lsp_types::{ProgressParams, ProgressParamsValue}; 14use lsp_types::{ProgressParams, ProgressParamsValue};
15use project_model::ProjectManifest; 15use project_model::{CargoConfig, ProjectManifest};
16use rust_analyzer::{ 16use rust_analyzer::{
17 config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject}, 17 config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject},
18 main_loop, 18 main_loop,
@@ -47,8 +47,8 @@ impl<'a> Project<'a> {
47 self 47 self
48 } 48 }
49 49
50 pub(crate) fn with_sysroot(mut self, sysroot: bool) -> Project<'a> { 50 pub(crate) fn with_sysroot(mut self, yes: bool) -> Project<'a> {
51 self.with_sysroot = sysroot; 51 self.with_sysroot = yes;
52 self 52 self
53 } 53 }
54 54
@@ -90,7 +90,7 @@ impl<'a> Project<'a> {
90 work_done_progress: true, 90 work_done_progress: true,
91 ..Default::default() 91 ..Default::default()
92 }, 92 },
93 with_sysroot: self.with_sysroot, 93 cargo: CargoConfig { no_sysroot: !self.with_sysroot, ..Default::default() },
94 linked_projects, 94 linked_projects,
95 files: FilesConfig { watcher: FilesWatcher::Client, exclude: Vec::new() }, 95 files: FilesConfig { watcher: FilesWatcher::Client, exclude: Vec::new() },
96 ..Config::new(tmp_dir_path) 96 ..Config::new(tmp_dir_path)