use std::{iter, mem};
use hir::{db::DefDatabase, ChangeWithProcMacros, ProcMacros, ProcMacrosBuilder};
use ide::CrateId;
use ide_db::{
base_db::{ra_salsa::Durability, CrateGraph, CrateWorkspaceData, ProcMacroPaths},
FxHashMap,
};
use itertools::Itertools;
use load_cargo::{load_proc_macro, ProjectFolders};
use lsp_types::FileSystemWatcher;
use proc_macro_api::ProcMacroServer;
use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
use stdx::{format_to, thread::ThreadIntent};
use triomphe::Arc;
use vfs::{AbsPath, AbsPathBuf, ChangeKind};
use crate::{
config::{Config, FilesWatcher, LinkedProject},
flycheck::{FlycheckConfig, FlycheckHandle},
global_state::{
FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState,
},
lsp_ext,
main_loop::{DiscoverProjectParam, Task},
op_queue::Cause,
};
use tracing::{debug, info};
#[derive(Debug)]
pub(crate) enum ProjectWorkspaceProgress {
Begin,
Report(String),
End(Vec<anyhow::Result<ProjectWorkspace>>, bool),
}
#[derive(Debug)]
pub(crate) enum BuildDataProgress {
Begin,
Report(String),
End((Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)),
}
#[derive(Debug)]
pub(crate) enum ProcMacroProgress {
Begin,
Report(String),
End(ProcMacros),
}
impl GlobalState {
pub(crate) fn is_quiescent(&self) -> bool {
self.vfs_done
&& self.last_reported_status.is_some()
&& !self.fetch_workspaces_queue.op_in_progress()
&& !self.fetch_build_data_queue.op_in_progress()
&& !self.fetch_proc_macros_queue.op_in_progress()
&& !self.discover_workspace_queue.op_in_progress()
&& self.vfs_progress_config_version >= self.vfs_config_version
}
fn is_fully_ready(&self) -> bool {
self.is_quiescent() && !self.prime_caches_queue.op_in_progress()
}
pub(crate) fn update_configuration(&mut self, config: Config) {
let _p = tracing::info_span!("GlobalState::update_configuration").entered();
let old_config = mem::replace(&mut self.config, Arc::new(config));
if self.config.lru_parse_query_capacity() != old_config.lru_parse_query_capacity() {
self.analysis_host.update_lru_capacity(self.config.lru_parse_query_capacity());
}
if self.config.lru_query_capacities_config() != old_config.lru_query_capacities_config() {
self.analysis_host.update_lru_capacities(
&self.config.lru_query_capacities_config().cloned().unwrap_or_default(),
);
}
if self.config.linked_or_discovered_projects() != old_config.linked_or_discovered_projects()
{
let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), req)
} else if self.config.flycheck(None) != old_config.flycheck(None) {
self.reload_flycheck();
}
if self.analysis_host.raw_database().expand_proc_attr_macros()
!= self.config.expand_proc_attr_macros()
{
self.analysis_host.raw_database_mut().set_expand_proc_attr_macros_with_durability(
self.config.expand_proc_attr_macros(),
Durability::HIGH,
);
}
}
pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams {
let mut status = lsp_ext::ServerStatusParams {
health: lsp_ext::Health::Ok,
quiescent: self.is_fully_ready(),
message: None,
};
let mut message = String::new();
if !self.config.cargo_autoreload_config(None)
&& self.is_quiescent()
&& self.fetch_workspaces_queue.op_requested()
&& self.config.discover_workspace_config().is_none()
{
status.health |= lsp_ext::Health::Warning;
message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n");
}
if self.build_deps_changed {
status.health |= lsp_ext::Health::Warning;
message.push_str(
"Proc-macros and/or build scripts have changed and need to be rebuilt.\n\n",
);
}
if self.fetch_build_data_error().is_err() {
status.health |= lsp_ext::Health::Warning;
message.push_str("Failed to run build scripts of some packages.\n\n");
}
if let Some(err) = &self.config_errors {
status.health |= lsp_ext::Health::Warning;
format_to!(message, "{err}\n");
}
if let Some(err) = &self.last_flycheck_error {
status.health |= lsp_ext::Health::Warning;
message.push_str(err);
message.push('\n');
}
if self.config.linked_or_discovered_projects().is_empty()
&& self.config.detached_files().is_empty()
{
status.health |= lsp_ext::Health::Warning;
message.push_str("Failed to discover workspace.\n");
message.push_str("Consider adding the `Cargo.toml` of the workspace to the [`linkedProjects`](https://rust-analyzer.github.io/manual.html#rust-analyzer.linkedProjects) setting.\n\n");
}
if self.fetch_workspace_error().is_err() {
status.health |= lsp_ext::Health::Error;
message.push_str("Failed to load workspaces.");
if self.config.has_linked_projects() {
message.push_str(
"`rust-analyzer.linkedProjects` have been specified, which may be incorrect. Specified project paths:\n",
);
message
.push_str(&format!(" {}", self.config.linked_manifests().format("\n ")));
if self.config.has_linked_project_jsons() {
message.push_str("\nAdditionally, one or more project jsons are specified")
}
}
message.push_str("\n\n");
}
if !self.workspaces.is_empty() {
let proc_macro_clients =
self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None));
for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) {
if let ProjectWorkspaceKind::Cargo { error: Some(error), .. }
| ProjectWorkspaceKind::DetachedFile {
cargo: Some((_, _, Some(error))), ..
} = &ws.kind
{
status.health |= lsp_ext::Health::Warning;
format_to!(
message,
"Failed to read Cargo metadata with dependencies for `{}`: {:#}\n\n",
ws.manifest_or_root(),
error
);
}
if let Some(err) = ws.sysroot.error() {
status.health |= lsp_ext::Health::Warning;
format_to!(
message,
"Workspace `{}` has sysroot errors: ",
ws.manifest_or_root()
);
message.push_str(err);
message.push_str("\n\n");
}
if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(err)), .. } = &ws.kind {
status.health |= lsp_ext::Health::Warning;
format_to!(
message,
"Failed loading rustc_private crates for workspace `{}`: ",
ws.manifest_or_root()
);
message.push_str(err);
message.push_str("\n\n");
};
match proc_macro_client {
Some(Err(err)) => {
status.health |= lsp_ext::Health::Warning;
format_to!(
message,
"Failed spawning proc-macro server for workspace `{}`: {err}",
ws.manifest_or_root()
);
message.push_str("\n\n");
}
Some(Ok(client)) => {
if let Some(err) = client.exited() {
status.health |= lsp_ext::Health::Warning;
format_to!(
message,
"proc-macro server for workspace `{}` exited: {err}",
ws.manifest_or_root()
);
message.push_str("\n\n");
}
}
_ => (),
}
}
}
if !message.is_empty() {
status.message = Some(message.trim_end().to_owned());
}
status
}
pub(crate) fn fetch_workspaces(
&mut self,
cause: Cause,
path: Option<AbsPathBuf>,
force_crate_graph_reload: bool,
) {
info!(%cause, "will fetch workspaces");
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
let linked_projects = self.config.linked_or_discovered_projects();
let detached_files: Vec<_> = self
.config
.detached_files()
.iter()
.cloned()
.map(ManifestPath::try_from)
.filter_map(Result::ok)
.collect();
let cargo_config = self.config.cargo(None);
let discover_command = self.config.discover_workspace_config().cloned();
let is_quiescent = !(self.discover_workspace_queue.op_in_progress()
|| self.vfs_progress_config_version < self.vfs_config_version
|| !self.vfs_done);
move |sender| {
let progress = {
let sender = sender.clone();
move |msg| {
sender
.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Report(msg)))
.unwrap()
}
};
sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
if let (Some(_command), Some(path)) = (&discover_command, &path) {
let build = linked_projects.iter().find_map(|project| match project {
LinkedProject::InlineJsonProject(it) => it.crate_by_buildfile(path),
_ => None,
});
if let Some(build) = build {
if is_quiescent {
let path = AbsPathBuf::try_from(build.build_file)
.expect("Unable to convert to an AbsPath");
let arg = DiscoverProjectParam::Buildfile(path);
sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
}
}
}
let mut workspaces = linked_projects
.iter()
.map(|project| match project {
LinkedProject::ProjectManifest(manifest) => {
debug!(path = %manifest, "loading project from manifest");
project_model::ProjectWorkspace::load(
manifest.clone(),
&cargo_config,
&progress,
)
}
LinkedProject::InlineJsonProject(it) => {
let workspace = project_model::ProjectWorkspace::load_inline(
it.clone(),
&cargo_config,
);
Ok(workspace)
}
})
.collect::<Vec<_>>();
let mut i = 0;
while i < workspaces.len() {
if let Ok(w) = &workspaces[i] {
let dupes: Vec<_> = workspaces[i + 1..]
.iter()
.positions(|it| it.as_ref().is_ok_and(|ws| ws.eq_ignore_build_data(w)))
.collect();
dupes.into_iter().rev().for_each(|d| {
_ = workspaces.remove(d + i + 1);
});
}
i += 1;
}
if !detached_files.is_empty() {
workspaces.extend(project_model::ProjectWorkspace::load_detached_files(
detached_files,
&cargo_config,
));
}
info!(?workspaces, "did fetch workspaces");
sender
.send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(
workspaces,
force_crate_graph_reload,
)))
.unwrap();
}
});
}
pub(crate) fn fetch_build_data(&mut self, cause: Cause) {
info!(%cause, "will fetch build data");
let workspaces = Arc::clone(&self.workspaces);
let config = self.config.cargo(None);
let root_path = self.config.root_path().clone();
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
let progress = {
let sender = sender.clone();
move |msg| {
sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
}
};
let res = ProjectWorkspace::run_all_build_scripts(
&workspaces,
&config,
&progress,
&root_path,
);
sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap();
});
}
pub(crate) fn fetch_proc_macros(&mut self, cause: Cause, paths: Vec<ProcMacroPaths>) {
info!(%cause, "will load proc macros");
let ignored_proc_macros = self.config.ignored_proc_macros(None).clone();
let proc_macro_clients = self.proc_macro_clients.clone();
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
sender.send(Task::LoadProcMacros(ProcMacroProgress::Begin)).unwrap();
let ignored_proc_macros = &ignored_proc_macros;
let progress = {
let sender = sender.clone();
&move |msg| {
sender.send(Task::LoadProcMacros(ProcMacroProgress::Report(msg))).unwrap()
}
};
let mut builder = ProcMacrosBuilder::default();
let chain = proc_macro_clients
.iter()
.map(|res| res.as_ref().map_err(|e| e.to_string()))
.chain(iter::repeat_with(|| Err("proc-macro-srv is not running".into())));
for (client, paths) in chain.zip(paths) {
paths
.into_iter()
.map(move |(crate_id, res)| {
(
crate_id,
res.map_or_else(
|e| Err((e, true)),
|(crate_name, path)| {
progress(path.to_string());
client.as_ref().map_err(|it| (it.clone(), true)).and_then(
|client| {
load_proc_macro(
client,
&path,
ignored_proc_macros
.iter()
.find_map(|(name, macros)| {
eq_ignore_underscore(name, &crate_name)
.then_some(&**macros)
})
.unwrap_or_default(),
)
},
)
},
),
)
})
.for_each(|(krate, res)| builder.insert(krate, res));
}
sender.send(Task::LoadProcMacros(ProcMacroProgress::End(builder.build()))).unwrap();
});
}
pub(crate) fn set_proc_macros(&mut self, proc_macros: ProcMacros) {
let mut change = ChangeWithProcMacros::new();
change.set_proc_macros(proc_macros);
self.analysis_host.apply_change(change);
}
pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
let _p = tracing::info_span!("GlobalState::switch_workspaces").entered();
tracing::info!(%cause, "will switch workspaces");
let Some(FetchWorkspaceResponse { workspaces, force_crate_graph_reload }) =
self.fetch_workspaces_queue.last_op_result()
else {
return;
};
info!(%cause, ?force_crate_graph_reload);
if self.fetch_workspace_error().is_err() && !self.workspaces.is_empty() {
if *force_crate_graph_reload {
self.recreate_crate_graph(cause);
}
return;
}
let workspaces =
workspaces.iter().filter_map(|res| res.as_ref().ok().cloned()).collect::<Vec<_>>();
let same_workspaces = workspaces.len() == self.workspaces.len()
&& workspaces
.iter()
.zip(self.workspaces.iter())
.all(|(l, r)| l.eq_ignore_build_data(r));
if same_workspaces {
let (workspaces, build_scripts) = match self.fetch_build_data_queue.last_op_result() {
Some(FetchBuildDataResponse { workspaces, build_scripts }) => {
(workspaces.clone(), build_scripts.as_slice())
}
None => (Default::default(), Default::default()),
};
if Arc::ptr_eq(&workspaces, &self.workspaces) {
info!("set build scripts to workspaces");
let workspaces = workspaces
.iter()
.cloned()
.zip(build_scripts)
.map(|(mut ws, bs)| {
ws.set_build_scripts(bs.as_ref().ok().cloned().unwrap_or_default());
ws
})
.collect::<Vec<_>>();
info!("same workspace, but new build data");
self.workspaces = Arc::new(workspaces);
} else {
info!("build scripts do not match the version of the active workspace");
if *force_crate_graph_reload {
self.recreate_crate_graph(cause);
}
return;
}
} else {
info!("abandon build scripts for workspaces");
self.workspaces = Arc::new(workspaces);
if self.config.run_build_scripts(None) {
self.build_deps_changed = false;
self.fetch_build_data_queue.request_op("workspace updated".to_owned(), ());
}
}
if let FilesWatcher::Client = self.config.files().watcher {
let filter = self
.workspaces
.iter()
.flat_map(|ws| ws.to_roots())
.filter(|it| it.is_local)
.map(|it| it.include);
let mut watchers: Vec<FileSystemWatcher> =
if self.config.did_change_watched_files_relative_pattern_support() {
filter
.flat_map(|include| {
include.into_iter().flat_map(|base| {
[
(base.clone(), "**/*.rs"),
(base.clone(), "**/Cargo.{lock,toml}"),
(base, "**/rust-analyzer.toml"),
]
})
})
.map(|(base, pat)| lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::Relative(
lsp_types::RelativePattern {
base_uri: lsp_types::OneOf::Right(
lsp_types::Url::from_file_path(base).unwrap(),
),
pattern: pat.to_owned(),
},
),
kind: None,
})
.collect()
} else {
filter
.flat_map(|include| {
include.into_iter().flat_map(|base| {
[
format!("{base}/**/*.rs"),
format!("{base}/**/Cargo.{{toml,lock}}"),
format!("{base}/**/rust-analyzer.toml"),
]
})
})
.map(|glob_pattern| lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
kind: None,
})
.collect()
};
for ws in self.workspaces.iter() {
if let ProjectWorkspaceKind::Json(project_json) = &ws.kind {
for (_, krate) in project_json.crates() {
let Some(build) = &krate.build else {
continue;
};
watchers.push(lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String(
build.build_file.to_string(),
),
kind: None,
});
}
}
}
watchers.extend(
iter::once(Config::user_config_dir_path().as_deref())
.chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref)))
.flatten()
.map(|glob_pattern| lsp_types::FileSystemWatcher {
glob_pattern: lsp_types::GlobPattern::String(glob_pattern.to_string()),
kind: None,
}),
);
let registration_options =
lsp_types::DidChangeWatchedFilesRegistrationOptions { watchers };
let registration = lsp_types::Registration {
id: "workspace/didChangeWatchedFiles".to_owned(),
method: "workspace/didChangeWatchedFiles".to_owned(),
register_options: Some(serde_json::to_value(registration_options).unwrap()),
};
self.send_request::<lsp_types::request::RegisterCapability>(
lsp_types::RegistrationParams { registrations: vec![registration] },
|_, _| (),
);
}
let files_config = self.config.files();
let project_folders = ProjectFolders::new(
&self.workspaces,
&files_config.exclude,
Config::user_config_dir_path().as_deref(),
);
if (self.proc_macro_clients.is_empty() || !same_workspaces)
&& self.config.expand_proc_macros()
{
info!("Spawning proc-macro servers");
self.proc_macro_clients = Arc::from_iter(self.workspaces.iter().map(|ws| {
let path = match self.config.proc_macro_srv() {
Some(path) => path,
None => ws.find_sysroot_proc_macro_srv()?,
};
let env: FxHashMap<_, _> = match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo_config_extra_env, .. }
| ProjectWorkspaceKind::DetachedFile {
cargo: Some(_),
cargo_config_extra_env,
..
} => cargo_config_extra_env
.iter()
.chain(self.config.extra_env(None))
.map(|(a, b)| (a.clone(), b.clone()))
.chain(
ws.sysroot
.root()
.map(|it| ("RUSTUP_TOOLCHAIN".to_owned(), it.to_string())),
)
.collect(),
_ => Default::default(),
};
info!("Using proc-macro server at {path}");
ProcMacroServer::spawn(&path, &env).map_err(|err| {
tracing::error!(
"Failed to run proc-macro server from path {path}, error: {err:?}",
);
anyhow::format_err!(
"Failed to run proc-macro server from path {path}, error: {err:?}",
)
})
}))
}
let watch = match files_config.watcher {
FilesWatcher::Client => vec![],
FilesWatcher::Server => project_folders.watch,
};
self.vfs_config_version += 1;
self.loader.handle.set_config(vfs::loader::Config {
load: project_folders.load,
watch,
version: self.vfs_config_version,
});
self.source_root_config = project_folders.source_root_config;
self.local_roots_parent_map = Arc::new(self.source_root_config.source_root_parent_map());
info!(?cause, "recreating the crate graph");
self.recreate_crate_graph(cause);
info!("did switch workspaces");
}
fn recreate_crate_graph(&mut self, cause: String) {
info!(?cause, "Building Crate Graph");
self.report_progress(
"Building CrateGraph",
crate::lsp::utils::Progress::Begin,
None,
None,
None,
);
self.crate_graph_file_dependencies.clear();
self.detached_files = self
.workspaces
.iter()
.filter_map(|ws| match &ws.kind {
ProjectWorkspaceKind::DetachedFile { file, .. } => Some(file.clone()),
_ => None,
})
.collect();
let (crate_graph, proc_macro_paths, ws_data) = {
let vfs = &mut self.vfs.write().0;
let load = |path: &AbsPath| {
let vfs_path = vfs::VfsPath::from(path.to_path_buf());
self.crate_graph_file_dependencies.insert(vfs_path.clone());
vfs.file_id(&vfs_path)
};
ws_to_crate_graph(&self.workspaces, self.config.extra_env(None), load)
};
let mut change = ChangeWithProcMacros::new();
if self.config.expand_proc_macros() {
change.set_proc_macros(
crate_graph
.iter()
.map(|id| (id, Err(("proc-macro has not been built yet".to_owned(), true))))
.collect(),
);
self.fetch_proc_macros_queue.request_op(cause, proc_macro_paths);
} else {
change.set_proc_macros(
crate_graph
.iter()
.map(|id| (id, Err(("proc-macro expansion is disabled".to_owned(), false))))
.collect(),
);
}
change.set_crate_graph(crate_graph, ws_data);
self.analysis_host.apply_change(change);
self.report_progress(
"Building CrateGraph",
crate::lsp::utils::Progress::End,
None,
None,
None,
);
self.process_changes();
self.reload_flycheck();
}
pub(super) fn fetch_workspace_error(&self) -> Result<(), String> {
let mut buf = String::new();
let Some(FetchWorkspaceResponse { workspaces, .. }) =
self.fetch_workspaces_queue.last_op_result()
else {
return Ok(());
};
if workspaces.is_empty() && self.config.discover_workspace_config().is_none() {
stdx::format_to!(buf, "rust-analyzer failed to fetch workspace");
} else {
for ws in workspaces {
if let Err(err) = ws {
stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
}
}
}
if buf.is_empty() {
return Ok(());
}
Err(buf)
}
pub(super) fn fetch_build_data_error(&self) -> Result<(), String> {
let mut buf = String::new();
let Some(FetchBuildDataResponse { build_scripts, .. }) =
&self.fetch_build_data_queue.last_op_result()
else {
return Ok(());
};
for script in build_scripts {
match script {
Ok(data) => {
if let Some(stderr) = data.error() {
stdx::format_to!(buf, "{:#}\n", stderr)
}
}
Err(err) => stdx::format_to!(buf, "{:#}\n", err),
}
}
if buf.is_empty() {
Ok(())
} else {
Err(buf)
}
}
fn reload_flycheck(&mut self) {
let _p = tracing::info_span!("GlobalState::reload_flycheck").entered();
let config = self.config.flycheck(None);
let sender = self.flycheck_sender.clone();
let invocation_strategy = match config {
FlycheckConfig::CargoCommand { .. } => {
crate::flycheck::InvocationStrategy::PerWorkspace
}
FlycheckConfig::CustomCommand { ref invocation_strategy, .. } => {
invocation_strategy.clone()
}
};
self.flycheck = match invocation_strategy {
crate::flycheck::InvocationStrategy::Once => {
vec![FlycheckHandle::spawn(
0,
sender,
config,
None,
self.config.root_path().clone(),
None,
)]
}
crate::flycheck::InvocationStrategy::PerWorkspace => {
self.workspaces
.iter()
.enumerate()
.filter_map(|(id, ws)| {
Some((
id,
match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
| ProjectWorkspaceKind::DetachedFile {
cargo: Some((cargo, _, _)),
..
} => (cargo.workspace_root(), Some(cargo.manifest_path())),
ProjectWorkspaceKind::Json(project) => {
match config {
FlycheckConfig::CustomCommand { .. } => {
(project.path(), None)
}
_ => return None,
}
}
ProjectWorkspaceKind::DetachedFile { .. } => return None,
},
ws.sysroot.root().map(ToOwned::to_owned),
))
})
.map(|(id, (root, manifest_path), sysroot_root)| {
FlycheckHandle::spawn(
id,
sender.clone(),
config.clone(),
sysroot_root,
root.to_path_buf(),
manifest_path.map(|it| it.to_path_buf()),
)
})
.collect()
}
}
.into();
}
}
pub fn ws_to_crate_graph(
workspaces: &[ProjectWorkspace],
extra_env: &FxHashMap<String, String>,
mut load: impl FnMut(&AbsPath) -> Option<vfs::FileId>,
) -> (CrateGraph, Vec<ProcMacroPaths>, FxHashMap<CrateId, Arc<CrateWorkspaceData>>) {
let mut crate_graph = CrateGraph::default();
let mut proc_macro_paths = Vec::default();
let mut ws_data = FxHashMap::default();
for ws in workspaces {
let (other, mut crate_proc_macros) = ws.to_crate_graph(&mut load, extra_env);
let ProjectWorkspace { toolchain, target_layout, .. } = ws;
let mapping = crate_graph.extend(other, &mut crate_proc_macros);
ws_data.extend(mapping.values().copied().zip(iter::repeat(Arc::new(CrateWorkspaceData {
toolchain: toolchain.clone(),
data_layout: target_layout.clone(),
proc_macro_cwd: Some(ws.workspace_root().to_owned()),
}))));
proc_macro_paths.push(crate_proc_macros);
}
crate_graph.shrink_to_fit();
proc_macro_paths.shrink_to_fit();
(crate_graph, proc_macro_paths, ws_data)
}
pub(crate) fn should_refresh_for_change(
path: &AbsPath,
change_kind: ChangeKind,
additional_paths: &[&str],
) -> bool {
const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
let file_name = match path.file_name() {
Some(it) => it,
None => return false,
};
if let "Cargo.toml" | "Cargo.lock" = file_name {
return true;
}
if additional_paths.contains(&file_name) {
return true;
}
if change_kind == ChangeKind::Modify {
return false;
}
if path.extension().unwrap_or_default() != "rs" {
let is_cargo_config = matches!(file_name, "config.toml" | "config")
&& path.parent().map(|parent| parent.as_str().ends_with(".cargo")).unwrap_or(false);
return is_cargo_config;
}
if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_str().ends_with(it)) {
return true;
}
let parent = match path.parent() {
Some(it) => it,
None => return false,
};
if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_str().ends_with(it)) {
return true;
}
if file_name == "main.rs" {
let grand_parent = match parent.parent() {
Some(it) => it,
None => return false,
};
if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_str().ends_with(it)) {
return true;
}
}
false
}
fn eq_ignore_underscore(s1: &str, s2: &str) -> bool {
if s1.len() != s2.len() {
return false;
}
s1.as_bytes().iter().zip(s2.as_bytes()).all(|(c1, c2)| {
let c1_underscore = c1 == &b'_' || c1 == &b'-';
let c2_underscore = c2 == &b'_' || c2 == &b'-';
c1 == c2 || (c1_underscore && c2_underscore)
})
}