Skip to content

Allow rust-project.json to specify sysroot workspace #19096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/project-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ fn parse_cfg(s: &str) -> Result<cfg::CfgAtom, String> {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RustSourceWorkspaceConfig {
CargoMetadata(CargoMetadataConfig),
Stitched,
Json(ProjectJson),
}

impl Default for RustSourceWorkspaceConfig {
Expand Down
12 changes: 11 additions & 1 deletion crates/project-model/src/project_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub struct ProjectJson {
pub(crate) sysroot: Option<AbsPathBuf>,
/// e.g. `path/to/sysroot/lib/rustlib/src/rust/library`
pub(crate) sysroot_src: Option<AbsPathBuf>,
/// A nested project describing the layout of the sysroot
pub(crate) sysroot_project: Option<Box<ProjectJson>>,
project_root: AbsPathBuf,
/// The path to the rust-project.json file. May be None if this
/// data was generated by the discoverConfig command.
Expand All @@ -91,9 +93,16 @@ impl ProjectJson {
data: ProjectJsonData,
) -> ProjectJson {
let absolutize_on_base = |p| base.absolutize(p);
let sysroot_src = data.sysroot_src.map(absolutize_on_base);
let sysroot_project =
data.sysroot_project.zip(sysroot_src.clone()).map(|(sysroot_data, sysroot_src)| {
Box::new(ProjectJson::new(None, &sysroot_src, *sysroot_data))
});

ProjectJson {
sysroot: data.sysroot.map(absolutize_on_base),
sysroot_src: data.sysroot_src.map(absolutize_on_base),
sysroot_src,
sysroot_project,
project_root: base.to_path_buf(),
manifest,
runnables: data.runnables.into_iter().map(Runnable::from).collect(),
Expand Down Expand Up @@ -330,6 +339,7 @@ pub enum RunnableKind {
pub struct ProjectJsonData {
sysroot: Option<Utf8PathBuf>,
sysroot_src: Option<Utf8PathBuf>,
sysroot_project: Option<Box<ProjectJsonData>>,
#[serde(default)]
cfg_groups: FxHashMap<String, CfgList>,
crates: Vec<CrateData>,
Expand Down
147 changes: 12 additions & 135 deletions crates/project-model/src/sysroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,17 @@
//! but we can't process `.rlib` and need source code instead. The source code
//! is typically installed with `rustup component add rust-src` command.

use std::{
env, fs,
ops::{self, Not},
path::Path,
process::Command,
};
use std::{env, fs, ops::Not, path::Path, process::Command};

use anyhow::{format_err, Result};
use base_db::CrateName;
use itertools::Itertools;
use la_arena::{Arena, Idx};
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rustc_hash::FxHashMap;
use stdx::format_to;
use toolchain::{probe_for_binary, Tool};

use crate::{
cargo_workspace::CargoMetadataConfig, utf8_stdout, CargoWorkspace, ManifestPath,
cargo_workspace::CargoMetadataConfig, utf8_stdout, CargoWorkspace, ManifestPath, ProjectJson,
RustSourceWorkspaceConfig,
};

Expand All @@ -36,58 +29,10 @@ pub struct Sysroot {
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum RustLibSrcWorkspace {
Workspace(CargoWorkspace),
Stitched(Stitched),
Json(ProjectJson),
Empty,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Stitched {
crates: Arena<RustLibSrcCrateData>,
}

impl ops::Index<RustLibSrcCrate> for Stitched {
type Output = RustLibSrcCrateData;
fn index(&self, index: RustLibSrcCrate) -> &RustLibSrcCrateData {
&self.crates[index]
}
}

impl Stitched {
pub(crate) fn public_deps(
&self,
) -> impl Iterator<Item = (CrateName, RustLibSrcCrate, bool)> + '_ {
// core is added as a dependency before std in order to
// mimic rustcs dependency order
[("core", true), ("alloc", false), ("std", true), ("test", false)].into_iter().filter_map(
move |(name, prelude)| {
Some((CrateName::new(name).unwrap(), self.by_name(name)?, prelude))
},
)
}

pub(crate) fn proc_macro(&self) -> Option<RustLibSrcCrate> {
self.by_name("proc_macro")
}

pub(crate) fn crates(&self) -> impl ExactSizeIterator<Item = RustLibSrcCrate> + '_ {
self.crates.iter().map(|(id, _data)| id)
}

fn by_name(&self, name: &str) -> Option<RustLibSrcCrate> {
let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
Some(id)
}
}

pub(crate) type RustLibSrcCrate = Idx<RustLibSrcCrateData>;

#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct RustLibSrcCrateData {
pub(crate) name: String,
pub(crate) root: ManifestPath,
pub(crate) deps: Vec<RustLibSrcCrate>,
}

impl Sysroot {
pub const fn empty() -> Sysroot {
Sysroot {
Expand All @@ -114,7 +59,7 @@ impl Sysroot {
pub fn is_rust_lib_src_empty(&self) -> bool {
match &self.workspace {
RustLibSrcWorkspace::Workspace(ws) => ws.packages().next().is_none(),
RustLibSrcWorkspace::Stitched(stitched) => stitched.crates.is_empty(),
RustLibSrcWorkspace::Json(project_json) => project_json.n_crates() == 0,
RustLibSrcWorkspace::Empty => true,
}
}
Expand All @@ -126,7 +71,7 @@ impl Sysroot {
pub fn num_packages(&self) -> usize {
match &self.workspace {
RustLibSrcWorkspace::Workspace(ws) => ws.packages().count(),
RustLibSrcWorkspace::Stitched(c) => c.crates().count(),
RustLibSrcWorkspace::Json(project_json) => project_json.n_crates(),
RustLibSrcWorkspace::Empty => 0,
}
}
Expand Down Expand Up @@ -252,52 +197,11 @@ impl Sysroot {
return Some(loaded);
}
}
}
tracing::debug!("Stitching sysroot library: {src_root}");

let mut stitched = Stitched { crates: Arena::default() };

for path in SYSROOT_CRATES.trim().lines() {
let name = path.split('/').last().unwrap();
let root = [format!("{path}/src/lib.rs"), format!("lib{path}/lib.rs")]
.into_iter()
.map(|it| src_root.join(it))
.filter_map(|it| ManifestPath::try_from(it).ok())
.find(|it| fs::metadata(it).is_ok());

if let Some(root) = root {
stitched.crates.alloc(RustLibSrcCrateData {
name: name.into(),
root,
deps: Vec::new(),
});
}
}

if let Some(std) = stitched.by_name("std") {
for dep in STD_DEPS.trim().lines() {
if let Some(dep) = stitched.by_name(dep) {
stitched.crates[std].deps.push(dep)
}
}
}

if let Some(alloc) = stitched.by_name("alloc") {
for dep in ALLOC_DEPS.trim().lines() {
if let Some(dep) = stitched.by_name(dep) {
stitched.crates[alloc].deps.push(dep)
}
}
} else if let RustSourceWorkspaceConfig::Json(project_json) = sysroot_source_config {
return Some(RustLibSrcWorkspace::Json(project_json.clone()));
}

if let Some(proc_macro) = stitched.by_name("proc_macro") {
for dep in PROC_MACRO_DEPS.trim().lines() {
if let Some(dep) = stitched.by_name(dep) {
stitched.crates[proc_macro].deps.push(dep)
}
}
}
Some(RustLibSrcWorkspace::Stitched(stitched))
None
}

pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) {
Expand All @@ -308,7 +212,10 @@ impl Sysroot {
RustLibSrcWorkspace::Workspace(ws) => {
ws.packages().any(|p| ws[p].name == "core")
}
RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
RustLibSrcWorkspace::Json(project_json) => project_json
.crates()
.filter_map(|(_, krate)| krate.display_name.clone())
.any(|name| name.canonical_name().as_str() == "core"),
RustLibSrcWorkspace::Empty => true,
};
if !has_core {
Expand Down Expand Up @@ -484,33 +391,3 @@ fn get_rust_lib_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
None
}
}

const SYSROOT_CRATES: &str = "
alloc
backtrace
core
panic_abort
panic_unwind
proc_macro
profiler_builtins
std
stdarch/crates/std_detect
test
unwind";

const ALLOC_DEPS: &str = "core";

const STD_DEPS: &str = "
alloc
panic_unwind
panic_abort
core
profiler_builtins
unwind
std_detect
test";

// core is required for our builtin derives to work in the proc_macro lib currently
const PROC_MACRO_DEPS: &str = "
std
core";
14 changes: 0 additions & 14 deletions crates/project-model/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::ops::Deref;

use base_db::{CrateGraph, ProcMacroPaths};
use cargo_metadata::Metadata;
use cfg::{CfgAtom, CfgDiff};
Expand Down Expand Up @@ -225,18 +223,6 @@ fn rust_project_cfg_groups() {
check_crate_graph(crate_graph, expect_file!["../test_data/output/rust_project_cfg_groups.txt"]);
}

#[test]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deleted this test because it's really testing a behavior of the stitched sysroot.

fn rust_project_is_proc_macro_has_proc_macro_dep() {
let (crate_graph, _proc_macros) = load_rust_project("is-proc-macro-project.json");
// Since the project only defines one crate (outside the sysroot crates),
// it should be the one with the biggest Id.
let crate_id = crate_graph.iter().max().unwrap();
let crate_data = &crate_graph[crate_id];
// Assert that the project crate with `is_proc_macro` has a dependency
// on the proc_macro sysroot crate.
crate_data.dependencies.iter().find(|&dep| *dep.name.deref() == sym::proc_macro).unwrap();
}

#[test]
fn crate_graph_dedup_identical() {
let (mut crate_graph, proc_macros) = load_cargo("regex-metadata.json");
Expand Down
Loading