diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 8c9393bcc9..fdaebeaa93 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -30,7 +30,8 @@ pub type ProcMacroPaths = pub enum ProcMacroLoadingError { Disabled, FailedToBuild, - MissingDylibPath, + ExpectedProcMacroArtifact, + MissingDylibPath(Box<[String]>), NotYetBuilt, NoProcMacros, ProcMacroSrvError(Box), @@ -39,8 +40,9 @@ impl ProcMacroLoadingError { pub fn is_hard_error(&self) -> bool { match self { ProcMacroLoadingError::Disabled | ProcMacroLoadingError::NotYetBuilt => false, - ProcMacroLoadingError::FailedToBuild - | ProcMacroLoadingError::MissingDylibPath + ProcMacroLoadingError::ExpectedProcMacroArtifact + | ProcMacroLoadingError::FailedToBuild + | ProcMacroLoadingError::MissingDylibPath(_) | ProcMacroLoadingError::NoProcMacros | ProcMacroLoadingError::ProcMacroSrvError(_) => true, } @@ -51,10 +53,23 @@ impl Error for ProcMacroLoadingError {} impl fmt::Display for ProcMacroLoadingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + ProcMacroLoadingError::ExpectedProcMacroArtifact => { + write!(f, "proc-macro crate did not build proc-macro artifact") + } ProcMacroLoadingError::Disabled => write!(f, "proc-macro expansion is disabled"), ProcMacroLoadingError::FailedToBuild => write!(f, "proc-macro failed to build"), - ProcMacroLoadingError::MissingDylibPath => { - write!(f, "proc-macro crate build data is missing a dylib path") + ProcMacroLoadingError::MissingDylibPath(candidates) if candidates.is_empty() => { + write!( + f, + "proc-macro crate built but the dylib path is missing, this indicates a problem with your build system." + ) + } + ProcMacroLoadingError::MissingDylibPath(candidates) => { + write!( + f, + "proc-macro crate built but the dylib path is missing, this indicates a problem with your build system. Candidates not considered due to not having a dynamic library extension: {}", + candidates.join(", ") + ) } ProcMacroLoadingError::NotYetBuilt => write!(f, "proc-macro not yet built"), ProcMacroLoadingError::NoProcMacros => { diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs index 499caa622c..664665625f 100644 --- a/crates/project-model/src/build_dependencies.rs +++ b/crates/project-model/src/build_dependencies.rs @@ -16,6 +16,7 @@ use la_arena::ArenaMap; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use serde::Deserialize as _; +use stdx::{always, never}; use toolchain::Tool; use crate::{ @@ -30,6 +31,15 @@ pub struct WorkspaceBuildScripts { error: Option, } +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum ProcMacroDylibPath { + Path(AbsPathBuf), + DylibNotFound(Box<[Utf8PathBuf]>), + NotProcMacro, + #[default] + NotBuilt, +} + /// Output of the build script and proc-macro building step for a concrete package. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub(crate) struct BuildScriptOutput { @@ -43,7 +53,7 @@ pub(crate) struct BuildScriptOutput { /// Directory where a build script might place its output. pub(crate) out_dir: Option, /// Path to the proc-macro library file if this package exposes proc-macros. - pub(crate) proc_macro_dylib_path: Option, + pub(crate) proc_macro_dylib_path: ProcMacroDylibPath, } impl BuildScriptOutput { @@ -51,7 +61,7 @@ impl BuildScriptOutput { self.cfgs.is_empty() && self.envs.is_empty() && self.out_dir.is_none() - && self.proc_macro_dylib_path.is_none() + && self.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt } } @@ -126,6 +136,8 @@ impl WorkspaceBuildScripts { |package, cb| { if let Some(&(package, workspace)) = by_id.get(package) { cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]); + } else { + never!("Received compiler message for unknown package: {}", package); } }, progress, @@ -140,12 +152,9 @@ impl WorkspaceBuildScripts { if tracing::enabled!(tracing::Level::INFO) { for (idx, workspace) in workspaces.iter().enumerate() { for package in workspace.packages() { - let package_build_data = &mut res[idx].outputs[package]; + let package_build_data: &mut BuildScriptOutput = &mut res[idx].outputs[package]; if !package_build_data.is_empty() { - tracing::info!( - "{}: {package_build_data:?}", - workspace[package].manifest.parent(), - ); + tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,); } } } @@ -198,10 +207,33 @@ impl WorkspaceBuildScripts { let path = dir_entry.path(); let extension = path.extension()?; if extension == std::env::consts::DLL_EXTENSION { - let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned(); - let path = AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).ok()?) - .ok()?; - return Some((name, path)); + let name = path + .file_stem()? + .to_str()? + .split_once('-')? + .0 + .trim_start_matches("lib") + .to_owned(); + let path = match Utf8PathBuf::from_path_buf(path) { + Ok(path) => path, + Err(path) => { + tracing::warn!( + "Proc-macro dylib path contains non-UTF8 characters: {:?}", + path.display() + ); + return None; + } + }; + return match AbsPathBuf::try_from(path) { + Ok(path) => Some((name, path)), + Err(path) => { + tracing::error!( + "proc-macro dylib path is not absolute: {:?}", + path + ); + None + } + }; } } None @@ -209,28 +241,24 @@ impl WorkspaceBuildScripts { .collect(); for p in rustc.packages() { let package = &rustc[p]; - if package - .targets - .iter() - .any(|&it| matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true })) - { - if let Some((_, path)) = proc_macro_dylibs - .iter() - .find(|(name, _)| *name.trim_start_matches("lib") == package.name) - { - bs.outputs[p].proc_macro_dylib_path = Some(path.clone()); + bs.outputs[p].proc_macro_dylib_path = + if package.targets.iter().any(|&it| { + matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true }) + }) { + match proc_macro_dylibs.iter().find(|(name, _)| *name == package.name) { + Some((_, path)) => ProcMacroDylibPath::Path(path.clone()), + _ => ProcMacroDylibPath::DylibNotFound(Box::default()), + } + } else { + ProcMacroDylibPath::NotProcMacro } - } } if tracing::enabled!(tracing::Level::INFO) { for package in rustc.packages() { let package_build_data = &bs.outputs[package]; if !package_build_data.is_empty() { - tracing::info!( - "{}: {package_build_data:?}", - rustc[package].manifest.parent(), - ); + tracing::info!("{}: {package_build_data:?}", rustc[package].manifest,); } } } @@ -263,6 +291,12 @@ impl WorkspaceBuildScripts { |package, cb| { if let Some(&package) = by_id.get(package) { cb(&workspace[package].name, &mut outputs[package]); + } else { + never!( + "Received compiler message for unknown package: {}\n {}", + package, + by_id.keys().join(", ") + ); } }, progress, @@ -272,10 +306,7 @@ impl WorkspaceBuildScripts { for package in workspace.packages() { let package_build_data = &outputs[package]; if !package_build_data.is_empty() { - tracing::info!( - "{}: {package_build_data:?}", - workspace[package].manifest.parent(), - ); + tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,); } } } @@ -348,15 +379,25 @@ impl WorkspaceBuildScripts { progress(format!( "building compile-time-deps: proc-macro {name} built" )); + always!( + data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt, + "received multiple compiler artifacts for the same package: {message:?}" + ); + if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt { + data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro; + } if message.target.kind.contains(&cargo_metadata::TargetKind::ProcMacro) { - // Skip rmeta file - if let Some(filename) = - message.filenames.iter().find(|file| is_dylib(file)) - { - let filename = AbsPath::assert(filename); - data.proc_macro_dylib_path = Some(filename.to_owned()); - } + data.proc_macro_dylib_path = + match message.filenames.iter().find(|file| is_dylib(file)) { + Some(filename) => { + let filename = AbsPath::assert(filename); + ProcMacroDylibPath::Path(filename.to_owned()) + } + None => ProcMacroDylibPath::DylibNotFound( + message.filenames.clone().into_boxed_slice(), + ), + }; } }); } diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index 25abb19d9d..767a4aade0 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -245,7 +245,7 @@ pub enum TargetKind { } impl TargetKind { - fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind { + pub fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind { for kind in kinds { return match kind { cargo_metadata::TargetKind::Bin => TargetKind::Bin, @@ -604,12 +604,12 @@ impl FetchMetadata { // but nothing else let mut extra_args = config.extra_args.iter(); while let Some(arg) = extra_args.next() { - if arg == "-Z" { - if let Some(arg) = extra_args.next() { - needs_nightly = true; - other_options.push("-Z".to_owned()); - other_options.push(arg.to_owned()); - } + if arg == "-Z" + && let Some(arg) = extra_args.next() + { + needs_nightly = true; + other_options.push("-Z".to_owned()); + other_options.push(arg.to_owned()); } } diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index 3bf3d06e6b..d39781b150 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -59,7 +59,7 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashSet; pub use crate::{ - build_dependencies::WorkspaceBuildScripts, + build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts}, cargo_workspace::{ CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustLibSource, Target, TargetData, TargetKind, @@ -139,21 +139,22 @@ impl ProjectManifest { } fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option { - if path.file_name().unwrap_or_default() == target_file_name { - if let Ok(manifest) = ManifestPath::try_from(path.to_path_buf()) { - return Some(manifest); - } + if path.file_name().unwrap_or_default() == target_file_name + && let Ok(manifest) = ManifestPath::try_from(path.to_path_buf()) + { + return Some(manifest); } let mut curr = Some(path); while let Some(path) = curr { let candidate = path.join(target_file_name); - if fs::metadata(&candidate).is_ok() { - if let Ok(manifest) = ManifestPath::try_from(candidate) { - return Some(manifest); - } + if fs::metadata(&candidate).is_ok() + && let Ok(manifest) = ManifestPath::try_from(candidate) + { + return Some(manifest); } + curr = path.parent(); } diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs index 9781c46737..c0a5009afb 100644 --- a/crates/project-model/src/sysroot.rs +++ b/crates/project-model/src/sysroot.rs @@ -143,12 +143,11 @@ impl Sysroot { Some(root) => { // special case rustc, we can look that up directly in the sysroot's bin folder // as it should never invoke another cargo binary - if let Tool::Rustc = tool { - if let Some(path) = + if let Tool::Rustc = tool + && let Some(path) = probe_for_binary(root.join("bin").join(Tool::Rustc.name()).into()) - { - return toolchain::command(path, current_dir, envs); - } + { + return toolchain::command(path, current_dir, envs); } let mut cmd = toolchain::command(tool.prefer_proxy(), current_dir, envs); @@ -291,29 +290,26 @@ impl Sysroot { pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) { self.workspace = workspace; - if self.error.is_none() { - if let Some(src_root) = &self.rust_lib_src_root { - let has_core = match &self.workspace { - RustLibSrcWorkspace::Workspace(ws) => { - ws.packages().any(|p| ws[p].name == "core") - } - RustLibSrcWorkspace::Json(project_json) => project_json - .crates() - .filter_map(|(_, krate)| krate.display_name.clone()) - .any(|name| name.canonical_name().as_str() == "core"), - RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(), - RustLibSrcWorkspace::Empty => true, + if self.error.is_none() + && let Some(src_root) = &self.rust_lib_src_root + { + let has_core = match &self.workspace { + RustLibSrcWorkspace::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"), + RustLibSrcWorkspace::Json(project_json) => project_json + .crates() + .filter_map(|(_, krate)| krate.display_name.clone()) + .any(|name| name.canonical_name().as_str() == "core"), + RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(), + RustLibSrcWorkspace::Empty => true, + }; + if !has_core { + let var_note = if env::var_os("RUST_SRC_PATH").is_some() { + " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)" + } else { + ", try running `rustup component add rust-src` to possibly fix this" }; - if !has_core { - let var_note = if env::var_os("RUST_SRC_PATH").is_some() { - " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)" - } else { - ", try running `rustup component add rust-src` to possibly fix this" - }; - self.error = Some(format!( - "sysroot at `{src_root}` is missing a `core` library{var_note}", - )); - } + self.error = + Some(format!("sysroot at `{src_root}` is missing a `core` library{var_note}",)); } } } diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 655da118bb..f031fef165 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -24,7 +24,7 @@ use crate::{ CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package, ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, - build_dependencies::BuildScriptOutput, + build_dependencies::{BuildScriptOutput, ProcMacroDylibPath}, cargo_config_file, cargo_workspace::{CargoMetadataConfig, DepKind, FetchMetadata, PackageData, RustLibSource}, env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env}, @@ -424,12 +424,12 @@ impl ProjectWorkspace { sysroot.set_workspace(loaded_sysroot); } - if !cargo.requires_rustc_private() { - if let Err(e) = &mut rustc { - // We don't need the rustc sources here, - // so just discard the error. - _ = e.take(); - } + if !cargo.requires_rustc_private() + && let Err(e) = &mut rustc + { + // We don't need the rustc sources here, + // so just discard the error. + _ = e.take(); } Ok(ProjectWorkspace { @@ -1163,17 +1163,15 @@ fn project_json_to_crate_graph( crate = display_name.as_ref().map(|name| name.canonical_name().as_str()), "added root to crate graph" ); - if *is_proc_macro { - if let Some(path) = proc_macro_dylib_path.clone() { - let node = Ok(( - display_name - .as_ref() - .map(|it| it.canonical_name().as_str().to_owned()) - .unwrap_or_else(|| format!("crate{}", idx.0)), - path, - )); - proc_macros.insert(crate_graph_crate_id, node); - } + if *is_proc_macro && let Some(path) = proc_macro_dylib_path.clone() { + let node = Ok(( + display_name + .as_ref() + .map(|it| it.canonical_name().as_str().to_owned()) + .unwrap_or_else(|| format!("crate{}", idx.0)), + path, + )); + proc_macros.insert(crate_graph_crate_id, node); } (idx, crate_graph_crate_id) }, @@ -1319,14 +1317,12 @@ fn cargo_to_crate_graph( // Add dep edge of all targets to the package's lib target if let Some((to, name)) = lib_tgt.clone() { - if to != from && kind != TargetKind::BuildScript { - // (build script can not depend on its library target) - - // 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); - add_dep(crate_graph, from, name, to); + match to != from && kind != TargetKind::BuildScript { + true => { + let name = CrateName::normalize_dashes(&name); + add_dep(crate_graph, from, name, to); + } + false => (), } } } @@ -1638,9 +1634,21 @@ fn add_target_crate_root( let proc_macro = match build_data { Some((BuildScriptOutput { proc_macro_dylib_path, .. }, has_errors)) => { match proc_macro_dylib_path { - Some(path) => Ok((cargo_name.to_owned(), path.clone())), - None if has_errors => Err(ProcMacroLoadingError::FailedToBuild), - None => Err(ProcMacroLoadingError::MissingDylibPath), + ProcMacroDylibPath::Path(path) => Ok((cargo_name.to_owned(), path.clone())), + ProcMacroDylibPath::NotBuilt => Err(ProcMacroLoadingError::NotYetBuilt), + ProcMacroDylibPath::NotProcMacro | ProcMacroDylibPath::DylibNotFound(_) + if has_errors => + { + Err(ProcMacroLoadingError::FailedToBuild) + } + ProcMacroDylibPath::NotProcMacro => { + Err(ProcMacroLoadingError::ExpectedProcMacroArtifact) + } + ProcMacroDylibPath::DylibNotFound(candidates) => { + Err(ProcMacroLoadingError::MissingDylibPath( + candidates.iter().map(ToString::to_string).collect(), + )) + } } } None => Err(ProcMacroLoadingError::NotYetBuilt),