diff options
Diffstat (limited to 'crates/project_model/src/cargo_workspace.rs')
-rw-r--r-- | crates/project_model/src/cargo_workspace.rs | 208 |
1 files changed, 19 insertions, 189 deletions
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs index c0ed37fc1..c8a5333c4 100644 --- a/crates/project_model/src/cargo_workspace.rs +++ b/crates/project_model/src/cargo_workspace.rs | |||
@@ -1,24 +1,15 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use std::{ | 3 | use std::{convert::TryInto, ops, process::Command}; |
4 | convert::TryInto, | ||
5 | ffi::OsStr, | ||
6 | io::BufReader, | ||
7 | ops, | ||
8 | path::{Path, PathBuf}, | ||
9 | process::{Command, Stdio}, | ||
10 | }; | ||
11 | 4 | ||
12 | use anyhow::{Context, Result}; | 5 | use anyhow::{Context, Result}; |
13 | use base_db::Edition; | 6 | use base_db::Edition; |
14 | use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; | 7 | use cargo_metadata::{CargoOpt, MetadataCommand}; |
15 | use itertools::Itertools; | ||
16 | use la_arena::{Arena, Idx}; | 8 | use la_arena::{Arena, Idx}; |
17 | use paths::{AbsPath, AbsPathBuf}; | 9 | use paths::{AbsPath, AbsPathBuf}; |
18 | use rustc_hash::FxHashMap; | 10 | use rustc_hash::FxHashMap; |
19 | use stdx::JodChild; | ||
20 | 11 | ||
21 | use crate::cfg_flag::CfgFlag; | 12 | use crate::build_data::{BuildData, BuildDataMap}; |
22 | use crate::utf8_stdout; | 13 | use crate::utf8_stdout; |
23 | 14 | ||
24 | /// `CargoWorkspace` represents the logical structure of, well, a Cargo | 15 | /// `CargoWorkspace` represents the logical structure of, well, a Cargo |
@@ -99,19 +90,12 @@ pub struct PackageData { | |||
99 | pub dependencies: Vec<PackageDependency>, | 90 | pub dependencies: Vec<PackageDependency>, |
100 | /// Rust edition for this package | 91 | /// Rust edition for this package |
101 | pub edition: Edition, | 92 | pub edition: Edition, |
102 | /// List of features to activate | 93 | /// Features provided by the crate, mapped to the features required by that feature. |
103 | pub features: Vec<String>, | 94 | pub features: FxHashMap<String, Vec<String>>, |
104 | /// List of config flags defined by this package's build script | 95 | /// List of features enabled on this package |
105 | pub cfgs: Vec<CfgFlag>, | 96 | pub active_features: Vec<String>, |
106 | /// List of cargo-related environment variables with their value | 97 | /// Build script related data for this package |
107 | /// | 98 | pub build_data: BuildData, |
108 | /// If the package has a build script which defines environment variables, | ||
109 | /// they can also be found here. | ||
110 | pub envs: Vec<(String, String)>, | ||
111 | /// Directory where a build script might place its output | ||
112 | pub out_dir: Option<AbsPathBuf>, | ||
113 | /// Path to the proc-macro library file if this package exposes proc-macros | ||
114 | pub proc_macro_dylib_path: Option<AbsPathBuf>, | ||
115 | } | 99 | } |
116 | 100 | ||
117 | #[derive(Debug, Clone, Eq, PartialEq)] | 101 | #[derive(Debug, Clone, Eq, PartialEq)] |
@@ -244,17 +228,11 @@ impl CargoWorkspace { | |||
244 | ) | 228 | ) |
245 | })?; | 229 | })?; |
246 | 230 | ||
247 | let mut out_dir_by_id = FxHashMap::default(); | 231 | let resources = if config.load_out_dirs_from_check { |
248 | let mut cfgs = FxHashMap::default(); | 232 | BuildDataMap::new(cargo_toml, config, &meta.packages, progress)? |
249 | let mut envs = FxHashMap::default(); | 233 | } else { |
250 | let mut proc_macro_dylib_paths = FxHashMap::default(); | 234 | BuildDataMap::with_cargo_env(&meta.packages) |
251 | if config.load_out_dirs_from_check { | 235 | }; |
252 | let resources = load_extern_resources(cargo_toml, config, progress)?; | ||
253 | out_dir_by_id = resources.out_dirs; | ||
254 | cfgs = resources.cfgs; | ||
255 | envs = resources.env; | ||
256 | proc_macro_dylib_paths = resources.proc_dylib_paths; | ||
257 | } | ||
258 | 236 | ||
259 | let mut pkg_by_id = FxHashMap::default(); | 237 | let mut pkg_by_id = FxHashMap::default(); |
260 | let mut packages = Arena::default(); | 238 | let mut packages = Arena::default(); |
@@ -265,7 +243,7 @@ impl CargoWorkspace { | |||
265 | meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); | 243 | meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); |
266 | for meta_pkg in meta.packages { | 244 | for meta_pkg in meta.packages { |
267 | let id = meta_pkg.id.clone(); | 245 | let id = meta_pkg.id.clone(); |
268 | inject_cargo_env(&meta_pkg, envs.entry(id).or_default()); | 246 | let build_data = resources.get(&id).cloned().unwrap_or_default(); |
269 | 247 | ||
270 | let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = | 248 | let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = |
271 | meta_pkg; | 249 | meta_pkg; |
@@ -281,11 +259,9 @@ impl CargoWorkspace { | |||
281 | is_member, | 259 | is_member, |
282 | edition, | 260 | edition, |
283 | dependencies: Vec::new(), | 261 | dependencies: Vec::new(), |
284 | features: Vec::new(), | 262 | features: meta_pkg.features.into_iter().collect(), |
285 | cfgs: cfgs.get(&id).cloned().unwrap_or_default(), | 263 | active_features: Vec::new(), |
286 | envs: envs.get(&id).cloned().unwrap_or_default(), | 264 | build_data, |
287 | out_dir: out_dir_by_id.get(&id).cloned(), | ||
288 | proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), | ||
289 | }); | 265 | }); |
290 | let pkg_data = &mut packages[pkg]; | 266 | let pkg_data = &mut packages[pkg]; |
291 | pkg_by_id.insert(id, pkg); | 267 | pkg_by_id.insert(id, pkg); |
@@ -328,7 +304,7 @@ impl CargoWorkspace { | |||
328 | let dep = PackageDependency { name: dep_node.name, pkg }; | 304 | let dep = PackageDependency { name: dep_node.name, pkg }; |
329 | packages[source].dependencies.push(dep); | 305 | packages[source].dependencies.push(dep); |
330 | } | 306 | } |
331 | packages[source].features.extend(node.features); | 307 | packages[source].active_features.extend(node.features); |
332 | } | 308 | } |
333 | 309 | ||
334 | let workspace_root = AbsPathBuf::assert(meta.workspace_root); | 310 | let workspace_root = AbsPathBuf::assert(meta.workspace_root); |
@@ -362,149 +338,3 @@ impl CargoWorkspace { | |||
362 | self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 | 338 | self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 |
363 | } | 339 | } |
364 | } | 340 | } |
365 | |||
366 | #[derive(Debug, Clone, Default)] | ||
367 | pub(crate) struct ExternResources { | ||
368 | out_dirs: FxHashMap<PackageId, AbsPathBuf>, | ||
369 | proc_dylib_paths: FxHashMap<PackageId, AbsPathBuf>, | ||
370 | cfgs: FxHashMap<PackageId, Vec<CfgFlag>>, | ||
371 | env: FxHashMap<PackageId, Vec<(String, String)>>, | ||
372 | } | ||
373 | |||
374 | pub(crate) fn load_extern_resources( | ||
375 | cargo_toml: &Path, | ||
376 | cargo_features: &CargoConfig, | ||
377 | progress: &dyn Fn(String), | ||
378 | ) -> Result<ExternResources> { | ||
379 | let mut cmd = Command::new(toolchain::cargo()); | ||
380 | cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]).arg(cargo_toml); | ||
381 | |||
382 | // --all-targets includes tests, benches and examples in addition to the | ||
383 | // default lib and bins. This is an independent concept from the --targets | ||
384 | // flag below. | ||
385 | cmd.arg("--all-targets"); | ||
386 | |||
387 | if let Some(target) = &cargo_features.target { | ||
388 | cmd.args(&["--target", target]); | ||
389 | } | ||
390 | |||
391 | if cargo_features.all_features { | ||
392 | cmd.arg("--all-features"); | ||
393 | } else { | ||
394 | if cargo_features.no_default_features { | ||
395 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` | ||
396 | // https://github.com/oli-obk/cargo_metadata/issues/79 | ||
397 | cmd.arg("--no-default-features"); | ||
398 | } | ||
399 | if !cargo_features.features.is_empty() { | ||
400 | cmd.arg("--features"); | ||
401 | cmd.arg(cargo_features.features.join(" ")); | ||
402 | } | ||
403 | } | ||
404 | |||
405 | cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); | ||
406 | |||
407 | let mut child = cmd.spawn().map(JodChild)?; | ||
408 | let child_stdout = child.stdout.take().unwrap(); | ||
409 | let stdout = BufReader::new(child_stdout); | ||
410 | |||
411 | let mut res = ExternResources::default(); | ||
412 | for message in cargo_metadata::Message::parse_stream(stdout) { | ||
413 | if let Ok(message) = message { | ||
414 | match message { | ||
415 | Message::BuildScriptExecuted(BuildScript { | ||
416 | package_id, | ||
417 | out_dir, | ||
418 | cfgs, | ||
419 | env, | ||
420 | .. | ||
421 | }) => { | ||
422 | let cfgs = { | ||
423 | let mut acc = Vec::new(); | ||
424 | for cfg in cfgs { | ||
425 | match cfg.parse::<CfgFlag>() { | ||
426 | Ok(it) => acc.push(it), | ||
427 | Err(err) => { | ||
428 | anyhow::bail!("invalid cfg from cargo-metadata: {}", err) | ||
429 | } | ||
430 | }; | ||
431 | } | ||
432 | acc | ||
433 | }; | ||
434 | // cargo_metadata crate returns default (empty) path for | ||
435 | // older cargos, which is not absolute, so work around that. | ||
436 | if out_dir != PathBuf::default() { | ||
437 | let out_dir = AbsPathBuf::assert(out_dir); | ||
438 | res.out_dirs.insert(package_id.clone(), out_dir); | ||
439 | res.cfgs.insert(package_id.clone(), cfgs); | ||
440 | } | ||
441 | |||
442 | res.env.insert(package_id, env); | ||
443 | } | ||
444 | Message::CompilerArtifact(message) => { | ||
445 | progress(format!("metadata {}", message.target.name)); | ||
446 | |||
447 | if message.target.kind.contains(&"proc-macro".to_string()) { | ||
448 | let package_id = message.package_id; | ||
449 | // Skip rmeta file | ||
450 | if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) | ||
451 | { | ||
452 | let filename = AbsPathBuf::assert(filename.clone()); | ||
453 | res.proc_dylib_paths.insert(package_id, filename); | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | Message::CompilerMessage(message) => { | ||
458 | progress(message.target.name.clone()); | ||
459 | } | ||
460 | Message::Unknown => (), | ||
461 | Message::BuildFinished(_) => {} | ||
462 | Message::TextLine(_) => {} | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | Ok(res) | ||
467 | } | ||
468 | |||
469 | // FIXME: File a better way to know if it is a dylib | ||
470 | fn is_dylib(path: &Path) -> bool { | ||
471 | match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) { | ||
472 | None => false, | ||
473 | Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), | ||
474 | } | ||
475 | } | ||
476 | |||
477 | /// Recreates the compile-time environment variables that Cargo sets. | ||
478 | /// | ||
479 | /// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates> | ||
480 | fn inject_cargo_env(package: &cargo_metadata::Package, env: &mut Vec<(String, String)>) { | ||
481 | // FIXME: Missing variables: | ||
482 | // CARGO, CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name> | ||
483 | |||
484 | let mut manifest_dir = package.manifest_path.clone(); | ||
485 | manifest_dir.pop(); | ||
486 | if let Some(cargo_manifest_dir) = manifest_dir.to_str() { | ||
487 | env.push(("CARGO_MANIFEST_DIR".into(), cargo_manifest_dir.into())); | ||
488 | } | ||
489 | |||
490 | env.push(("CARGO_PKG_VERSION".into(), package.version.to_string())); | ||
491 | env.push(("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string())); | ||
492 | env.push(("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string())); | ||
493 | env.push(("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string())); | ||
494 | |||
495 | let pre = package.version.pre.iter().map(|id| id.to_string()).format("."); | ||
496 | env.push(("CARGO_PKG_VERSION_PRE".into(), pre.to_string())); | ||
497 | |||
498 | let authors = package.authors.join(";"); | ||
499 | env.push(("CARGO_PKG_AUTHORS".into(), authors)); | ||
500 | |||
501 | env.push(("CARGO_PKG_NAME".into(), package.name.clone())); | ||
502 | env.push(("CARGO_PKG_DESCRIPTION".into(), package.description.clone().unwrap_or_default())); | ||
503 | //env.push(("CARGO_PKG_HOMEPAGE".into(), package.homepage.clone().unwrap_or_default())); | ||
504 | env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default())); | ||
505 | env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default())); | ||
506 | |||
507 | let license_file = | ||
508 | package.license_file.as_ref().map(|buf| buf.display().to_string()).unwrap_or_default(); | ||
509 | env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file)); | ||
510 | } | ||