Auto merge of #17118 - Veykril:linked-rust-files, r=Veykril

feat: Allow rust files to be used linkedProjects

With this, script files become more usable as the user can at least add them manually to the linked projects, allowing them to be used "on the (manual) fly" without having to open a separate vscode window that only has files open and no folder.

Also makes build scripts work for them (though no proc-macros, for some reason the dylib field is not populated in the output)
This commit is contained in:
bors 2024-04-21 14:51:51 +00:00
commit f512a5e95c
21 changed files with 203 additions and 180 deletions

View file

@ -51,6 +51,7 @@ pub trait FileLoader {
/// Text of the file. /// Text of the file.
fn file_text(&self, file_id: FileId) -> Arc<str>; fn file_text(&self, file_id: FileId) -> Arc<str>;
fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>; fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>;
/// Crates whose root's source root is the same as the source root of `file_id`
fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]>; fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]>;
} }
@ -104,6 +105,7 @@ pub trait SourceDatabaseExt: SourceDatabase {
#[salsa::input] #[salsa::input]
fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>; fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>;
/// Crates whose root fool is in `id`.
fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>; fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>;
} }

View file

@ -121,7 +121,7 @@ impl SourceToDefCtx<'_, '_> {
let _p = tracing::span!(tracing::Level::INFO, "SourceBinder::file_to_module_def").entered(); let _p = tracing::span!(tracing::Level::INFO, "SourceBinder::file_to_module_def").entered();
let mut mods = SmallVec::new(); let mut mods = SmallVec::new();
for &crate_id in self.db.relevant_crates(file).iter() { for &crate_id in self.db.relevant_crates(file).iter() {
// FIXME: inner items // Note: `mod` declarations in block modules cannot be supported here
let crate_def_map = self.db.crate_def_map(crate_id); let crate_def_map = self.db.crate_def_map(crate_id);
mods.extend( mods.extend(
crate_def_map crate_def_map
@ -129,6 +129,9 @@ impl SourceToDefCtx<'_, '_> {
.map(|local_id| crate_def_map.module_id(local_id)), .map(|local_id| crate_def_map.module_id(local_id)),
) )
} }
if mods.is_empty() {
// FIXME: detached file
}
mods mods
} }

View file

@ -1,6 +1,6 @@
//! rust-analyzer is lazy and doesn't compute anything unless asked. This //! rust-analyzer is lazy and doesn't compute anything unless asked. This
//! sometimes is counter productive when, for example, the first goto definition //! sometimes is counter productive when, for example, the first goto definition
//! request takes longer to compute. This modules implemented prepopulation of //! request takes longer to compute. This module implements prepopulation of
//! various caches, it's not really advanced at the moment. //! various caches, it's not really advanced at the moment.
mod topologic_sort; mod topologic_sort;

View file

@ -24,7 +24,7 @@ use toolchain::Tool;
use crate::{ use crate::{
cfg::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, cfg::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
InvocationStrategy, Package, Sysroot, TargetKind, InvocationStrategy, ManifestPath, Package, Sysroot, TargetKind,
}; };
/// Output of the build script and proc-macro building steps for a workspace. /// Output of the build script and proc-macro building steps for a workspace.
@ -63,7 +63,7 @@ impl WorkspaceBuildScripts {
fn build_command( fn build_command(
config: &CargoConfig, config: &CargoConfig,
allowed_features: &FxHashSet<String>, allowed_features: &FxHashSet<String>,
workspace_root: &AbsPathBuf, manifest_path: &ManifestPath,
sysroot: Option<&Sysroot>, sysroot: Option<&Sysroot>,
) -> io::Result<Command> { ) -> io::Result<Command> {
let mut cmd = match config.run_build_script_command.as_deref() { let mut cmd = match config.run_build_script_command.as_deref() {
@ -79,7 +79,7 @@ impl WorkspaceBuildScripts {
cmd.args(&config.extra_args); cmd.args(&config.extra_args);
cmd.arg("--manifest-path"); cmd.arg("--manifest-path");
cmd.arg(workspace_root.join("Cargo.toml")); cmd.arg(manifest_path.as_ref());
if let Some(target_dir) = &config.target_dir { if let Some(target_dir) = &config.target_dir {
cmd.arg("--target-dir").arg(target_dir); cmd.arg("--target-dir").arg(target_dir);
@ -116,6 +116,10 @@ impl WorkspaceBuildScripts {
} }
} }
if manifest_path.extension().map_or(false, |ext| ext == "rs") {
cmd.arg("-Zscript");
}
cmd cmd
} }
}; };
@ -152,37 +156,12 @@ impl WorkspaceBuildScripts {
.as_ref(); .as_ref();
let allowed_features = workspace.workspace_features(); let allowed_features = workspace.workspace_features();
let mut cmd =
match Self::run_per_ws( Self::build_command(config, &allowed_features, workspace.manifest_path(), sysroot)?;
Self::build_command( if toolchain.as_ref().map_or(false, |it| *it >= RUST_1_75) {
config, cmd.args(["--keep-going"]);
&allowed_features,
&workspace.workspace_root().to_path_buf(),
sysroot,
)?,
workspace,
current_dir,
progress,
) {
Ok(WorkspaceBuildScripts { error: Some(error), .. })
if toolchain.as_ref().map_or(false, |it| *it >= RUST_1_75) =>
{
// building build scripts failed, attempt to build with --keep-going so
// that we potentially get more build data
let mut cmd = Self::build_command(
config,
&allowed_features,
&workspace.workspace_root().to_path_buf(),
sysroot,
)?;
cmd.args(["--keep-going"]);
let mut res = Self::run_per_ws(cmd, workspace, current_dir, progress)?;
res.error = Some(error);
Ok(res)
}
res => res,
} }
Self::run_per_ws(cmd, workspace, current_dir, progress)
} }
/// Runs the build scripts by invoking the configured command *once*. /// Runs the build scripts by invoking the configured command *once*.
@ -204,7 +183,13 @@ impl WorkspaceBuildScripts {
)) ))
} }
}; };
let cmd = Self::build_command(config, &Default::default(), workspace_root, None)?; let cmd = Self::build_command(
config,
&Default::default(),
// This is not gonna be used anyways, so just construct a dummy here
&ManifestPath::try_from(workspace_root.clone()).unwrap(),
None,
)?;
// NB: Cargo.toml could have been modified between `cargo metadata` and // NB: Cargo.toml could have been modified between `cargo metadata` and
// `cargo check`. We shouldn't assume that package ids we see here are // `cargo check`. We shouldn't assume that package ids we see here are
// exactly those from `config`. // exactly those from `config`.

View file

@ -32,6 +32,7 @@ pub struct CargoWorkspace {
targets: Arena<TargetData>, targets: Arena<TargetData>,
workspace_root: AbsPathBuf, workspace_root: AbsPathBuf,
target_directory: AbsPathBuf, target_directory: AbsPathBuf,
manifest_path: ManifestPath,
} }
impl ops::Index<Package> for CargoWorkspace { impl ops::Index<Package> for CargoWorkspace {
@ -334,7 +335,7 @@ impl CargoWorkspace {
.with_context(|| format!("Failed to run `{:?}`", meta.cargo_command())) .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))
} }
pub fn new(mut meta: cargo_metadata::Metadata) -> CargoWorkspace { pub fn new(mut meta: cargo_metadata::Metadata, manifest_path: ManifestPath) -> CargoWorkspace {
let mut pkg_by_id = FxHashMap::default(); let mut pkg_by_id = FxHashMap::default();
let mut packages = Arena::default(); let mut packages = Arena::default();
let mut targets = Arena::default(); let mut targets = Arena::default();
@ -448,7 +449,7 @@ 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 } CargoWorkspace { packages, targets, workspace_root, target_directory, manifest_path }
} }
pub fn packages(&self) -> impl ExactSizeIterator<Item = Package> + '_ { pub fn packages(&self) -> impl ExactSizeIterator<Item = Package> + '_ {
@ -466,6 +467,10 @@ impl CargoWorkspace {
&self.workspace_root &self.workspace_root
} }
pub fn manifest_path(&self) -> &ManifestPath {
&self.manifest_path
}
pub fn target_directory(&self) -> &AbsPath { pub fn target_directory(&self) -> &AbsPath {
&self.target_directory &self.target_directory
} }

View file

@ -54,11 +54,13 @@ pub use crate::{
sysroot::Sysroot, sysroot::Sysroot,
workspace::{FileLoader, PackageRoot, ProjectWorkspace}, workspace::{FileLoader, PackageRoot, ProjectWorkspace},
}; };
pub use cargo_metadata::Metadata;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ProjectManifest { pub enum ProjectManifest {
ProjectJson(ManifestPath), ProjectJson(ManifestPath),
CargoToml(ManifestPath), CargoToml(ManifestPath),
CargoScript(ManifestPath),
} }
impl ProjectManifest { impl ProjectManifest {
@ -71,7 +73,10 @@ impl ProjectManifest {
if path.file_name().unwrap_or_default() == "Cargo.toml" { if path.file_name().unwrap_or_default() == "Cargo.toml" {
return Ok(ProjectManifest::CargoToml(path)); return Ok(ProjectManifest::CargoToml(path));
} }
bail!("project root must point to Cargo.toml or rust-project.json: {path}"); if path.extension().unwrap_or_default() == "rs" {
return Ok(ProjectManifest::CargoScript(path));
}
bail!("project root must point to a Cargo.toml, rust-project.json or <script>.rs file: {path}");
} }
pub fn discover_single(path: &AbsPath) -> anyhow::Result<ProjectManifest> { pub fn discover_single(path: &AbsPath) -> anyhow::Result<ProjectManifest> {
@ -146,15 +151,19 @@ impl ProjectManifest {
res.sort(); res.sort();
res res
} }
pub fn manifest_path(&self) -> &ManifestPath {
match self {
ProjectManifest::ProjectJson(it)
| ProjectManifest::CargoToml(it)
| ProjectManifest::CargoScript(it) => it,
}
}
} }
impl fmt::Display for ProjectManifest { impl fmt::Display for ProjectManifest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { fmt::Display::fmt(self.manifest_path(), f)
ProjectManifest::ProjectJson(it) | ProjectManifest::CargoToml(it) => {
fmt::Display::fmt(&it, f)
}
}
} }
} }

View file

@ -1,5 +1,5 @@
//! See [`ManifestPath`]. //! See [`ManifestPath`].
use std::{fmt, ops, path::Path}; use std::{borrow::Borrow, fmt, ops};
use paths::{AbsPath, AbsPathBuf}; use paths::{AbsPath, AbsPathBuf};
@ -54,8 +54,14 @@ impl ops::Deref for ManifestPath {
} }
} }
impl AsRef<Path> for ManifestPath { impl AsRef<AbsPath> for ManifestPath {
fn as_ref(&self) -> &Path { fn as_ref(&self) -> &AbsPath {
self.file.as_ref() self.file.as_ref()
} }
} }
impl Borrow<AbsPath> for ManifestPath {
fn borrow(&self) -> &AbsPath {
self.file.borrow()
}
}

View file

@ -349,7 +349,7 @@ impl Sysroot {
.filter(|&package| RELEVANT_SYSROOT_CRATES.contains(&&*package.name)) .filter(|&package| RELEVANT_SYSROOT_CRATES.contains(&&*package.name))
.map(|package| package.id.clone()) .map(|package| package.id.clone())
.collect(); .collect();
let cargo_workspace = CargoWorkspace::new(res); let cargo_workspace = CargoWorkspace::new(res, sysroot_cargo_toml);
Some(Sysroot { Some(Sysroot {
root: sysroot_dir.clone(), root: sysroot_dir.clone(),
src_root: Some(Ok(sysroot_src_dir.clone())), src_root: Some(Ok(sysroot_src_dir.clone())),
@ -368,7 +368,7 @@ impl Sysroot {
.into_iter() .into_iter()
.map(|it| sysroot_src_dir.join(it)) .map(|it| sysroot_src_dir.join(it))
.filter_map(|it| ManifestPath::try_from(it).ok()) .filter_map(|it| ManifestPath::try_from(it).ok())
.find(|it| fs::metadata(it).is_ok()); .find(|it| fs::metadata(it.as_ref()).is_ok());
if let Some(root) = root { if let Some(root) = root {
stitched.crates.alloc(SysrootCrateData { stitched.crates.alloc(SysrootCrateData {
@ -468,7 +468,7 @@ fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml"); let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
let rustc_src = ManifestPath::try_from(rustc_src).ok()?; let rustc_src = ManifestPath::try_from(rustc_src).ok()?;
tracing::debug!("checking for rustc source code: {rustc_src}"); tracing::debug!("checking for rustc source code: {rustc_src}");
if fs::metadata(&rustc_src).is_ok() { if fs::metadata(rustc_src.as_ref()).is_ok() {
Some(rustc_src) Some(rustc_src)
} else { } else {
None None

View file

@ -1,6 +1,7 @@
use std::ops::Deref; use std::ops::Deref;
use base_db::{CrateGraph, FileId, ProcMacroPaths}; use base_db::{CrateGraph, FileId, ProcMacroPaths};
use cargo_metadata::Metadata;
use cfg::{CfgAtom, CfgDiff}; use cfg::{CfgAtom, CfgDiff};
use expect_test::{expect_file, ExpectFile}; use expect_test::{expect_file, ExpectFile};
use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
@ -9,8 +10,8 @@ use serde::de::DeserializeOwned;
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot, CargoWorkspace, CfgOverrides, ManifestPath, ProjectJson, ProjectJsonData, ProjectWorkspace,
WorkspaceBuildScripts, Sysroot, WorkspaceBuildScripts,
}; };
fn load_cargo(file: &str) -> (CrateGraph, ProcMacroPaths) { fn load_cargo(file: &str) -> (CrateGraph, ProcMacroPaths) {
@ -21,8 +22,10 @@ fn load_cargo_with_overrides(
file: &str, file: &str,
cfg_overrides: CfgOverrides, cfg_overrides: CfgOverrides,
) -> (CrateGraph, ProcMacroPaths) { ) -> (CrateGraph, ProcMacroPaths) {
let meta = get_test_json_file(file); let meta: Metadata = get_test_json_file(file);
let cargo_workspace = CargoWorkspace::new(meta); let manifest_path =
ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
let project_workspace = ProjectWorkspace::Cargo { let project_workspace = ProjectWorkspace::Cargo {
cargo: cargo_workspace, cargo: cargo_workspace,
build_scripts: WorkspaceBuildScripts::default(), build_scripts: WorkspaceBuildScripts::default(),
@ -41,8 +44,10 @@ fn load_cargo_with_fake_sysroot(
file_map: &mut FxHashMap<AbsPathBuf, FileId>, file_map: &mut FxHashMap<AbsPathBuf, FileId>,
file: &str, file: &str,
) -> (CrateGraph, ProcMacroPaths) { ) -> (CrateGraph, ProcMacroPaths) {
let meta = get_test_json_file(file); let meta: Metadata = get_test_json_file(file);
let cargo_workspace = CargoWorkspace::new(meta); let manifest_path =
ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
let project_workspace = ProjectWorkspace::Cargo { let project_workspace = ProjectWorkspace::Cargo {
cargo: cargo_workspace, cargo: cargo_workspace,
build_scripts: WorkspaceBuildScripts::default(), build_scripts: WorkspaceBuildScripts::default(),
@ -268,9 +273,10 @@ fn smoke_test_real_sysroot_cargo() {
return; return;
} }
let file_map = &mut FxHashMap::<AbsPathBuf, FileId>::default(); let file_map = &mut FxHashMap::<AbsPathBuf, FileId>::default();
let meta = get_test_json_file("hello-world-metadata.json"); let meta: Metadata = get_test_json_file("hello-world-metadata.json");
let manifest_path =
let cargo_workspace = CargoWorkspace::new(meta); ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
let sysroot = Ok(Sysroot::discover( let sysroot = Ok(Sysroot::discover(
AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))), AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))),
&Default::default(), &Default::default(),

View file

@ -4,7 +4,7 @@
use std::{collections::VecDeque, fmt, fs, iter, sync}; use std::{collections::VecDeque, fmt, fs, iter, sync};
use anyhow::{format_err, Context}; use anyhow::Context;
use base_db::{ use base_db::{
CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Env, FileId, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Env, FileId,
LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult, LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult,
@ -14,7 +14,6 @@ use paths::{AbsPath, AbsPathBuf};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version; use semver::Version;
use span::Edition; use span::Edition;
use stdx::always;
use toolchain::Tool; use toolchain::Tool;
use triomphe::Arc; use triomphe::Arc;
@ -101,7 +100,7 @@ pub enum ProjectWorkspace {
/// Backed by basic sysroot crates for basic completion and highlighting. /// Backed by basic sysroot crates for basic completion and highlighting.
DetachedFile { DetachedFile {
/// The file in question. /// The file in question.
file: AbsPathBuf, file: ManifestPath,
/// The sysroot loaded for this workspace. /// The sysroot loaded for this workspace.
sysroot: Result<Sysroot, Option<String>>, sysroot: Result<Sysroot, Option<String>>,
/// Holds cfg flags for the current target. We get those by running /// Holds cfg flags for the current target. We get those by running
@ -116,7 +115,7 @@ pub enum ProjectWorkspace {
/// A set of cfg overrides for the files. /// A set of cfg overrides for the files.
cfg_overrides: CfgOverrides, cfg_overrides: CfgOverrides,
/// Is this file a cargo script file? /// Is this file a cargo script file?
cargo_script: Option<CargoWorkspace>, cargo_script: Option<(CargoWorkspace, WorkspaceBuildScripts)>,
}, },
} }
@ -230,7 +229,7 @@ impl ProjectWorkspace {
) -> anyhow::Result<ProjectWorkspace> { ) -> anyhow::Result<ProjectWorkspace> {
let res = match manifest { let res = match manifest {
ProjectManifest::ProjectJson(project_json) => { ProjectManifest::ProjectJson(project_json) => {
let file = fs::read_to_string(project_json) let file = fs::read_to_string(project_json.as_ref())
.with_context(|| format!("Failed to read json file {project_json}"))?; .with_context(|| format!("Failed to read json file {project_json}"))?;
let data = serde_json::from_str(&file) let data = serde_json::from_str(&file)
.with_context(|| format!("Failed to deserialize json file {project_json}"))?; .with_context(|| format!("Failed to deserialize json file {project_json}"))?;
@ -243,6 +242,9 @@ impl ProjectWorkspace {
&config.cfg_overrides, &config.cfg_overrides,
) )
} }
ProjectManifest::CargoScript(rust_file) => {
ProjectWorkspace::load_detached_file(rust_file, config)?
}
ProjectManifest::CargoToml(cargo_toml) => { ProjectManifest::CargoToml(cargo_toml) => {
let sysroot = match (&config.sysroot, &config.sysroot_src) { let sysroot = match (&config.sysroot, &config.sysroot_src) {
(Some(RustLibSource::Path(path)), None) => { (Some(RustLibSource::Path(path)), None) => {
@ -299,7 +301,7 @@ impl ProjectWorkspace {
progress, progress,
) { ) {
Ok(meta) => { Ok(meta) => {
let workspace = CargoWorkspace::new(meta); let workspace = CargoWorkspace::new(meta, cargo_toml.clone());
let buildscripts = WorkspaceBuildScripts::rustc_crates( let buildscripts = WorkspaceBuildScripts::rustc_crates(
&workspace, &workspace,
cargo_toml.parent(), cargo_toml.parent(),
@ -355,7 +357,7 @@ impl ProjectWorkspace {
"Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}", "Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
) )
})?; })?;
let cargo = CargoWorkspace::new(meta); let cargo = CargoWorkspace::new(meta, cargo_toml.clone());
let cargo_config_extra_env = let cargo_config_extra_env =
cargo_config_env(cargo_toml, &config.extra_env, sysroot_ref); cargo_config_env(cargo_toml, &config.extra_env, sysroot_ref);
@ -433,82 +435,71 @@ impl ProjectWorkspace {
} }
} }
pub fn load_detached_file(
detached_file: &ManifestPath,
config: &CargoConfig,
) -> anyhow::Result<ProjectWorkspace> {
let dir = detached_file.parent();
let sysroot = match &config.sysroot {
Some(RustLibSource::Path(path)) => {
Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata)
.map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}")))
}
Some(RustLibSource::Discover) => Sysroot::discover(
dir,
&config.extra_env,
config.sysroot_query_metadata,
)
.map_err(|e| {
Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}"))
}),
None => Err(None),
};
let sysroot_ref = sysroot.as_ref().ok();
let toolchain =
match get_toolchain_version(dir, sysroot_ref, Tool::Rustc, &config.extra_env, "rustc ")
{
Ok(it) => it,
Err(e) => {
tracing::error!("{e}");
None
}
};
let rustc_cfg = rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(sysroot_ref));
let data_layout = target_data_layout::get(
RustcDataLayoutConfig::Rustc(sysroot_ref),
None,
&config.extra_env,
);
let cargo_script =
CargoWorkspace::fetch_metadata(detached_file, dir, config, sysroot_ref, &|_| ())
.ok()
.map(|ws| {
(
CargoWorkspace::new(ws, detached_file.clone()),
WorkspaceBuildScripts::default(),
)
});
Ok(ProjectWorkspace::DetachedFile {
file: detached_file.to_owned(),
sysroot,
rustc_cfg,
toolchain,
target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
cfg_overrides: config.cfg_overrides.clone(),
cargo_script,
})
}
pub fn load_detached_files( pub fn load_detached_files(
detached_files: Vec<AbsPathBuf>, detached_files: Vec<ManifestPath>,
config: &CargoConfig, config: &CargoConfig,
) -> Vec<anyhow::Result<ProjectWorkspace>> { ) -> Vec<anyhow::Result<ProjectWorkspace>> {
detached_files detached_files.into_iter().map(|file| Self::load_detached_file(&file, config)).collect()
.into_iter()
.map(|detached_file| {
let dir = detached_file
.parent()
.ok_or_else(|| format_err!("detached file has no parent"))?;
let sysroot = match &config.sysroot {
Some(RustLibSource::Path(path)) => {
Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata)
.map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}")))
}
Some(RustLibSource::Discover) => {
Sysroot::discover(dir, &config.extra_env, config.sysroot_query_metadata)
.map_err(|e| {
Some(format!(
"Failed to find sysroot for {dir}. Is rust-src installed? {e}"
))
})
}
None => Err(None),
};
let sysroot_ref = sysroot.as_ref().ok();
let toolchain = match get_toolchain_version(
dir,
sysroot_ref,
Tool::Rustc,
&config.extra_env,
"rustc ",
) {
Ok(it) => it,
Err(e) => {
tracing::error!("{e}");
None
}
};
let rustc_cfg =
rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(sysroot_ref));
let data_layout = target_data_layout::get(
RustcDataLayoutConfig::Rustc(sysroot_ref),
None,
&config.extra_env,
);
let cargo_script = ManifestPath::try_from(detached_file.clone())
.ok()
.and_then(|file| {
CargoWorkspace::fetch_metadata(
&file,
file.parent(),
config,
sysroot_ref,
&|_| (),
)
.ok()
})
.map(CargoWorkspace::new);
Ok(ProjectWorkspace::DetachedFile {
file: detached_file,
sysroot,
rustc_cfg,
toolchain,
target_layout: data_layout
.map(Arc::from)
.map_err(|it| Arc::from(it.to_string())),
cfg_overrides: config.cfg_overrides.clone(),
cargo_script,
})
})
.collect()
} }
/// Runs the build scripts for this [`ProjectWorkspace`]. /// Runs the build scripts for this [`ProjectWorkspace`].
@ -519,7 +510,7 @@ impl ProjectWorkspace {
) -> anyhow::Result<WorkspaceBuildScripts> { ) -> anyhow::Result<WorkspaceBuildScripts> {
match self { match self {
ProjectWorkspace::DetachedFile { ProjectWorkspace::DetachedFile {
cargo_script: Some(cargo), cargo_script: Some((cargo, _)),
toolchain, toolchain,
sysroot, sysroot,
.. ..
@ -586,10 +577,11 @@ impl ProjectWorkspace {
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) { pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
match self { match self {
ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs, ProjectWorkspace::Cargo { build_scripts, .. }
_ => { | ProjectWorkspace::DetachedFile { cargo_script: Some((_, build_scripts)), .. } => {
always!(bs == WorkspaceBuildScripts::default()); *build_scripts = bs
} }
_ => assert_eq!(bs, WorkspaceBuildScripts::default()),
} }
} }
@ -742,15 +734,18 @@ impl ProjectWorkspace {
ProjectWorkspace::DetachedFile { file, cargo_script, sysroot, .. } => { ProjectWorkspace::DetachedFile { file, cargo_script, sysroot, .. } => {
iter::once(PackageRoot { iter::once(PackageRoot {
is_local: true, is_local: true,
include: vec![file.clone()], include: vec![file.as_ref().to_owned()],
exclude: Vec::new(), exclude: Vec::new(),
}) })
.chain(cargo_script.iter().flat_map(|cargo| { .chain(cargo_script.iter().flat_map(|(cargo, build_scripts)| {
cargo.packages().map(|pkg| { cargo.packages().map(|pkg| {
let is_local = cargo[pkg].is_local; let is_local = cargo[pkg].is_local;
let pkg_root = cargo[pkg].manifest.parent().to_path_buf(); let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
let mut include = vec![pkg_root.clone()]; 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);
// In case target's path is manually set in Cargo.toml to be // In case target's path is manually set in Cargo.toml to be
// outside the package root, add its parent as an extra include. // outside the package root, add its parent as an extra include.
@ -801,7 +796,7 @@ impl ProjectWorkspace {
ProjectWorkspace::DetachedFile { sysroot, cargo_script, .. } => { ProjectWorkspace::DetachedFile { sysroot, cargo_script, .. } => {
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages()); let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
sysroot_package_len sysroot_package_len
+ cargo_script.as_ref().map_or(1, |cargo| cargo.packages().len()) + cargo_script.as_ref().map_or(1, |(cargo, _)| cargo.packages().len())
} }
} }
} }
@ -863,7 +858,7 @@ impl ProjectWorkspace {
cfg_overrides, cfg_overrides,
cargo_script, cargo_script,
} => ( } => (
if let Some(cargo) = cargo_script { if let Some((cargo, build_scripts)) = cargo_script {
cargo_to_crate_graph( cargo_to_crate_graph(
&mut |path| load(path), &mut |path| load(path),
None, None,
@ -871,7 +866,7 @@ impl ProjectWorkspace {
sysroot.as_ref().ok(), sysroot.as_ref().ok(),
rustc_cfg.clone(), rustc_cfg.clone(),
cfg_overrides, cfg_overrides,
&WorkspaceBuildScripts::default(), build_scripts,
) )
} else { } else {
detached_file_to_crate_graph( detached_file_to_crate_graph(
@ -959,7 +954,7 @@ impl ProjectWorkspace {
file, file,
sysroot, sysroot,
rustc_cfg, rustc_cfg,
cargo_script, cargo_script: Some((cargo_script, _)),
toolchain, toolchain,
target_layout, target_layout,
cfg_overrides, cfg_overrides,
@ -968,7 +963,7 @@ impl ProjectWorkspace {
file: o_file, file: o_file,
sysroot: o_sysroot, sysroot: o_sysroot,
rustc_cfg: o_rustc_cfg, rustc_cfg: o_rustc_cfg,
cargo_script: o_cargo_script, cargo_script: Some((o_cargo_script, _)),
toolchain: o_toolchain, toolchain: o_toolchain,
target_layout: o_target_layout, target_layout: o_target_layout,
cfg_overrides: o_cfg_overrides, cfg_overrides: o_cfg_overrides,
@ -1294,11 +1289,11 @@ fn cargo_to_crate_graph(
fn detached_file_to_crate_graph( fn detached_file_to_crate_graph(
rustc_cfg: Vec<CfgFlag>, rustc_cfg: Vec<CfgFlag>,
load: FileLoader<'_>, load: FileLoader<'_>,
detached_file: &AbsPathBuf, detached_file: &ManifestPath,
sysroot: Option<&Sysroot>, sysroot: Option<&Sysroot>,
override_cfg: &CfgOverrides, override_cfg: &CfgOverrides,
) -> (CrateGraph, ProcMacroPaths) { ) -> (CrateGraph, ProcMacroPaths) {
let _p = tracing::span!(tracing::Level::INFO, "detached_files_to_crate_graph").entered(); let _p = tracing::span!(tracing::Level::INFO, "detached_file_to_crate_graph").entered();
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
let (public_deps, _libproc_macro) = match sysroot { let (public_deps, _libproc_macro) = match sysroot {
Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load), Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),

View file

@ -96,7 +96,7 @@ impl<'a> ProgressReport<'a> {
} }
fn set_value(&mut self, value: f32) { fn set_value(&mut self, value: f32) {
self.curr = f32::max(0.0, f32::min(1.0, value)); self.curr = value.clamp(0.0, 1.0);
} }
fn clear(&mut self) { fn clear(&mut self) {

View file

@ -10,7 +10,9 @@ use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig};
use itertools::Either; use itertools::Either;
use profile::StopWatch; use profile::StopWatch;
use project_model::target_data_layout::RustcDataLayoutConfig; use project_model::target_data_layout::RustcDataLayoutConfig;
use project_model::{target_data_layout, CargoConfig, ProjectWorkspace, RustLibSource, Sysroot}; use project_model::{
target_data_layout, CargoConfig, ManifestPath, ProjectWorkspace, RustLibSource, Sysroot,
};
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice}; use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -76,7 +78,7 @@ impl Tester {
); );
let workspace = ProjectWorkspace::DetachedFile { let workspace = ProjectWorkspace::DetachedFile {
file: tmp_file, file: ManifestPath::try_from(tmp_file).unwrap(),
sysroot, sysroot,
rustc_cfg: vec![], rustc_cfg: vec![],
toolchain: None, toolchain: None,

View file

@ -356,7 +356,8 @@ config_data! {
/// of projects. /// of projects.
/// ///
/// Elements must be paths pointing to `Cargo.toml`, /// Elements must be paths pointing to `Cargo.toml`,
/// `rust-project.json`, or JSON objects in `rust-project.json` format. /// `rust-project.json`, `.rs` files (which will be treated as standalone files) or JSON
/// objects in `rust-project.json` format.
linkedProjects: Vec<ManifestOrProjectJson> = vec![], linkedProjects: Vec<ManifestOrProjectJson> = vec![],
/// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128. /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
@ -1301,12 +1302,9 @@ impl Config {
self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect(); self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
self.discovered_projects self.discovered_projects
.iter() .iter()
.filter( .filter(|project| {
|(ProjectManifest::ProjectJson(path) !exclude_dirs.iter().any(|p| project.manifest_path().starts_with(p))
| ProjectManifest::CargoToml(path))| { })
!exclude_dirs.iter().any(|p| path.starts_with(p))
},
)
.cloned() .cloned()
.map(LinkedProject::from) .map(LinkedProject::from)
.collect() .collect()

View file

@ -18,7 +18,9 @@ use parking_lot::{
RwLockWriteGuard, RwLockWriteGuard,
}; };
use proc_macro_api::ProcMacroServer; use proc_macro_api::ProcMacroServer;
use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts}; use project_model::{
CargoWorkspace, ManifestPath, ProjectWorkspace, Target, WorkspaceBuildScripts,
};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use triomphe::Arc; use triomphe::Arc;
use vfs::{AnchoredPathBuf, ChangedFile, Vfs}; use vfs::{AnchoredPathBuf, ChangedFile, Vfs};
@ -125,7 +127,7 @@ pub(crate) struct GlobalState {
/// to invalidate any salsa caches. /// to invalidate any salsa caches.
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
pub(crate) crate_graph_file_dependencies: FxHashSet<vfs::VfsPath>, pub(crate) crate_graph_file_dependencies: FxHashSet<vfs::VfsPath>,
pub(crate) detached_files: FxHashSet<vfs::AbsPathBuf>, pub(crate) detached_files: FxHashSet<ManifestPath>,
// op queues // op queues
pub(crate) fetch_workspaces_queue: pub(crate) fetch_workspaces_queue:

View file

@ -1759,11 +1759,12 @@ pub(crate) fn handle_open_docs(
let position = from_proto::file_position(&snap, params)?; let position = from_proto::file_position(&snap, params)?;
let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match ws { let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match ws {
ProjectWorkspace::Cargo { cargo, sysroot, .. } => Some((cargo, sysroot.as_ref().ok())), ProjectWorkspace::Cargo { cargo, sysroot, .. }
ProjectWorkspace::Json { .. } => None, | ProjectWorkspace::DetachedFile { cargo_script: Some((cargo, _)), sysroot, .. } => {
ProjectWorkspace::DetachedFile { cargo_script, sysroot, .. } => { Some((cargo, sysroot.as_ref().ok()))
cargo_script.as_ref().zip(Some(sysroot.as_ref().ok()))
} }
ProjectWorkspace::Json { .. } => None,
ProjectWorkspace::DetachedFile { .. } => None,
}); });
let (cargo, sysroot) = match ws_and_sysroot { let (cargo, sysroot) = match ws_and_sysroot {

View file

@ -25,7 +25,7 @@ use ide_db::{
use itertools::Itertools; use itertools::Itertools;
use load_cargo::{load_proc_macro, ProjectFolders}; use load_cargo::{load_proc_macro, ProjectFolders};
use proc_macro_api::ProcMacroServer; use proc_macro_api::ProcMacroServer;
use project_model::{ProjectWorkspace, WorkspaceBuildScripts}; use project_model::{ManifestPath, ProjectWorkspace, WorkspaceBuildScripts};
use stdx::{format_to, thread::ThreadIntent}; use stdx::{format_to, thread::ThreadIntent};
use triomphe::Arc; use triomphe::Arc;
use vfs::{AbsPath, AbsPathBuf, ChangeKind}; use vfs::{AbsPath, AbsPathBuf, ChangeKind};
@ -204,7 +204,14 @@ impl GlobalState {
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, { self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
let linked_projects = self.config.linked_or_discovered_projects(); let linked_projects = self.config.linked_or_discovered_projects();
let detached_files = self.config.detached_files().to_vec(); 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(); let cargo_config = self.config.cargo();
move |sender| { move |sender| {

View file

@ -1,14 +1,18 @@
use std::path::PathBuf; use std::path::PathBuf;
use project_model::{CargoWorkspace, ProjectWorkspace, Sysroot, WorkspaceBuildScripts}; use project_model::{
CargoWorkspace, ManifestPath, Metadata, ProjectWorkspace, Sysroot, WorkspaceBuildScripts,
};
use rust_analyzer::ws_to_crate_graph; use rust_analyzer::ws_to_crate_graph;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use vfs::{AbsPathBuf, FileId}; use vfs::{AbsPathBuf, FileId};
fn load_cargo_with_fake_sysroot(file: &str) -> ProjectWorkspace { fn load_cargo_with_fake_sysroot(file: &str) -> ProjectWorkspace {
let meta = get_test_json_file(file); let meta: Metadata = get_test_json_file(file);
let cargo_workspace = CargoWorkspace::new(meta); let manifest_path =
ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
ProjectWorkspace::Cargo { ProjectWorkspace::Cargo {
cargo: cargo_workspace, cargo: cargo_workspace,
build_scripts: WorkspaceBuildScripts::default(), build_scripts: WorkspaceBuildScripts::default(),

View file

@ -150,6 +150,7 @@ use dependency2::Spam;
) )
.with_config(serde_json::json!({ .with_config(serde_json::json!({
"cargo": { "sysroot": null }, "cargo": { "sysroot": null },
"detachedFiles": ["/src/lib.rs"],
})) }))
.server() .server()
.wait_until_workspace_is_loaded(); .wait_until_workspace_is_loaded();

View file

@ -185,11 +185,7 @@ impl Project<'_> {
roots, roots,
None, None,
); );
// TODO: don't hardcode src/lib.rs as detached file config.update(self.config).expect("invalid config");
let mut c = self.config;
let p = tmp_dir_path.join("src/lib.rs").to_string();
c["detachedFiles"] = serde_json::json!([p]);
config.update(c).expect("invalid config");
config.rediscover_workspaces(); config.rediscover_workspaces();
Server::new(tmp_dir.keep(), config) Server::new(tmp_dir.keep(), config)

View file

@ -778,7 +778,8 @@ Disable project auto-discovery in favor of explicitly specified set
of projects. of projects.
Elements must be paths pointing to `Cargo.toml`, Elements must be paths pointing to `Cargo.toml`,
`rust-project.json`, or JSON objects in `rust-project.json` format. `rust-project.json`, `.rs` files (which will be treated as standalone files) or JSON
objects in `rust-project.json` format.
-- --
[[rust-analyzer.lru.capacity]]rust-analyzer.lru.capacity (default: `null`):: [[rust-analyzer.lru.capacity]]rust-analyzer.lru.capacity (default: `null`)::
+ +

View file

@ -1495,7 +1495,7 @@
"type": "boolean" "type": "boolean"
}, },
"rust-analyzer.linkedProjects": { "rust-analyzer.linkedProjects": {
"markdownDescription": "Disable project auto-discovery in favor of explicitly specified set\nof projects.\n\nElements must be paths pointing to `Cargo.toml`,\n`rust-project.json`, or JSON objects in `rust-project.json` format.", "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set\nof projects.\n\nElements must be paths pointing to `Cargo.toml`,\n`rust-project.json`, `.rs` files (which will be treated as standalone files) or JSON\nobjects in `rust-project.json` format.",
"default": [], "default": [],
"type": "array", "type": "array",
"items": { "items": {