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