From aeda30e301d74e40fc1eb992fad581afb627126f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 13 Nov 2020 16:44:48 +0100 Subject: Move tricky workspace logic to a separate module --- crates/project_model/src/lib.rs | 606 +--------------------------------------- 1 file changed, 4 insertions(+), 602 deletions(-) (limited to 'crates/project_model/src/lib.rs') 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; mod project_json; mod sysroot; mod cfg_flag; +mod workspace; use std::{ - fmt, - fs::{self, read_dir, ReadDir}, + fs::{read_dir, ReadDir}, io, - path::Component, process::Command, }; use anyhow::{bail, Context, Result}; -use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId}; -use cfg::CfgOptions; use paths::{AbsPath, AbsPathBuf}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use crate::cfg_flag::CfgFlag; +use rustc_hash::FxHashSet; pub use crate::{ cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, project_json::{ProjectJson, ProjectJsonData}, sysroot::Sysroot, + workspace::{PackageRoot, ProjectWorkspace}, }; pub use proc_macro_api::ProcMacroClient; -#[derive(Clone, Eq, PartialEq)] -pub enum ProjectWorkspace { - /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. - Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option }, - /// Project workspace was manually specified using a `rust-project.json` file. - Json { project: ProjectJson, sysroot: Option }, -} - -impl fmt::Debug for ProjectWorkspace { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f - .debug_struct("Cargo") - .field("n_packages", &cargo.packages().len()) - .field("n_sysroot_crates", &sysroot.crates().len()) - .field( - "n_rustc_compiler_crates", - &rustc.as_ref().map_or(0, |rc| rc.packages().len()), - ) - .finish(), - ProjectWorkspace::Json { project, sysroot } => { - let mut debug_struct = f.debug_struct("Json"); - debug_struct.field("n_crates", &project.n_crates()); - if let Some(sysroot) = sysroot { - debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); - } - debug_struct.finish() - } - } - } -} - -/// `PackageRoot` describes a package root folder. -/// Which may be an external dependency, or a member of -/// the current workspace. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct PackageRoot { - /// Is a member of the current workspace - pub is_member: bool, - pub include: Vec, - pub exclude: Vec, -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum ProjectManifest { ProjectJson(AbsPathBuf), @@ -158,462 +111,6 @@ impl ProjectManifest { } } -impl ProjectWorkspace { - pub fn load( - manifest: ProjectManifest, - cargo_config: &CargoConfig, - with_sysroot: bool, - ) -> Result { - let res = match manifest { - ProjectManifest::ProjectJson(project_json) => { - let file = fs::read_to_string(&project_json).with_context(|| { - format!("Failed to read json file {}", project_json.display()) - })?; - let data = serde_json::from_str(&file).with_context(|| { - format!("Failed to deserialize json file {}", project_json.display()) - })?; - let project_location = project_json.parent().unwrap().to_path_buf(); - let project = ProjectJson::new(&project_location, data); - let sysroot = match &project.sysroot_src { - Some(path) => Some(Sysroot::load(path)?), - None => None, - }; - ProjectWorkspace::Json { project, sysroot } - } - ProjectManifest::CargoToml(cargo_toml) => { - let cargo_version = utf8_stdout({ - let mut cmd = Command::new(toolchain::cargo()); - cmd.arg("--version"); - cmd - })?; - - let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config) - .with_context(|| { - format!( - "Failed to read Cargo metadata from Cargo.toml file {}, {}", - cargo_toml.display(), - cargo_version - ) - })?; - let sysroot = if with_sysroot { - Sysroot::discover(&cargo_toml).with_context(|| { - format!( - "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", - cargo_toml.display() - ) - })? - } else { - Sysroot::default() - }; - - let rustc = if let Some(rustc_dir) = &cargo_config.rustc_source { - Some( - CargoWorkspace::from_cargo_metadata(&rustc_dir, cargo_config) - .with_context(|| { - format!("Failed to read Cargo metadata for Rust sources") - })?, - ) - } else { - None - }; - - ProjectWorkspace::Cargo { cargo, sysroot, rustc } - } - }; - - Ok(res) - } - - pub fn load_inline(project_json: ProjectJson) -> Result { - let sysroot = match &project_json.sysroot_src { - Some(path) => Some(Sysroot::load(path)?), - None => None, - }; - - Ok(ProjectWorkspace::Json { project: project_json, sysroot }) - } - - /// Returns the roots for the current `ProjectWorkspace` - /// The return type contains the path and whether or not - /// the root is a member of the current workspace - pub fn to_roots(&self) -> Vec { - match self { - ProjectWorkspace::Json { project, sysroot } => project - .crates() - .map(|(_, krate)| PackageRoot { - is_member: krate.is_workspace_member, - include: krate.include.clone(), - exclude: krate.exclude.clone(), - }) - .collect::>() - .into_iter() - .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| { - sysroot.crates().map(move |krate| PackageRoot { - is_member: false, - include: vec![sysroot[krate].root_dir().to_path_buf()], - exclude: Vec::new(), - }) - })) - .collect::>(), - ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { - let roots = cargo - .packages() - .map(|pkg| { - let is_member = cargo[pkg].is_member; - let pkg_root = cargo[pkg].root().to_path_buf(); - - let mut include = vec![pkg_root.clone()]; - include.extend(cargo[pkg].out_dir.clone()); - - let mut exclude = vec![pkg_root.join(".git")]; - if is_member { - exclude.push(pkg_root.join("target")); - } else { - exclude.push(pkg_root.join("tests")); - exclude.push(pkg_root.join("examples")); - exclude.push(pkg_root.join("benches")); - } - PackageRoot { is_member, include, exclude } - }) - .chain(sysroot.crates().map(|krate| PackageRoot { - is_member: false, - include: vec![sysroot[krate].root_dir().to_path_buf()], - exclude: Vec::new(), - })); - if let Some(rustc_packages) = rustc { - roots - .chain(rustc_packages.packages().map(|krate| PackageRoot { - is_member: false, - include: vec![rustc_packages[krate].root().to_path_buf()], - exclude: Vec::new(), - })) - .collect() - } else { - roots.collect() - } - } - } - } - - pub fn proc_macro_dylib_paths(&self) -> Vec { - match self { - ProjectWorkspace::Json { project, sysroot: _ } => project - .crates() - .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) - .cloned() - .collect(), - ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo - .packages() - .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) - .cloned() - .collect(), - } - } - - pub fn n_packages(&self) -> usize { - match self { - ProjectWorkspace::Json { project, .. } => project.n_crates(), - ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { - let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len()); - cargo.packages().len() + sysroot.crates().len() + rustc_package_len - } - } - } - - pub fn to_crate_graph( - &self, - target: Option<&str>, - proc_macro_client: &ProcMacroClient, - load: &mut dyn FnMut(&AbsPath) -> Option, - ) -> CrateGraph { - let mut crate_graph = CrateGraph::default(); - match self { - ProjectWorkspace::Json { project, sysroot } => { - let sysroot_dps = sysroot - .as_ref() - .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); - - let mut cfg_cache: FxHashMap, Vec> = FxHashMap::default(); - let crates: FxHashMap<_, _> = project - .crates() - .filter_map(|(crate_id, krate)| { - let file_path = &krate.root_module; - let file_id = match load(&file_path) { - Some(id) => id, - None => { - log::error!("failed to load crate root {}", file_path.display()); - return None; - } - }; - - let env = krate.env.clone().into_iter().collect(); - let proc_macro = krate - .proc_macro_dylib_path - .clone() - .map(|it| proc_macro_client.by_dylib_path(&it)); - - let target = krate.target.as_deref().or(target); - let target_cfgs = cfg_cache - .entry(target) - .or_insert_with(|| get_rustc_cfg_options(target)); - - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned()); - - Some(( - crate_id, - crate_graph.add_crate_root( - file_id, - krate.edition, - krate.display_name.clone(), - cfg_options, - env, - proc_macro.unwrap_or_default(), - ), - )) - }) - .collect(); - - for (from, krate) in project.crates() { - if let Some(&from) = crates.get(&from) { - if let Some((public_deps, _proc_macro)) = &sysroot_dps { - for (name, to) in public_deps.iter() { - if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) { - log::error!("cyclic dependency on {} for {:?}", name, from) - } - } - } - - for dep in &krate.deps { - let to_crate_id = dep.crate_id; - if let Some(&to) = crates.get(&to_crate_id) { - if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) { - log::error!("cyclic dependency {:?} -> {:?}", from, to); - } - } - } - } - } - } - ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { - let (public_deps, libproc_macro) = - sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load); - - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(get_rustc_cfg_options(target)); - - let mut pkg_to_lib_crate = FxHashMap::default(); - - // Add test cfg for non-sysroot crates - cfg_options.insert_atom("test".into()); - cfg_options.insert_atom("debug_assertions".into()); - - let mut pkg_crates = FxHashMap::default(); - - // Next, create crates for each package, target pair - for pkg in cargo.packages() { - let mut lib_tgt = None; - for &tgt in cargo[pkg].targets.iter() { - if let Some(crate_id) = add_target_crate_root( - &mut crate_graph, - &cargo[pkg], - &cargo[tgt], - &cfg_options, - proc_macro_client, - load, - ) { - if cargo[tgt].kind == TargetKind::Lib { - lib_tgt = Some((crate_id, cargo[tgt].name.clone())); - pkg_to_lib_crate.insert(pkg, crate_id); - } - if cargo[tgt].is_proc_macro { - if let Some(proc_macro) = libproc_macro { - if let Err(_) = crate_graph.add_dep( - crate_id, - CrateName::new("proc_macro").unwrap(), - proc_macro, - ) { - log::error!( - "cyclic dependency on proc_macro for {}", - &cargo[pkg].name - ) - } - } - } - - pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); - } - } - - // Set deps to the core, std and to the lib target of the current package - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if let Some((to, name)) = lib_tgt.clone() { - // For root projects with dashes in their name, - // cargo metadata does not do any normalization, - // so we do it ourselves currently - let name = CrateName::normalize_dashes(&name); - if to != from && crate_graph.add_dep(from, name, to).is_err() { - log::error!( - "cyclic dependency between targets of {}", - &cargo[pkg].name - ) - } - } - for (name, krate) in public_deps.iter() { - if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) { - log::error!( - "cyclic dependency on {} for {}", - name, - &cargo[pkg].name - ) - } - } - } - } - - // Now add a dep edge from all targets of upstream to the lib - // target of downstream. - for pkg in cargo.packages() { - for dep in cargo[pkg].dependencies.iter() { - let name = CrateName::new(&dep.name).unwrap(); - if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { - log::error!( - "cyclic dependency {} -> {}", - &cargo[pkg].name, - &cargo[dep.pkg].name - ) - } - } - } - } - } - - let mut rustc_pkg_crates = FxHashMap::default(); - - // If the user provided a path to rustc sources, we add all the rustc_private crates - // and create dependencies on them for the crates in the current workspace - if let Some(rustc_workspace) = rustc { - for pkg in rustc_workspace.packages() { - for &tgt in rustc_workspace[pkg].targets.iter() { - if rustc_workspace[tgt].kind != TargetKind::Lib { - continue; - } - // Exclude alloc / core / std - if rustc_workspace[tgt] - .root - .components() - .any(|c| c == Component::Normal("library".as_ref())) - { - continue; - } - - if let Some(crate_id) = add_target_crate_root( - &mut crate_graph, - &rustc_workspace[pkg], - &rustc_workspace[tgt], - &cfg_options, - proc_macro_client, - load, - ) { - pkg_to_lib_crate.insert(pkg, crate_id); - // Add dependencies on the core / std / alloc for rustc - for (name, krate) in public_deps.iter() { - if let Err(_) = - crate_graph.add_dep(crate_id, name.clone(), *krate) - { - log::error!( - "cyclic dependency on {} for {}", - name, - &cargo[pkg].name - ) - } - } - rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); - } - } - } - // Now add a dep edge from all targets of upstream to the lib - // target of downstream. - for pkg in rustc_workspace.packages() { - for dep in rustc_workspace[pkg].dependencies.iter() { - let name = CrateName::new(&dep.name).unwrap(); - if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { - for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() { - if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { - log::error!( - "cyclic dependency {} -> {}", - &rustc_workspace[pkg].name, - &rustc_workspace[dep.pkg].name - ) - } - } - } - } - } - - // Add dependencies for all the crates of the current workspace to rustc_private libraries - for dep in rustc_workspace.packages() { - let name = CrateName::normalize_dashes(&rustc_workspace[dep].name); - - if let Some(&to) = pkg_to_lib_crate.get(&dep) { - for pkg in cargo.packages() { - if !cargo[pkg].is_member { - continue; - } - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { - log::error!( - "cyclic dependency {} -> {}", - &cargo[pkg].name, - &rustc_workspace[dep].name - ) - } - } - } - } - } - } - } - } - if crate_graph.patch_cfg_if() { - log::debug!("Patched std to depend on cfg-if") - } else { - log::debug!("Did not patch std to depend on cfg-if") - } - crate_graph - } -} - -fn get_rustc_cfg_options(target: Option<&str>) -> Vec { - let mut res = Vec::new(); - - // Some nightly-only cfgs, which are required for stdlib - res.push(CfgFlag::Atom("target_thread_local".into())); - for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() { - for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() { - res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() }); - } - } - - let rustc_cfgs = { - let mut cmd = Command::new(toolchain::rustc()); - cmd.args(&["--print", "cfg", "-O"]); - if let Some(target) = target { - cmd.args(&["--target", target]); - } - utf8_stdout(cmd) - }; - - match rustc_cfgs { - Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())), - Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), - } - - res -} - fn utf8_stdout(mut cmd: Command) -> Result { let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; if !output.status.success() { @@ -627,98 +124,3 @@ fn utf8_stdout(mut cmd: Command) -> Result { let stdout = String::from_utf8(output.stdout)?; Ok(stdout.trim().to_string()) } - -fn add_target_crate_root( - crate_graph: &mut CrateGraph, - pkg: &cargo_workspace::PackageData, - tgt: &cargo_workspace::TargetData, - cfg_options: &CfgOptions, - proc_macro_client: &ProcMacroClient, - load: &mut dyn FnMut(&AbsPath) -> Option, -) -> Option { - let root = tgt.root.as_path(); - if let Some(file_id) = load(root) { - let edition = pkg.edition; - let cfg_options = { - let mut opts = cfg_options.clone(); - for feature in pkg.features.iter() { - opts.insert_key_value("feature".into(), feature.into()); - } - opts.extend(pkg.cfgs.iter().cloned()); - opts - }; - let mut env = Env::default(); - if let Some(out_dir) = &pkg.out_dir { - // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() - if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { - env.set("OUT_DIR", out_dir); - } - } - let proc_macro = pkg - .proc_macro_dylib_path - .as_ref() - .map(|it| proc_macro_client.by_dylib_path(&it)) - .unwrap_or_default(); - - let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone()); - let crate_id = crate_graph.add_crate_root( - file_id, - edition, - Some(display_name), - cfg_options, - env, - proc_macro.clone(), - ); - - return Some(crate_id); - } - None -} -fn sysroot_to_crate_graph( - crate_graph: &mut CrateGraph, - sysroot: &Sysroot, - target: Option<&str>, - load: &mut dyn FnMut(&AbsPath) -> Option, -) -> (Vec<(CrateName, CrateId)>, Option) { - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(get_rustc_cfg_options(target)); - let sysroot_crates: FxHashMap<_, _> = sysroot - .crates() - .filter_map(|krate| { - let file_id = load(&sysroot[krate].root)?; - - let env = Env::default(); - let proc_macro = vec![]; - let name = CrateName::new(&sysroot[krate].name) - .expect("Sysroot crates' names do not contain dashes"); - let crate_id = crate_graph.add_crate_root( - file_id, - Edition::Edition2018, - Some(name.into()), - cfg_options.clone(), - env, - proc_macro, - ); - Some((krate, crate_id)) - }) - .collect(); - - for from in sysroot.crates() { - for &to in sysroot[from].deps.iter() { - let name = CrateName::new(&sysroot[to].name).unwrap(); - if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) { - if let Err(_) = crate_graph.add_dep(from, name, to) { - log::error!("cyclic dependency between sysroot crates") - } - } - } - } - - let public_deps = sysroot - .public_deps() - .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx])) - .collect::>(); - - let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied()); - (public_deps, libproc_macro) -} -- cgit v1.2.3