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