use std::{collections::VecDeque, fmt, fs, iter, process::Command, str::FromStr, sync};
use anyhow::{format_err, Context};
use base_db::{
CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
FileId, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, TargetLayoutLoadResult,
};
use cfg::{CfgDiff, CfgOptions};
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
use stdx::always;
use triomphe::Arc;
use crate::{
build_scripts::BuildScriptOutput,
cargo_workspace::{DepKind, PackageData, RustLibSource},
cfg_flag::CfgFlag,
project_json::Crate,
rustc_cfg::{self, RustcCfgConfig},
sysroot::SysrootCrate,
target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath,
Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
};
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct CfgOverrides {
pub global: CfgDiff,
pub selective: FxHashMap<String, CfgDiff>,
}
impl CfgOverrides {
pub fn len(&self) -> usize {
self.global.len() + self.selective.iter().map(|(_, it)| it.len()).sum::<usize>()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PackageRoot {
pub is_local: bool,
pub include: Vec<AbsPathBuf>,
pub exclude: Vec<AbsPathBuf>,
}
#[derive(Clone)]
pub enum ProjectWorkspace {
Cargo {
cargo: CargoWorkspace,
build_scripts: WorkspaceBuildScripts,
sysroot: Result<Sysroot, Option<String>>,
rustc: Result<(CargoWorkspace, WorkspaceBuildScripts), Option<String>>,
rustc_cfg: Vec<CfgFlag>,
cfg_overrides: CfgOverrides,
toolchain: Option<Version>,
target_layout: Result<String, String>,
},
Json {
project: ProjectJson,
sysroot: Result<Sysroot, Option<String>>,
rustc_cfg: Vec<CfgFlag>,
toolchain: Option<Version>,
},
DetachedFiles {
files: Vec<AbsPathBuf>,
sysroot: Result<Sysroot, Option<String>>,
rustc_cfg: Vec<CfgFlag>,
},
}
impl fmt::Debug for ProjectWorkspace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProjectWorkspace::Cargo {
cargo,
build_scripts: _,
sysroot,
rustc,
rustc_cfg,
cfg_overrides,
toolchain,
target_layout: data_layout,
} => f
.debug_struct("Cargo")
.field("root", &cargo.workspace_root().file_name())
.field("n_packages", &cargo.packages().len())
.field("sysroot", &sysroot.is_ok())
.field(
"n_rustc_compiler_crates",
&rustc.as_ref().map_or(0, |(rc, _)| rc.packages().len()),
)
.field("n_rustc_cfg", &rustc_cfg.len())
.field("n_cfg_overrides", &cfg_overrides.len())
.field("toolchain", &toolchain)
.field("data_layout", &data_layout)
.finish(),
ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain } => {
let mut debug_struct = f.debug_struct("Json");
debug_struct.field("n_crates", &project.n_crates());
if let Ok(sysroot) = sysroot {
debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
}
debug_struct.field("toolchain", &toolchain);
debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
debug_struct.finish()
}
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
.debug_struct("DetachedFiles")
.field("n_files", &files.len())
.field("sysroot", &sysroot.is_ok())
.field("n_rustc_cfg", &rustc_cfg.len())
.finish(),
}
}
}
impl ProjectWorkspace {
pub fn load(
manifest: ProjectManifest,
config: &CargoConfig,
progress: &dyn Fn(String),
) -> anyhow::Result<ProjectWorkspace> {
ProjectWorkspace::load_inner(&manifest, config, progress)
.with_context(|| format!("Failed to load the project at {manifest}"))
}
fn load_inner(
manifest: &ProjectManifest,
config: &CargoConfig,
progress: &dyn Fn(String),
) -> anyhow::Result<ProjectWorkspace> {
let version = |current_dir, cmd_path, prefix: &str| {
let cargo_version = utf8_stdout({
let mut cmd = Command::new(cmd_path);
cmd.envs(&config.extra_env);
cmd.arg("--version").current_dir(current_dir);
cmd
})
.with_context(|| format!("Failed to query rust toolchain version at {current_dir}, is your toolchain setup correctly?"))?;
anyhow::Ok(
cargo_version
.get(prefix.len()..)
.and_then(|it| Version::parse(it.split_whitespace().next()?).ok()),
)
};
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}"))?;
let data = serde_json::from_str(&file)
.with_context(|| format!("Failed to deserialize json file {project_json}"))?;
let project_location = project_json.parent().to_path_buf();
let toolchain = version(&*project_location, toolchain::rustc(), "rustc ")?;
let project_json = ProjectJson::new(&project_location, data);
ProjectWorkspace::load_inline(
project_json,
config.target.as_deref(),
&config.extra_env,
toolchain,
)
}
ProjectManifest::CargoToml(cargo_toml) => {
let toolchain = version(cargo_toml.parent(), toolchain::cargo(), "cargo ")?;
let meta = CargoWorkspace::fetch_metadata(
&cargo_toml,
cargo_toml.parent(),
config,
progress,
)
.with_context(|| {
format!(
"Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
)
})?;
let cargo = CargoWorkspace::new(meta);
let sysroot = match (&config.sysroot, &config.sysroot_src) {
(Some(RustLibSource::Path(path)), None) => {
Sysroot::with_sysroot_dir(path.clone()).map_err(|e| {
Some(format!("Failed to find sysroot at {path}:{e}"))
})
}
(Some(RustLibSource::Discover), None) => {
Sysroot::discover(cargo_toml.parent(), &config.extra_env).map_err(|e| {
Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
})
}
(Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone()))
}
(Some(RustLibSource::Discover), Some(sysroot_src)) => {
Sysroot::discover_with_src_override(
cargo_toml.parent(),
&config.extra_env,
sysroot_src.clone(),
).map_err(|e| {
Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
})
}
(None, _) => Err(None),
};
if let Ok(sysroot) = &sysroot {
tracing::info!(workspace = %cargo_toml, src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
}
let rustc_dir = match &config.rustc_source {
Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
.map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
Some(RustLibSource::Discover) => {
sysroot.as_ref().ok().and_then(Sysroot::discover_rustc_src).ok_or_else(
|| Some(format!("Failed to discover rustc source for sysroot.")),
)
}
None => Err(None),
};
let rustc = rustc_dir.and_then(|rustc_dir| {
tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
match CargoWorkspace::fetch_metadata(
&rustc_dir,
cargo_toml.parent(),
&CargoConfig {
features: crate::CargoFeatures::default(),
..config.clone()
},
progress,
) {
Ok(meta) => {
let workspace = CargoWorkspace::new(meta);
let buildscripts = WorkspaceBuildScripts::rustc_crates(
&workspace,
cargo_toml.parent(),
&config.extra_env,
);
Ok((workspace, buildscripts))
}
Err(e) => {
tracing::error!(
%e,
"Failed to read Cargo metadata from rustc source at {rustc_dir}",
);
Err(Some(format!(
"Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}"
)))
}
}
});
let rustc_cfg = rustc_cfg::get(
config.target.as_deref(),
&config.extra_env,
RustcCfgConfig::Cargo(cargo_toml),
);
let cfg_overrides = config.cfg_overrides.clone();
let data_layout = target_data_layout::get(
Some(&cargo_toml),
config.target.as_deref(),
&config.extra_env,
);
if let Err(e) = &data_layout {
tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace");
}
ProjectWorkspace::Cargo {
cargo,
build_scripts: WorkspaceBuildScripts::default(),
sysroot,
rustc,
rustc_cfg,
cfg_overrides,
toolchain,
target_layout: data_layout.map_err(|it| it.to_string()),
}
}
};
Ok(res)
}
pub fn load_inline(
project_json: ProjectJson,
target: Option<&str>,
extra_env: &FxHashMap<String, String>,
toolchain: Option<Version>,
) -> ProjectWorkspace {
let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
(Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src)),
(Some(sysroot), None) => {
let sysroot_src =
sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
Ok(Sysroot::load(sysroot, sysroot_src))
}
(None, Some(sysroot_src)) => {
let mut sysroot = sysroot_src.clone();
for _ in 0..5 {
sysroot.pop();
}
Ok(Sysroot::load(sysroot, sysroot_src))
}
(None, None) => Err(None),
};
let config = match &sysroot {
Ok(sysroot) => {
tracing::debug!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
RustcCfgConfig::Explicit(sysroot)
}
Err(_) => {
tracing::debug!("discovering sysroot");
RustcCfgConfig::Discover
}
};
let rustc_cfg = rustc_cfg::get(target, extra_env, config);
ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg, toolchain }
}
pub fn load_detached_files(
detached_files: Vec<AbsPathBuf>,
config: &CargoConfig,
) -> anyhow::Result<ProjectWorkspace> {
let sysroot = match &config.sysroot {
Some(RustLibSource::Path(path)) => Sysroot::with_sysroot_dir(path.clone())
.map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}"))),
Some(RustLibSource::Discover) => {
let dir = &detached_files
.first()
.and_then(|it| it.parent())
.ok_or_else(|| format_err!("No detached files to load"))?;
Sysroot::discover(dir, &config.extra_env).map_err(|e| {
Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}"))
})
}
None => Err(None),
};
let rustc_config = match &sysroot {
Ok(sysroot) => {
tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
RustcCfgConfig::Explicit(sysroot)
}
Err(_) => {
tracing::info!("discovering sysroot");
RustcCfgConfig::Discover
}
};
let rustc_cfg = rustc_cfg::get(None, &FxHashMap::default(), rustc_config);
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
}
pub fn run_build_scripts(
&self,
config: &CargoConfig,
progress: &dyn Fn(String),
) -> anyhow::Result<WorkspaceBuildScripts> {
match self {
ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
.with_context(|| {
format!("Failed to run build scripts for {}", cargo.workspace_root())
})
}
ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
Ok(WorkspaceBuildScripts::default())
}
}
}
pub fn run_all_build_scripts(
workspaces: &[ProjectWorkspace],
config: &CargoConfig,
progress: &dyn Fn(String),
) -> Vec<anyhow::Result<WorkspaceBuildScripts>> {
if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace)
|| config.run_build_script_command.is_none()
{
return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect();
}
let cargo_ws: Vec<_> = workspaces
.iter()
.filter_map(|it| match it {
ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
_ => None,
})
.collect();
let outputs = &mut match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress) {
Ok(it) => Ok(it.into_iter()),
Err(e) => Err(sync::Arc::new(e)),
};
workspaces
.iter()
.map(|it| match it {
ProjectWorkspace::Cargo { cargo, .. } => match outputs {
Ok(outputs) => Ok(outputs.next().unwrap()),
Err(e) => Err(e.clone()).with_context(|| {
format!("Failed to run build scripts for {}", cargo.workspace_root())
}),
},
_ => Ok(WorkspaceBuildScripts::default()),
})
.collect()
}
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
match self {
ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,
_ => {
always!(bs == WorkspaceBuildScripts::default());
}
}
}
pub fn workspace_definition_path(&self) -> Option<&AbsPath> {
match self {
ProjectWorkspace::Cargo { cargo, .. } => Some(cargo.workspace_root()),
ProjectWorkspace::Json { project, .. } => Some(project.path()),
ProjectWorkspace::DetachedFiles { .. } => None,
}
}
pub fn find_sysroot_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> {
match self {
ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. }
| ProjectWorkspace::Json { sysroot: Ok(sysroot), .. }
| ProjectWorkspace::DetachedFiles { sysroot: Ok(sysroot), .. } => {
let standalone_server_name =
format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
["libexec", "lib"]
.into_iter()
.map(|segment| sysroot.root().join(segment).join(&standalone_server_name))
.find(|server_path| std::fs::metadata(server_path).is_ok())
.ok_or_else(|| {
anyhow::format_err!(
"cannot find proc-macro server in sysroot `{}`",
sysroot.root()
)
})
}
ProjectWorkspace::DetachedFiles { .. } => {
Err(anyhow::format_err!("cannot find proc-macro server, no sysroot was found"))
}
ProjectWorkspace::Cargo { cargo, .. } => Err(anyhow::format_err!(
"cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
cargo.workspace_root()
)),
ProjectWorkspace::Json { project, .. } => Err(anyhow::format_err!(
"cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
project.path()
)),
}
}
pub fn to_roots(&self) -> Vec<PackageRoot> {
let mk_sysroot = |sysroot: Result<&Sysroot, _>, project_root: Option<&AbsPath>| {
sysroot.map(|sysroot| PackageRoot {
is_local: project_root
.map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
include: vec![sysroot.src_root().to_path_buf()],
exclude: Vec::new(),
})
};
match self {
ProjectWorkspace::Json { project, sysroot, rustc_cfg: _, toolchain: _ } => project
.crates()
.map(|(_, krate)| PackageRoot {
is_local: krate.is_workspace_member,
include: krate.include.clone(),
exclude: krate.exclude.clone(),
})
.collect::<FxHashSet<_>>()
.into_iter()
.chain(mk_sysroot(sysroot.as_ref(), Some(project.path())))
.collect::<Vec<_>>(),
ProjectWorkspace::Cargo {
cargo,
sysroot,
rustc,
rustc_cfg: _,
cfg_overrides: _,
build_scripts,
toolchain: _,
target_layout: _,
} => {
cargo
.packages()
.map(|pkg| {
let is_local = cargo[pkg].is_local;
let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
let mut include = vec![pkg_root.clone()];
let out_dir =
build_scripts.get_output(pkg).and_then(|it| it.out_dir.clone());
include.extend(out_dir);
let extra_targets = cargo[pkg]
.targets
.iter()
.filter(|&&tgt| cargo[tgt].kind == TargetKind::Lib)
.filter_map(|&tgt| cargo[tgt].root.parent())
.map(|tgt| tgt.normalize().to_path_buf())
.filter(|path| !path.starts_with(&pkg_root));
include.extend(extra_targets);
let mut exclude = vec![pkg_root.join(".git")];
if is_local {
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_local, include, exclude }
})
.chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root())))
.chain(rustc.iter().flat_map(|(rustc, _)| {
rustc.packages().map(move |krate| PackageRoot {
is_local: false,
include: vec![rustc[krate].manifest.parent().to_path_buf()],
exclude: Vec::new(),
})
}))
.collect()
}
ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
.iter()
.map(|detached_file| PackageRoot {
is_local: true,
include: vec![detached_file.clone()],
exclude: Vec::new(),
})
.chain(mk_sysroot(sysroot.as_ref(), None))
.collect(),
}
}
pub fn n_packages(&self) -> usize {
match self {
ProjectWorkspace::Json { project, sysroot, .. } => {
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
sysroot_package_len + project.n_crates()
}
ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
let rustc_package_len = rustc.as_ref().map_or(0, |(it, _)| it.packages().len());
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
cargo.packages().len() + sysroot_package_len + rustc_package_len
}
ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
sysroot_package_len + files.len()
}
}
}
pub fn to_crate_graph(
&self,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
extra_env: &FxHashMap<String, String>,
) -> (CrateGraph, ProcMacroPaths) {
let _p = profile::span("ProjectWorkspace::to_crate_graph");
let (mut crate_graph, proc_macros) = match self {
ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain } => {
project_json_to_crate_graph(
rustc_cfg.clone(),
load,
project,
sysroot.as_ref().ok(),
extra_env,
Err("rust-project.json projects have no target layout set".into()),
toolchain.as_ref().and_then(|it| ReleaseChannel::from_str(it.pre.as_str())),
)
}
ProjectWorkspace::Cargo {
cargo,
sysroot,
rustc,
rustc_cfg,
cfg_overrides,
build_scripts,
toolchain,
target_layout,
} => cargo_to_crate_graph(
load,
rustc.as_ref().ok(),
cargo,
sysroot.as_ref().ok(),
rustc_cfg.clone(),
cfg_overrides,
None,
build_scripts,
match target_layout.as_ref() {
Ok(it) => Ok(Arc::from(it.as_str())),
Err(it) => Err(Arc::from(it.as_str())),
},
toolchain.as_ref().and_then(|it| ReleaseChannel::from_str(it.pre.as_str())),
),
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
detached_files_to_crate_graph(
rustc_cfg.clone(),
load,
files,
sysroot.as_ref().ok(),
Err("detached file projects have no target layout set".into()),
)
}
};
if crate_graph.patch_cfg_if() {
tracing::debug!("Patched std to depend on cfg-if")
} else {
tracing::debug!("Did not patch std to depend on cfg-if")
}
(crate_graph, proc_macros)
}
pub fn eq_ignore_build_data(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Cargo {
cargo,
sysroot,
rustc,
rustc_cfg,
cfg_overrides,
toolchain,
build_scripts: _,
target_layout: _,
},
Self::Cargo {
cargo: o_cargo,
sysroot: o_sysroot,
rustc: o_rustc,
rustc_cfg: o_rustc_cfg,
cfg_overrides: o_cfg_overrides,
toolchain: o_toolchain,
build_scripts: _,
target_layout: _,
},
) => {
cargo == o_cargo
&& rustc == o_rustc
&& rustc_cfg == o_rustc_cfg
&& cfg_overrides == o_cfg_overrides
&& toolchain == o_toolchain
&& sysroot == o_sysroot
}
(
Self::Json { project, sysroot, rustc_cfg, toolchain },
Self::Json {
project: o_project,
sysroot: o_sysroot,
rustc_cfg: o_rustc_cfg,
toolchain: o_toolchain,
},
) => {
project == o_project
&& rustc_cfg == o_rustc_cfg
&& sysroot == o_sysroot
&& toolchain == o_toolchain
}
(
Self::DetachedFiles { files, sysroot, rustc_cfg },
Self::DetachedFiles { files: o_files, sysroot: o_sysroot, rustc_cfg: o_rustc_cfg },
) => files == o_files && sysroot == o_sysroot && rustc_cfg == o_rustc_cfg,
_ => false,
}
}
#[must_use]
pub fn is_json(&self) -> bool {
matches!(self, Self::Json { .. })
}
}
fn project_json_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
project: &ProjectJson,
sysroot: Option<&Sysroot>,
extra_env: &FxHashMap<String, String>,
target_layout: TargetLayoutLoadResult,
channel: Option<ReleaseChannel>,
) -> (CrateGraph, ProcMacroPaths) {
let mut res = (CrateGraph::default(), ProcMacroPaths::default());
let (crate_graph, proc_macros) = &mut res;
let sysroot_deps = sysroot.as_ref().map(|sysroot| {
sysroot_to_crate_graph(
crate_graph,
sysroot,
rustc_cfg.clone(),
target_layout.clone(),
load,
channel,
)
});
let r_a_cfg_flag = CfgFlag::Atom("rust_analyzer".to_owned());
let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
let crates: FxHashMap<CrateId, CrateId> = project
.crates()
.filter_map(|(crate_id, krate)| Some((crate_id, krate, load(&krate.root_module)?)))
.map(
|(
crate_id,
Crate {
display_name,
edition,
version,
cfg,
target,
env,
proc_macro_dylib_path,
is_proc_macro,
repository,
..
},
file_id,
)| {
let env = env.clone().into_iter().collect();
let target_cfgs = match target.as_deref() {
Some(target) => cfg_cache.entry(target).or_insert_with(|| {
let rustc_cfg = match sysroot {
Some(sysroot) => RustcCfgConfig::Explicit(sysroot),
None => RustcCfgConfig::Discover,
};
rustc_cfg::get(Some(target), extra_env, rustc_cfg)
}),
None => &rustc_cfg,
};
let crate_graph_crate_id = crate_graph.add_crate_root(
file_id,
*edition,
display_name.clone(),
version.clone(),
target_cfgs
.iter()
.chain(cfg.iter())
.chain(iter::once(&r_a_cfg_flag))
.cloned()
.collect(),
None,
env,
*is_proc_macro,
if let Some(name) = display_name.clone() {
CrateOrigin::Local {
repo: repository.clone(),
name: Some(name.canonical_name().to_string()),
}
} else {
CrateOrigin::Local { repo: None, name: None }
},
target_layout.clone(),
channel,
);
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().to_owned()),
path,
));
proc_macros.insert(crate_graph_crate_id, node);
}
}
(crate_id, crate_graph_crate_id)
},
)
.collect();
for (from, krate) in project.crates() {
if let Some(&from) = crates.get(&from) {
if let Some((public_deps, libproc_macro)) = &sysroot_deps {
public_deps.add_to_crate_graph(crate_graph, from);
if let Some(proc_macro) = libproc_macro {
add_proc_macro_dep(crate_graph, from, *proc_macro, krate.is_proc_macro);
}
}
for dep in &krate.deps {
if let Some(&to) = crates.get(&dep.crate_id) {
add_dep(crate_graph, from, dep.name.clone(), to)
}
}
}
}
res
}
fn cargo_to_crate_graph(
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
cargo: &CargoWorkspace,
sysroot: Option<&Sysroot>,
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
forced_cfg: Option<CfgOptions>,
build_scripts: &WorkspaceBuildScripts,
target_layout: TargetLayoutLoadResult,
channel: Option<ReleaseChannel>,
) -> (CrateGraph, ProcMacroPaths) {
let _p = profile::span("cargo_to_crate_graph");
let mut res = (CrateGraph::default(), ProcMacroPaths::default());
let crate_graph = &mut res.0;
let proc_macros = &mut res.1;
let (public_deps, libproc_macro) = match sysroot {
Some(sysroot) => sysroot_to_crate_graph(
crate_graph,
sysroot,
rustc_cfg.clone(),
target_layout.clone(),
load,
channel,
),
None => (SysrootPublicDeps::default(), None),
};
let cfg_options = create_cfg_options(rustc_cfg);
let mut pkg_to_lib_crate = FxHashMap::default();
let mut pkg_crates = FxHashMap::default();
let mut has_private = false;
for pkg in cargo.packages() {
has_private |= cargo[pkg].metadata.rustc_private;
let cfg_options = forced_cfg.clone().unwrap_or_else(|| {
let mut cfg_options = cfg_options.clone();
if cargo[pkg].is_local {
cfg_options.insert_atom("test".into());
}
if cargo[pkg].is_member {
cfg_options.insert_atom("rust_analyzer".into());
}
if !override_cfg.global.is_empty() {
cfg_options.apply_diff(override_cfg.global.clone());
};
if let Some(diff) = override_cfg.selective.get(&cargo[pkg].name) {
cfg_options.apply_diff(diff.clone());
};
cfg_options
});
let mut lib_tgt = None;
for &tgt in cargo[pkg].targets.iter() {
if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
continue;
}
let &TargetData { ref name, kind, is_proc_macro, ref root, .. } = &cargo[tgt];
if kind == TargetKind::Lib
&& sysroot.map_or(false, |sysroot| root.starts_with(sysroot.src_root()))
{
if let Some(&(_, crate_id, _)) =
public_deps.deps.iter().find(|(dep_name, ..)| dep_name.as_smol_str() == name)
{
pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, kind));
lib_tgt = Some((crate_id, name.clone()));
pkg_to_lib_crate.insert(pkg, crate_id);
continue;
}
}
let Some(file_id) = load(root) else { continue };
let crate_id = add_target_crate_root(
crate_graph,
proc_macros,
&cargo[pkg],
build_scripts.get_output(pkg),
cfg_options.clone(),
file_id,
name,
is_proc_macro,
target_layout.clone(),
false,
channel,
);
if kind == TargetKind::Lib {
lib_tgt = Some((crate_id, name.clone()));
pkg_to_lib_crate.insert(pkg, crate_id);
}
if let Some(proc_macro) = libproc_macro {
add_proc_macro_dep(crate_graph, crate_id, proc_macro, is_proc_macro);
}
pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, kind));
}
for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
public_deps.add_to_crate_graph(crate_graph, from);
if let Some((to, name)) = lib_tgt.clone() {
if to != from && kind != TargetKind::BuildScript {
let name = CrateName::normalize_dashes(&name);
add_dep(crate_graph, from, name, to);
}
}
}
}
for pkg in cargo.packages() {
for dep in &cargo[pkg].dependencies {
let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) else { continue };
let Some(targets) = pkg_crates.get(&pkg) else { continue };
let name = CrateName::new(&dep.name).unwrap();
for &(from, kind) in targets {
if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) {
continue;
}
add_dep(crate_graph, from, name.clone(), to)
}
}
}
if has_private {
if let Some((rustc_workspace, rustc_build_scripts)) = rustc {
handle_rustc_crates(
crate_graph,
proc_macros,
&mut pkg_to_lib_crate,
load,
rustc_workspace,
cargo,
&public_deps,
libproc_macro,
&pkg_crates,
&cfg_options,
override_cfg,
if rustc_workspace.workspace_root() == cargo.workspace_root() {
build_scripts
} else {
rustc_build_scripts
},
target_layout,
channel,
);
}
}
res
}
fn detached_files_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
detached_files: &[AbsPathBuf],
sysroot: Option<&Sysroot>,
target_layout: TargetLayoutLoadResult,
) -> (CrateGraph, ProcMacroPaths) {
let _p = profile::span("detached_files_to_crate_graph");
let mut crate_graph = CrateGraph::default();
let (public_deps, _libproc_macro) = match sysroot {
Some(sysroot) => sysroot_to_crate_graph(
&mut crate_graph,
sysroot,
rustc_cfg.clone(),
target_layout.clone(),
load,
None,
),
None => (SysrootPublicDeps::default(), None),
};
let mut cfg_options = create_cfg_options(rustc_cfg);
cfg_options.insert_atom("rust_analyzer".into());
for detached_file in detached_files {
let file_id = match load(detached_file) {
Some(file_id) => file_id,
None => {
tracing::error!("Failed to load detached file {:?}", detached_file);
continue;
}
};
let display_name = detached_file
.file_stem()
.and_then(|os_str| os_str.to_str())
.map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
let detached_file_crate = crate_graph.add_crate_root(
file_id,
Edition::CURRENT,
display_name.clone(),
None,
cfg_options.clone(),
None,
Env::default(),
false,
CrateOrigin::Local {
repo: None,
name: display_name.map(|n| n.canonical_name().to_string()),
},
target_layout.clone(),
None,
);
public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate);
}
(crate_graph, FxHashMap::default())
}
fn handle_rustc_crates(
crate_graph: &mut CrateGraph,
proc_macros: &mut ProcMacroPaths,
pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
rustc_workspace: &CargoWorkspace,
cargo: &CargoWorkspace,
public_deps: &SysrootPublicDeps,
libproc_macro: Option<CrateId>,
pkg_crates: &FxHashMap<Package, Vec<(CrateId, TargetKind)>>,
cfg_options: &CfgOptions,
override_cfg: &CfgOverrides,
build_scripts: &WorkspaceBuildScripts,
target_layout: TargetLayoutLoadResult,
channel: Option<ReleaseChannel>,
) {
let mut rustc_pkg_crates = FxHashMap::default();
let root_pkg =
rustc_workspace.packages().find(|&package| rustc_workspace[package].name == "rustc_driver");
if let Some(root_pkg) = root_pkg {
let mut queue = VecDeque::new();
queue.push_back(root_pkg);
while let Some(pkg) = queue.pop_front() {
if rustc_pkg_crates.contains_key(&pkg) {
continue;
}
for dep in &rustc_workspace[pkg].dependencies {
queue.push_back(dep.pkg);
}
let mut cfg_options = cfg_options.clone();
if !override_cfg.global.is_empty() {
cfg_options.apply_diff(override_cfg.global.clone());
};
if let Some(diff) = override_cfg.selective.get(&rustc_workspace[pkg].name) {
cfg_options.apply_diff(diff.clone());
};
for &tgt in rustc_workspace[pkg].targets.iter() {
if rustc_workspace[tgt].kind != TargetKind::Lib {
continue;
}
if let Some(file_id) = load(&rustc_workspace[tgt].root) {
let crate_id = add_target_crate_root(
crate_graph,
proc_macros,
&rustc_workspace[pkg],
build_scripts.get_output(pkg),
cfg_options.clone(),
file_id,
&rustc_workspace[tgt].name,
rustc_workspace[tgt].is_proc_macro,
target_layout.clone(),
true,
channel,
);
pkg_to_lib_crate.insert(pkg, crate_id);
public_deps.add_to_crate_graph(crate_graph, crate_id);
if let Some(proc_macro) = libproc_macro {
add_proc_macro_dep(
crate_graph,
crate_id,
proc_macro,
rustc_workspace[tgt].is_proc_macro,
);
}
rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
}
}
}
}
for pkg in rustc_pkg_crates.keys().copied() {
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() {
add_dep(crate_graph, from, name.clone(), to);
}
}
}
}
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() {
let package = &cargo[pkg];
if !package.metadata.rustc_private {
continue;
}
for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
add_dep(crate_graph, *from, name.clone(), to);
}
}
}
}
}
}
fn add_target_crate_root(
crate_graph: &mut CrateGraph,
proc_macros: &mut ProcMacroPaths,
pkg: &PackageData,
build_data: Option<&BuildScriptOutput>,
cfg_options: CfgOptions,
file_id: FileId,
cargo_name: &str,
is_proc_macro: bool,
target_layout: TargetLayoutLoadResult,
rustc_crate: bool,
channel: Option<ReleaseChannel>,
) -> CrateId {
let edition = pkg.edition;
let potential_cfg_options = if pkg.features.is_empty() {
None
} else {
let mut potential_cfg_options = cfg_options.clone();
potential_cfg_options.extend(
pkg.features
.iter()
.map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
);
Some(potential_cfg_options)
};
let cfg_options = {
let mut opts = cfg_options;
for feature in pkg.active_features.iter() {
opts.insert_key_value("feature".into(), feature.into());
}
if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
opts.extend(cfgs.iter().cloned());
}
opts
};
let mut env = Env::default();
inject_cargo_env(pkg, &mut env);
if let Ok(cname) = String::from_str(cargo_name) {
env.set("CARGO_CRATE_NAME", cname.replace("-", "_"));
}
if let Some(envs) = build_data.map(|it| &it.envs) {
for (k, v) in envs {
env.set(k, v.clone());
}
}
let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
let crate_id = crate_graph.add_crate_root(
file_id,
edition,
Some(display_name),
Some(pkg.version.to_string()),
cfg_options,
potential_cfg_options,
env,
is_proc_macro,
if rustc_crate {
CrateOrigin::Rustc { name: pkg.name.clone() }
} else if pkg.is_member {
CrateOrigin::Local { repo: pkg.repository.clone(), name: Some(pkg.name.clone()) }
} else {
CrateOrigin::Library { repo: pkg.repository.clone(), name: pkg.name.clone() }
},
target_layout,
channel,
);
if is_proc_macro {
let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) {
Some(it) => it.cloned().map(|path| Ok((Some(cargo_name.to_owned()), path))),
None => Some(Err("crate has not yet been built".to_owned())),
};
if let Some(proc_macro) = proc_macro {
proc_macros.insert(crate_id, proc_macro);
}
}
crate_id
}
#[derive(Default)]
struct SysrootPublicDeps {
deps: Vec<(CrateName, CrateId, bool)>,
}
impl SysrootPublicDeps {
fn add_to_crate_graph(&self, crate_graph: &mut CrateGraph, from: CrateId) {
for (name, krate, prelude) in &self.deps {
add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
}
}
}
fn sysroot_to_crate_graph(
crate_graph: &mut CrateGraph,
sysroot: &Sysroot,
rustc_cfg: Vec<CfgFlag>,
target_layout: TargetLayoutLoadResult,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
channel: Option<ReleaseChannel>,
) -> (SysrootPublicDeps, Option<CrateId>) {
let _p = profile::span("sysroot_to_crate_graph");
let cfg_options = create_cfg_options(rustc_cfg.clone());
let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = match &sysroot.hack_cargo_workspace {
Some(cargo) => handle_hack_cargo_workspace(
load,
cargo,
rustc_cfg,
cfg_options,
target_layout,
channel,
crate_graph,
sysroot,
),
None => sysroot
.crates()
.filter_map(|krate| {
let file_id = load(&sysroot[krate].root)?;
let env = Env::default();
let display_name =
CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
let crate_id = crate_graph.add_crate_root(
file_id,
Edition::CURRENT,
Some(display_name),
None,
cfg_options.clone(),
None,
env,
false,
CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
target_layout.clone(),
channel,
);
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)) {
add_dep(crate_graph, from, name, to);
}
}
}
let public_deps = SysrootPublicDeps {
deps: sysroot
.public_deps()
.map(|(name, idx, prelude)| (name, sysroot_crates[&idx], prelude))
.collect::<Vec<_>>(),
};
let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
(public_deps, libproc_macro)
}
fn handle_hack_cargo_workspace(
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
cargo: &CargoWorkspace,
rustc_cfg: Vec<CfgFlag>,
cfg_options: CfgOptions,
target_layout: Result<Arc<str>, Arc<str>>,
channel: Option<ReleaseChannel>,
crate_graph: &mut CrateGraph,
sysroot: &Sysroot,
) -> FxHashMap<SysrootCrate, CrateId> {
let (cg, mut pm) = cargo_to_crate_graph(
load,
None,
cargo,
None,
rustc_cfg,
&CfgOverrides::default(),
Some(cfg_options),
&WorkspaceBuildScripts::default(),
target_layout,
channel,
);
crate_graph.extend(cg, &mut pm);
for crate_name in ["std", "alloc", "core"] {
let original = crate_graph
.iter()
.find(|x| {
crate_graph[*x]
.display_name
.as_ref()
.map(|x| x.canonical_name() == crate_name)
.unwrap_or(false)
})
.unwrap();
let fake_crate_name = format!("rustc-std-workspace-{}", crate_name);
let fake = crate_graph
.iter()
.find(|x| {
crate_graph[*x]
.display_name
.as_ref()
.map(|x| x.canonical_name() == fake_crate_name)
.unwrap_or(false)
})
.unwrap();
crate_graph.remove_and_replace(fake, original).unwrap();
}
for (_, c) in crate_graph.iter_mut() {
if c.origin.is_local() {
c.origin = CrateOrigin::Lang(LangCrateOrigin::Other);
}
}
sysroot
.crates()
.filter_map(|krate| {
let file_id = load(&sysroot[krate].root)?;
let crate_id = crate_graph.crate_id_for_crate_root(file_id)?;
Some((krate, crate_id))
})
.collect()
}
fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
add_dep_inner(graph, from, Dependency::new(name, to))
}
fn add_dep_with_prelude(
graph: &mut CrateGraph,
from: CrateId,
name: CrateName,
to: CrateId,
prelude: bool,
) {
add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
}
fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) {
add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude);
}
fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
if let Err(err) = graph.add_dep(from, dep) {
tracing::error!("{}", err)
}
}
fn inject_cargo_env(package: &PackageData, env: &mut Env) {
let manifest_dir = package.manifest.parent();
env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned());
env.set("CARGO", "cargo".into());
env.set("CARGO_PKG_VERSION", package.version.to_string());
env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string());
env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string());
env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string());
env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string());
env.set("CARGO_PKG_AUTHORS", String::new());
env.set("CARGO_PKG_NAME", package.name.clone());
env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string());
env.set("CARGO_PKG_DESCRIPTION", String::new());
env.set("CARGO_PKG_HOMEPAGE", String::new());
env.set("CARGO_PKG_REPOSITORY", String::new());
env.set("CARGO_PKG_LICENSE", String::new());
env.set("CARGO_PKG_LICENSE_FILE", String::new());
}
fn create_cfg_options(rustc_cfg: Vec<CfgFlag>) -> CfgOptions {
let mut cfg_options = CfgOptions::default();
cfg_options.extend(rustc_cfg);
cfg_options.insert_atom("debug_assertions".into());
cfg_options
}