feat: Make rust-analyzer work partially when missing an internet connection

This commit is contained in:
Lukas Wirth 2024-08-17 11:18:50 +02:00
parent 07659783fd
commit 07c1b83e98
4 changed files with 75 additions and 17 deletions

View file

@ -33,6 +33,8 @@ pub struct CargoWorkspace {
workspace_root: AbsPathBuf, workspace_root: AbsPathBuf,
target_directory: AbsPathBuf, target_directory: AbsPathBuf,
manifest_path: ManifestPath, manifest_path: ManifestPath,
// Whether this workspace was queried with `--no-deps`.
no_deps: bool,
} }
impl ops::Index<Package> for CargoWorkspace { impl ops::Index<Package> for CargoWorkspace {
@ -259,6 +261,18 @@ impl CargoWorkspace {
sysroot: &Sysroot, sysroot: &Sysroot,
locked: bool, locked: bool,
progress: &dyn Fn(String), progress: &dyn Fn(String),
) -> anyhow::Result<cargo_metadata::Metadata> {
Self::fetch_metadata_(cargo_toml, current_dir, config, sysroot, locked, false, progress)
}
fn fetch_metadata_(
cargo_toml: &ManifestPath,
current_dir: &AbsPath,
config: &CargoConfig,
sysroot: &Sysroot,
locked: bool,
no_deps: bool,
progress: &dyn Fn(String),
) -> anyhow::Result<cargo_metadata::Metadata> { ) -> anyhow::Result<cargo_metadata::Metadata> {
let targets = find_list_of_build_targets(config, cargo_toml, sysroot); let targets = find_list_of_build_targets(config, cargo_toml, sysroot);
@ -314,6 +328,9 @@ impl CargoWorkspace {
if locked { if locked {
other_options.push("--locked".to_owned()); other_options.push("--locked".to_owned());
} }
if no_deps {
other_options.push("--no-deps".to_owned());
}
meta.other_options(other_options); meta.other_options(other_options);
// FIXME: Fetching metadata is a slow process, as it might require // FIXME: Fetching metadata is a slow process, as it might require
@ -324,6 +341,22 @@ impl CargoWorkspace {
(|| -> Result<cargo_metadata::Metadata, cargo_metadata::Error> { (|| -> Result<cargo_metadata::Metadata, cargo_metadata::Error> {
let output = meta.cargo_command().output()?; let output = meta.cargo_command().output()?;
if !output.status.success() { if !output.status.success() {
if !no_deps {
// If we failed to fetch metadata with deps, try again without them.
// This makes r-a still work partially when offline.
if let Ok(metadata) = Self::fetch_metadata_(
cargo_toml,
current_dir,
config,
sysroot,
locked,
true,
progress,
) {
return Ok(metadata);
}
}
return Err(cargo_metadata::Error::CargoMetadata { return Err(cargo_metadata::Error::CargoMetadata {
stderr: String::from_utf8(output.stderr)?, stderr: String::from_utf8(output.stderr)?,
}); });
@ -431,8 +464,8 @@ impl CargoWorkspace {
pkg_data.targets.push(tgt); pkg_data.targets.push(tgt);
} }
} }
let resolve = meta.resolve.expect("metadata executed with deps"); let no_deps = meta.resolve.is_none();
for mut node in resolve.nodes { for mut node in meta.resolve.map_or_else(Vec::new, |it| it.nodes) {
let &source = pkg_by_id.get(&node.id).unwrap(); let &source = pkg_by_id.get(&node.id).unwrap();
node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg)); node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg));
let dependencies = node let dependencies = node
@ -451,7 +484,14 @@ impl CargoWorkspace {
let target_directory = AbsPathBuf::assert(meta.target_directory); let target_directory = AbsPathBuf::assert(meta.target_directory);
CargoWorkspace { packages, targets, workspace_root, target_directory, manifest_path } CargoWorkspace {
packages,
targets,
workspace_root,
target_directory,
manifest_path,
no_deps,
}
} }
pub fn packages(&self) -> impl ExactSizeIterator<Item = Package> + '_ { pub fn packages(&self) -> impl ExactSizeIterator<Item = Package> + '_ {
@ -533,6 +573,10 @@ impl CargoWorkspace {
fn is_unique(&self, name: &str) -> bool { fn is_unique(&self, name: &str) -> bool {
self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
} }
pub fn no_deps(&self) -> bool {
self.no_deps
}
} }
fn find_list_of_build_targets( fn find_list_of_build_targets(

View file

@ -372,18 +372,19 @@ impl Sysroot {
.flatten() .flatten()
}; };
let resolve = res.resolve.as_mut().expect("metadata executed with deps"); if let Some(resolve) = res.resolve.as_mut() {
resolve.nodes.retain_mut(|node| { resolve.nodes.retain_mut(|node| {
// Replace `rustc-std-workspace` crate with the actual one in the dependency list // Replace `rustc-std-workspace` crate with the actual one in the dependency list
node.deps.iter_mut().for_each(|dep| { node.deps.iter_mut().for_each(|dep| {
let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg); let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg);
if let Some((_, real)) = real_pkg { if let Some((_, real)) = real_pkg {
dep.pkg = real; dep.pkg = real;
} }
});
// Remove this node if it's a fake one
!patches.clone().any(|((_, fake), _)| fake == node.id)
}); });
// Remove this node if it's a fake one }
!patches.clone().any(|((_, fake), _)| fake == node.id)
});
// Remove the fake ones from the package list // Remove the fake ones from the package list
patches.map(|((idx, _), _)| idx).sorted().rev().for_each(|idx| { patches.map(|((idx, _), _)| idx).sorted().rev().for_each(|idx| {
res.packages.remove(idx); res.packages.remove(idx);

View file

@ -441,14 +441,15 @@ impl ProjectWorkspace {
) -> anyhow::Result<WorkspaceBuildScripts> { ) -> anyhow::Result<WorkspaceBuildScripts> {
match &self.kind { match &self.kind {
ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. }
| ProjectWorkspaceKind::Cargo { cargo, .. } => { | ProjectWorkspaceKind::Cargo { cargo, .. }
if !cargo.no_deps() =>
{
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, &self.sysroot) WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, &self.sysroot)
.with_context(|| { .with_context(|| {
format!("Failed to run build scripts for {}", cargo.workspace_root()) format!("Failed to run build scripts for {}", cargo.workspace_root())
}) })
} }
ProjectWorkspaceKind::DetachedFile { cargo: None, .. } _ => Ok(WorkspaceBuildScripts::default()),
| ProjectWorkspaceKind::Json { .. } => Ok(WorkspaceBuildScripts::default()),
} }
} }

View file

@ -165,6 +165,18 @@ impl GlobalState {
self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None)); 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) { for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) {
if matches!(
&ws.kind,
ProjectWorkspaceKind::Cargo { cargo, .. } | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. }
if cargo.no_deps()
) {
status.health |= lsp_ext::Health::Warning;
format_to!(
message,
"Workspace `{}` has been queried without dependencies, connecting to crates.io might have failed.\n\n",
ws.manifest_or_root()
);
}
if let Some(err) = ws.sysroot.error() { if let Some(err) = ws.sysroot.error() {
status.health |= lsp_ext::Health::Warning; status.health |= lsp_ext::Health::Warning;
format_to!( format_to!(