Avoid blocking the main loop when editing Cargo.toml

I've noticed a bunch of "main loop too long" warnings in console when
typing in Cargo.toml. Profiling showed that the culprit is `rustc
--print cfg` call.

I moved it to the background project loading phase, where it belongs.
This highlighted a problem: we generally use single `cfg`, while it
really should be per crate.
This commit is contained in:
Aleksey Kladov 2021-01-18 14:52:12 +03:00
parent cc963d2b11
commit add87f5424
5 changed files with 100 additions and 71 deletions

View File

@ -5,6 +5,7 @@
mod project_json; mod project_json;
mod sysroot; mod sysroot;
mod workspace; mod workspace;
mod rustc_cfg;
use std::{ use std::{
fs::{read_dir, ReadDir}, fs::{read_dir, ReadDir},

View File

@ -0,0 +1,34 @@
//! Runs `rustc --print cfg` to get built-in cfg flags.
use std::process::Command;
use crate::{cfg_flag::CfgFlag, utf8_stdout};
pub(crate) fn get(target: Option<&str>) -> Vec<CfgFlag> {
let _p = profile::span("rustc_cfg::get");
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
}

View File

@ -16,7 +16,7 @@
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use crate::{ use crate::{
cargo_workspace, cfg_flag::CfgFlag, sysroot::SysrootCrate, utf8_stdout, CargoConfig, cargo_workspace, cfg_flag::CfgFlag, rustc_cfg, sysroot::SysrootCrate, utf8_stdout, CargoConfig,
CargoWorkspace, ProjectJson, ProjectManifest, Sysroot, TargetKind, CargoWorkspace, ProjectJson, ProjectManifest, Sysroot, TargetKind,
}; };
@ -34,15 +34,25 @@ pub struct PackageRoot {
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub enum ProjectWorkspace { pub enum ProjectWorkspace {
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> }, Cargo {
cargo: CargoWorkspace,
sysroot: Sysroot,
rustc: Option<CargoWorkspace>,
/// Holds cfg flags for the current target. We get those by running
/// `rustc --print cfg`.
///
/// FIXME: make this a per-crate map, as, eg, build.rs might have a
/// different target.
rustc_cfg: Vec<CfgFlag>,
},
/// Project workspace was manually specified using a `rust-project.json` file. /// Project workspace was manually specified using a `rust-project.json` file.
Json { project: ProjectJson, sysroot: Option<Sysroot> }, Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
} }
impl fmt::Debug for ProjectWorkspace { impl fmt::Debug for ProjectWorkspace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f
.debug_struct("Cargo") .debug_struct("Cargo")
.field("n_packages", &cargo.packages().len()) .field("n_packages", &cargo.packages().len())
.field("n_sysroot_crates", &sysroot.crates().len()) .field("n_sysroot_crates", &sysroot.crates().len())
@ -50,13 +60,15 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"n_rustc_compiler_crates", "n_rustc_compiler_crates",
&rustc.as_ref().map_or(0, |rc| rc.packages().len()), &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
) )
.field("rustc_cfg", rustc_cfg)
.finish(), .finish(),
ProjectWorkspace::Json { project, sysroot } => { ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
let mut debug_struct = f.debug_struct("Json"); let mut debug_struct = f.debug_struct("Json");
debug_struct.field("n_crates", &project.n_crates()); debug_struct.field("n_crates", &project.n_crates());
if let Some(sysroot) = sysroot { if let Some(sysroot) = sysroot {
debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
} }
debug_struct.field("rustc_cfg", rustc_cfg);
debug_struct.finish() debug_struct.finish()
} }
} }
@ -79,7 +91,7 @@ pub fn load(
})?; })?;
let project_location = project_json.parent().unwrap().to_path_buf(); let project_location = project_json.parent().unwrap().to_path_buf();
let project_json = ProjectJson::new(&project_location, data); let project_json = ProjectJson::new(&project_location, data);
ProjectWorkspace::load_inline(project_json)? ProjectWorkspace::load_inline(project_json, config.target.as_deref())?
} }
ProjectManifest::CargoToml(cargo_toml) => { ProjectManifest::CargoToml(cargo_toml) => {
let cargo_version = utf8_stdout({ let cargo_version = utf8_stdout({
@ -117,21 +129,24 @@ pub fn load(
} else { } else {
None None
}; };
let rustc_cfg = rustc_cfg::get(config.target.as_deref());
ProjectWorkspace::Cargo { cargo, sysroot, rustc } ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg }
} }
}; };
Ok(res) Ok(res)
} }
pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> { pub fn load_inline(
project_json: ProjectJson,
target: Option<&str>,
) -> Result<ProjectWorkspace> {
let sysroot = match &project_json.sysroot_src { let sysroot = match &project_json.sysroot_src {
Some(path) => Some(Sysroot::load(path)?), Some(path) => Some(Sysroot::load(path)?),
None => None, None => None,
}; };
let rustc_cfg = rustc_cfg::get(target);
Ok(ProjectWorkspace::Json { project: project_json, sysroot }) Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
} }
/// Returns the roots for the current `ProjectWorkspace` /// Returns the roots for the current `ProjectWorkspace`
@ -139,7 +154,7 @@ pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
/// the root is a member of the current workspace /// the root is a member of the current workspace
pub fn to_roots(&self) -> Vec<PackageRoot> { pub fn to_roots(&self) -> Vec<PackageRoot> {
match self { match self {
ProjectWorkspace::Json { project, sysroot } => project ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
.crates() .crates()
.map(|(_, krate)| PackageRoot { .map(|(_, krate)| PackageRoot {
is_member: krate.is_workspace_member, is_member: krate.is_workspace_member,
@ -156,7 +171,7 @@ pub fn to_roots(&self) -> Vec<PackageRoot> {
}) })
})) }))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => cargo ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _ } => cargo
.packages() .packages()
.map(|pkg| { .map(|pkg| {
let is_member = cargo[pkg].is_member; let is_member = cargo[pkg].is_member;
@ -194,7 +209,7 @@ pub fn to_roots(&self) -> Vec<PackageRoot> {
pub fn n_packages(&self) -> usize { pub fn n_packages(&self) -> usize {
match self { match self {
ProjectWorkspace::Json { project, .. } => project.n_crates(), ProjectWorkspace::Json { project, .. } => project.n_crates(),
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len()); let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
cargo.packages().len() + sysroot.crates().len() + rustc_package_len cargo.packages().len() + sysroot.crates().len() + rustc_package_len
} }
@ -203,7 +218,6 @@ pub fn n_packages(&self) -> usize {
pub fn to_crate_graph( pub fn to_crate_graph(
&self, &self,
target: Option<&str>,
proc_macro_client: Option<&ProcMacroClient>, proc_macro_client: Option<&ProcMacroClient>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
) -> CrateGraph { ) -> CrateGraph {
@ -214,12 +228,21 @@ pub fn to_crate_graph(
}; };
let mut crate_graph = match self { let mut crate_graph = match self {
ProjectWorkspace::Json { project, sysroot } => { ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
project_json_to_crate_graph(target, &proc_macro_loader, load, project, sysroot) rustc_cfg.clone(),
} &proc_macro_loader,
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { load,
cargo_to_crate_graph(target, &proc_macro_loader, load, cargo, sysroot, rustc) project,
} sysroot,
),
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => cargo_to_crate_graph(
rustc_cfg.clone(),
&proc_macro_loader,
load,
cargo,
sysroot,
rustc,
),
}; };
if crate_graph.patch_cfg_if() { if crate_graph.patch_cfg_if() {
log::debug!("Patched std to depend on cfg-if") log::debug!("Patched std to depend on cfg-if")
@ -231,7 +254,7 @@ pub fn to_crate_graph(
} }
fn project_json_to_crate_graph( fn project_json_to_crate_graph(
target: Option<&str>, rustc_cfg: Vec<CfgFlag>,
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
project: &ProjectJson, project: &ProjectJson,
@ -240,9 +263,9 @@ fn project_json_to_crate_graph(
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
let sysroot_deps = sysroot let sysroot_deps = sysroot
.as_ref() .as_ref()
.map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load));
let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default(); let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
let crates: FxHashMap<CrateId, CrateId> = project let crates: FxHashMap<CrateId, CrateId> = project
.crates() .crates()
.filter_map(|(crate_id, krate)| { .filter_map(|(crate_id, krate)| {
@ -254,9 +277,12 @@ fn project_json_to_crate_graph(
let env = krate.env.clone().into_iter().collect(); let env = krate.env.clone().into_iter().collect();
let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| proc_macro_loader(&it)); let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| proc_macro_loader(&it));
let target = krate.target.as_deref().or(target); let target_cfgs = match krate.target.as_deref() {
let target_cfgs = Some(target) => {
cfg_cache.entry(target).or_insert_with(|| get_rustc_cfg_options(target)); cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(Some(target)))
}
None => &rustc_cfg,
};
let mut cfg_options = CfgOptions::default(); let mut cfg_options = CfgOptions::default();
cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned()); cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
@ -293,7 +319,7 @@ fn project_json_to_crate_graph(
} }
fn cargo_to_crate_graph( fn cargo_to_crate_graph(
target: Option<&str>, rustc_cfg: Vec<CfgFlag>,
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
cargo: &CargoWorkspace, cargo: &CargoWorkspace,
@ -303,10 +329,10 @@ fn cargo_to_crate_graph(
let _p = profile::span("cargo_to_crate_graph"); let _p = profile::span("cargo_to_crate_graph");
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
let (public_deps, libproc_macro) = let (public_deps, libproc_macro) =
sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load); sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
let mut cfg_options = CfgOptions::default(); let mut cfg_options = CfgOptions::default();
cfg_options.extend(get_rustc_cfg_options(target)); cfg_options.extend(rustc_cfg);
let mut pkg_to_lib_crate = FxHashMap::default(); let mut pkg_to_lib_crate = FxHashMap::default();
@ -492,12 +518,12 @@ fn add_target_crate_root(
fn sysroot_to_crate_graph( fn sysroot_to_crate_graph(
crate_graph: &mut CrateGraph, crate_graph: &mut CrateGraph,
sysroot: &Sysroot, sysroot: &Sysroot,
target: Option<&str>, rustc_cfg: Vec<CfgFlag>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) { ) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
let _p = profile::span("sysroot_to_crate_graph"); let _p = profile::span("sysroot_to_crate_graph");
let mut cfg_options = CfgOptions::default(); let mut cfg_options = CfgOptions::default();
cfg_options.extend(get_rustc_cfg_options(target)); cfg_options.extend(rustc_cfg);
let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
.crates() .crates()
.filter_map(|krate| { .filter_map(|krate| {
@ -536,35 +562,6 @@ fn sysroot_to_crate_graph(
(public_deps, libproc_macro) (public_deps, libproc_macro)
} }
fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
let _p = profile::span("get_rustc_cfg_options");
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 add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) { fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
if let Err(err) = graph.add_dep(from, name, to) { if let Err(err) = graph.add_dep(from, name, to) {
log::error!("{}", err) log::error!("{}", err)

View File

@ -39,7 +39,7 @@ pub fn load_cargo(
None None
}; };
let crate_graph = ws.to_crate_graph(None, proc_macro_client.as_ref(), &mut |path: &AbsPath| { let crate_graph = ws.to_crate_graph(proc_macro_client.as_ref(), &mut |path: &AbsPath| {
let contents = loader.load_sync(path); let contents = loader.load_sync(path);
let path = vfs::VfsPath::from(path.to_path_buf()); let path = vfs::VfsPath::from(path.to_path_buf());
vfs.set_file_contents(path.clone(), contents); vfs.set_file_contents(path.clone(), contents);

View File

@ -135,7 +135,10 @@ pub(crate) fn fetch_workspaces_if_needed(&mut self) {
) )
} }
LinkedProject::InlineJsonProject(it) => { LinkedProject::InlineJsonProject(it) => {
project_model::ProjectWorkspace::load_inline(it.clone()) project_model::ProjectWorkspace::load_inline(
it.clone(),
cargo_config.target.as_deref(),
)
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -253,11 +256,7 @@ pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<Projec
res res
}; };
for ws in workspaces.iter() { for ws in workspaces.iter() {
crate_graph.extend(ws.to_crate_graph( crate_graph.extend(ws.to_crate_graph(self.proc_macro_client.as_ref(), &mut load));
self.config.cargo().target.as_deref(),
self.proc_macro_client.as_ref(),
&mut load,
));
} }
crate_graph crate_graph
@ -289,9 +288,7 @@ fn reload_flycheck(&mut self) {
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(id, w)| match w { .filter_map(|(id, w)| match w {
ProjectWorkspace::Cargo { cargo, sysroot: _, rustc: _ } => { ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
Some((id, cargo.workspace_root()))
}
ProjectWorkspace::Json { project, .. } => { ProjectWorkspace::Json { project, .. } => {
// Enable flychecks for json projects if a custom flycheck command was supplied // Enable flychecks for json projects if a custom flycheck command was supplied
// in the workspace configuration. // in the workspace configuration.