Allow rust files to be used linkedProjects

This commit is contained in:
Lukas Wirth 2024-04-21 14:40:10 +02:00
parent 55d9a533b3
commit a2ed6837bc
21 changed files with 203 additions and 180 deletions

View file

@ -24,7 +24,7 @@ use toolchain::Tool;
use crate::{
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.
@ -63,7 +63,7 @@ impl WorkspaceBuildScripts {
fn build_command(
config: &CargoConfig,
allowed_features: &FxHashSet<String>,
workspace_root: &AbsPathBuf,
manifest_path: &ManifestPath,
sysroot: Option<&Sysroot>,
) -> io::Result<Command> {
let mut cmd = match config.run_build_script_command.as_deref() {
@ -79,7 +79,7 @@ impl WorkspaceBuildScripts {
cmd.args(&config.extra_args);
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 {
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
}
};
@ -152,37 +156,12 @@ impl WorkspaceBuildScripts {
.as_ref();
let allowed_features = workspace.workspace_features();
match Self::run_per_ws(
Self::build_command(
config,
&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,
let mut cmd =
Self::build_command(config, &allowed_features, workspace.manifest_path(), sysroot)?;
if toolchain.as_ref().map_or(false, |it| *it >= RUST_1_75) {
cmd.args(["--keep-going"]);
}
Self::run_per_ws(cmd, workspace, current_dir, progress)
}
/// 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
// `cargo check`. We shouldn't assume that package ids we see here are
// exactly those from `config`.

View file

@ -32,6 +32,7 @@ pub struct CargoWorkspace {
targets: Arena<TargetData>,
workspace_root: AbsPathBuf,
target_directory: AbsPathBuf,
manifest_path: ManifestPath,
}
impl ops::Index<Package> for CargoWorkspace {
@ -334,7 +335,7 @@ impl CargoWorkspace {
.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 packages = Arena::default();
let mut targets = Arena::default();
@ -448,7 +449,7 @@ impl CargoWorkspace {
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> + '_ {
@ -466,6 +467,10 @@ impl CargoWorkspace {
&self.workspace_root
}
pub fn manifest_path(&self) -> &ManifestPath {
&self.manifest_path
}
pub fn target_directory(&self) -> &AbsPath {
&self.target_directory
}

View file

@ -54,11 +54,13 @@ pub use crate::{
sysroot::Sysroot,
workspace::{FileLoader, PackageRoot, ProjectWorkspace},
};
pub use cargo_metadata::Metadata;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ProjectManifest {
ProjectJson(ManifestPath),
CargoToml(ManifestPath),
CargoScript(ManifestPath),
}
impl ProjectManifest {
@ -71,7 +73,10 @@ impl ProjectManifest {
if path.file_name().unwrap_or_default() == "Cargo.toml" {
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> {
@ -146,15 +151,19 @@ impl ProjectManifest {
res.sort();
res
}
pub fn manifest_path(&self) -> &ManifestPath {
match self {
ProjectManifest::ProjectJson(it)
| ProjectManifest::CargoToml(it)
| ProjectManifest::CargoScript(it) => it,
}
}
}
impl fmt::Display for ProjectManifest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProjectManifest::ProjectJson(it) | ProjectManifest::CargoToml(it) => {
fmt::Display::fmt(&it, f)
}
}
fmt::Display::fmt(self.manifest_path(), f)
}
}

View file

@ -1,5 +1,5 @@
//! See [`ManifestPath`].
use std::{fmt, ops, path::Path};
use std::{borrow::Borrow, fmt, ops};
use paths::{AbsPath, AbsPathBuf};
@ -54,8 +54,14 @@ impl ops::Deref for ManifestPath {
}
}
impl AsRef<Path> for ManifestPath {
fn as_ref(&self) -> &Path {
impl AsRef<AbsPath> for ManifestPath {
fn as_ref(&self) -> &AbsPath {
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))
.map(|package| package.id.clone())
.collect();
let cargo_workspace = CargoWorkspace::new(res);
let cargo_workspace = CargoWorkspace::new(res, sysroot_cargo_toml);
Some(Sysroot {
root: sysroot_dir.clone(),
src_root: Some(Ok(sysroot_src_dir.clone())),
@ -368,7 +368,7 @@ impl Sysroot {
.into_iter()
.map(|it| sysroot_src_dir.join(it))
.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 {
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 = ManifestPath::try_from(rustc_src).ok()?;
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)
} else {
None

View file

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

View file

@ -4,7 +4,7 @@
use std::{collections::VecDeque, fmt, fs, iter, sync};
use anyhow::{format_err, Context};
use anyhow::Context;
use base_db::{
CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Env, FileId,
LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult,
@ -14,7 +14,6 @@ use paths::{AbsPath, AbsPathBuf};
use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
use span::Edition;
use stdx::always;
use toolchain::Tool;
use triomphe::Arc;
@ -101,7 +100,7 @@ pub enum ProjectWorkspace {
/// Backed by basic sysroot crates for basic completion and highlighting.
DetachedFile {
/// The file in question.
file: AbsPathBuf,
file: ManifestPath,
/// The sysroot loaded for this workspace.
sysroot: Result<Sysroot, Option<String>>,
/// 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.
cfg_overrides: CfgOverrides,
/// 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> {
let res = match manifest {
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}"))?;
let data = serde_json::from_str(&file)
.with_context(|| format!("Failed to deserialize json file {project_json}"))?;
@ -243,6 +242,9 @@ impl ProjectWorkspace {
&config.cfg_overrides,
)
}
ProjectManifest::CargoScript(rust_file) => {
ProjectWorkspace::load_detached_file(rust_file, config)?
}
ProjectManifest::CargoToml(cargo_toml) => {
let sysroot = match (&config.sysroot, &config.sysroot_src) {
(Some(RustLibSource::Path(path)), None) => {
@ -299,7 +301,7 @@ impl ProjectWorkspace {
progress,
) {
Ok(meta) => {
let workspace = CargoWorkspace::new(meta);
let workspace = CargoWorkspace::new(meta, cargo_toml.clone());
let buildscripts = WorkspaceBuildScripts::rustc_crates(
&workspace,
cargo_toml.parent(),
@ -355,7 +357,7 @@ impl ProjectWorkspace {
"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 =
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(
detached_files: Vec<AbsPathBuf>,
detached_files: Vec<ManifestPath>,
config: &CargoConfig,
) -> Vec<anyhow::Result<ProjectWorkspace>> {
detached_files
.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()
detached_files.into_iter().map(|file| Self::load_detached_file(&file, config)).collect()
}
/// Runs the build scripts for this [`ProjectWorkspace`].
@ -519,7 +510,7 @@ impl ProjectWorkspace {
) -> anyhow::Result<WorkspaceBuildScripts> {
match self {
ProjectWorkspace::DetachedFile {
cargo_script: Some(cargo),
cargo_script: Some((cargo, _)),
toolchain,
sysroot,
..
@ -586,10 +577,11 @@ impl ProjectWorkspace {
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
match self {
ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,
_ => {
always!(bs == WorkspaceBuildScripts::default());
ProjectWorkspace::Cargo { build_scripts, .. }
| ProjectWorkspace::DetachedFile { cargo_script: Some((_, build_scripts)), .. } => {
*build_scripts = bs
}
_ => assert_eq!(bs, WorkspaceBuildScripts::default()),
}
}
@ -742,15 +734,18 @@ impl ProjectWorkspace {
ProjectWorkspace::DetachedFile { file, cargo_script, sysroot, .. } => {
iter::once(PackageRoot {
is_local: true,
include: vec![file.clone()],
include: vec![file.as_ref().to_owned()],
exclude: Vec::new(),
})
.chain(cargo_script.iter().flat_map(|cargo| {
.chain(cargo_script.iter().flat_map(|(cargo, build_scripts)| {
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);
// In case target's path is manually set in Cargo.toml to be
// outside the package root, add its parent as an extra include.
@ -801,7 +796,7 @@ impl ProjectWorkspace {
ProjectWorkspace::DetachedFile { sysroot, cargo_script, .. } => {
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
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,
cargo_script,
} => (
if let Some(cargo) = cargo_script {
if let Some((cargo, build_scripts)) = cargo_script {
cargo_to_crate_graph(
&mut |path| load(path),
None,
@ -871,7 +866,7 @@ impl ProjectWorkspace {
sysroot.as_ref().ok(),
rustc_cfg.clone(),
cfg_overrides,
&WorkspaceBuildScripts::default(),
build_scripts,
)
} else {
detached_file_to_crate_graph(
@ -959,7 +954,7 @@ impl ProjectWorkspace {
file,
sysroot,
rustc_cfg,
cargo_script,
cargo_script: Some((cargo_script, _)),
toolchain,
target_layout,
cfg_overrides,
@ -968,7 +963,7 @@ impl ProjectWorkspace {
file: o_file,
sysroot: o_sysroot,
rustc_cfg: o_rustc_cfg,
cargo_script: o_cargo_script,
cargo_script: Some((o_cargo_script, _)),
toolchain: o_toolchain,
target_layout: o_target_layout,
cfg_overrides: o_cfg_overrides,
@ -1294,11 +1289,11 @@ fn cargo_to_crate_graph(
fn detached_file_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
load: FileLoader<'_>,
detached_file: &AbsPathBuf,
detached_file: &ManifestPath,
sysroot: Option<&Sysroot>,
override_cfg: &CfgOverrides,
) -> (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 (public_deps, _libproc_macro) = match sysroot {
Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),