From 6533515635e918a23ed2ed06e68250f4e8439e98 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Feb 2024 15:54:45 +0300 Subject: [PATCH 1/7] create helper function `project_metadata` in bootstrap Signed-off-by: onur-ozkan --- src/bootstrap/src/core/metadata.rs | 60 +++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/bootstrap/src/core/metadata.rs b/src/bootstrap/src/core/metadata.rs index 5802082326a88..66372eeb3f652 100644 --- a/src/bootstrap/src/core/metadata.rs +++ b/src/bootstrap/src/core/metadata.rs @@ -17,25 +17,29 @@ struct Output { /// For more information, see the output of /// #[derive(Debug, Deserialize)] -struct Package { - name: String, - source: Option, - manifest_path: String, - dependencies: Vec, - targets: Vec, +pub(crate) struct Package { + pub(crate) name: String, + pub(crate) source: Option, + pub(crate) manifest_path: String, + pub(crate) dependencies: Vec, + pub(crate) targets: Vec, } /// For more information, see the output of /// -#[derive(Debug, Deserialize)] -struct Dependency { - name: String, - source: Option, +#[derive(Debug, Deserialize, PartialEq)] +pub(crate) struct Dependency { + pub(crate) name: String, + pub(crate) source: Option, } #[derive(Debug, Deserialize)] -struct Target { - kind: Vec, +pub(crate) struct Target { + pub(crate) name: String, + pub(crate) kind: Vec, + pub(crate) crate_types: Vec, + pub(crate) src_path: String, + pub(crate) edition: String, } /// Collects and stores package metadata of each workspace members into `build`, @@ -70,7 +74,7 @@ pub fn build(build: &mut Build) { /// /// Note that `src/tools/cargo` is no longer a workspace member but we still /// treat it as one here, by invoking an additional `cargo metadata` command. -fn workspace_members(build: &Build) -> impl Iterator { +pub(crate) fn workspace_members(build: &Build) -> impl Iterator { let collect_metadata = |manifest_path| { let mut cargo = Command::new(&build.initial_cargo); cargo @@ -99,3 +103,33 @@ fn workspace_members(build: &Build) -> impl Iterator { packages.into_iter().chain(cargo_package).chain(ra_packages).chain(bootstrap_packages) } + +/// Invokes `cargo metadata` to get package metadata of whole workspace including the dependencies. +pub(crate) fn project_metadata(build: &Build) -> impl Iterator { + let collect_metadata = |manifest_path| { + let mut cargo = Command::new(&build.initial_cargo); + cargo + // Will read the libstd Cargo.toml + // which uses the unstable `public-dependency` feature. + .env("RUSTC_BOOTSTRAP", "1") + .arg("metadata") + .arg("--format-version") + .arg("1") + .arg("--manifest-path") + .arg(build.src.join(manifest_path)); + let metadata_output = output(&mut cargo); + let Output { packages, .. } = t!(serde_json::from_str(&metadata_output)); + packages + }; + + // Collects `metadata.packages` from all workspaces. + let packages = collect_metadata("Cargo.toml"); + let cargo_packages = collect_metadata("src/tools/cargo/Cargo.toml"); + let ra_packages = collect_metadata("src/tools/rust-analyzer/Cargo.toml"); + let bootstrap_packages = collect_metadata("src/bootstrap/Cargo.toml"); + + // We only care about the root package from `src/tool/cargo` workspace. + let cargo_package = cargo_packages.into_iter().find(|pkg| pkg.name == "cargo").into_iter(); + + packages.into_iter().chain(cargo_package).chain(ra_packages).chain(bootstrap_packages) +} From 8e6a40975f7d57b72914c4590212608779f19235 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Feb 2024 15:56:38 +0300 Subject: [PATCH 2/7] implement new util `ra_project` for bootstrap Contains the implementation for generating rust-project.json data which can be utilized for LSPs (Language Server Protocols). Signed-off-by: onur-ozkan --- src/bootstrap/src/utils/mod.rs | 1 + src/bootstrap/src/utils/ra_project.rs | 154 ++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/bootstrap/src/utils/ra_project.rs diff --git a/src/bootstrap/src/utils/mod.rs b/src/bootstrap/src/utils/mod.rs index cb535f0e1632a..e962c1f4eefb4 100644 --- a/src/bootstrap/src/utils/mod.rs +++ b/src/bootstrap/src/utils/mod.rs @@ -12,5 +12,6 @@ pub(crate) mod helpers; pub(crate) mod job; #[cfg(feature = "build-metrics")] pub(crate) mod metrics; +pub(crate) mod ra_project; pub(crate) mod render_tests; pub(crate) mod tarball; diff --git a/src/bootstrap/src/utils/ra_project.rs b/src/bootstrap/src/utils/ra_project.rs new file mode 100644 index 0000000000000..74b57f6f67df4 --- /dev/null +++ b/src/bootstrap/src/utils/ra_project.rs @@ -0,0 +1,154 @@ +//! This module contains the implementation for generating rust-project.json data which can be +//! utilized for LSPs (Language Server Protocols). +//! +//! The primary reason for relying on rust-analyzer.json instead of the default rust-analyzer +//! is because rust-analyzer is not so capable of handling rust-lang/rust workspaces out of the box. +//! It often encounters new issues while trying to fix current problems with some hacky workarounds. +//! +//! For additional context, see the [zulip thread]. +//! +//! [zulip thread]: https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/r-a.20support.20for.20rust-lang.2Frust.20via.20project-rust.2Ejson/near/412505824 + +use serde_derive::{Deserialize, Serialize}; +use std::collections::{BTreeMap, BTreeSet}; +use std::io; +use std::path::Path; + +use crate::core::metadata::{project_metadata, workspace_members, Dependency}; +use crate::Build; + +#[derive(Debug, Deserialize, Serialize)] +/// FIXME(before-merge): doc-comment +struct RustAnalyzerProject { + crates: Vec, + sysroot: String, + sysroot_src: String, +} + +#[derive(Debug, Default, Deserialize, Serialize, PartialEq)] +struct Crate { + cfg: Vec, + deps: BTreeSet, + display_name: String, + edition: String, + env: BTreeMap, + is_proc_macro: bool, + proc_macro_dylib_path: Option, + is_workspace_member: bool, + root_module: String, +} + +#[derive(Debug, Default, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq)] +struct Dep { + #[serde(rename = "crate")] + crate_index: usize, + name: String, +} + +impl RustAnalyzerProject { + #[allow(dead_code)] // FIXME(before-merge): remove this + pub(crate) fn collect_ra_project_data(build: &mut Build) -> Self { + let mut ra_project = RustAnalyzerProject { + crates: vec![], + sysroot: format!("{}", build.out.join("host").join("stage0").display()), + sysroot_src: format!("{}", build.src.join("library").display()), + }; + + let packages: Vec<_> = project_metadata(build).collect(); + let workspace_members: Vec<_> = workspace_members(build).collect(); + + for package in &packages { + let is_not_indirect_dependency = packages + .iter() + .filter(|t| { + let used_from_other_crates = t.dependencies.contains(&Dependency { + name: package.name.clone(), + source: package.source.clone(), + }); + + let is_local = t.source.is_none(); + + (used_from_other_crates && is_local) || package.source.is_none() + }) + .next() + .is_some(); + + if !is_not_indirect_dependency { + continue; + } + + for target in &package.targets { + let mut krate = Crate::default(); + krate.display_name = target.name.clone(); + krate.root_module = target.src_path.clone(); + krate.edition = target.edition.clone(); + krate.is_workspace_member = workspace_members.iter().any(|p| p.name == target.name); + krate.is_proc_macro = target.crate_types.contains(&"proc-macro".to_string()); + + // FIXME(before-merge): We need to figure out how to find proc-macro dylibs. + // if krate.is_proc_macro { + // krate.proc_macro_dylib_path = + // } + + krate.env.insert("RUSTC_BOOTSTRAP".into(), "1".into()); + + if target + .src_path + .starts_with(&build.src.join("library").to_string_lossy().to_string()) + { + krate.cfg.push("bootstrap".into()); + } + + ra_project.crates.push(krate); + } + } + + ra_project.crates.sort_by_key(|c| c.display_name.clone()); + ra_project.crates.dedup_by_key(|c| c.display_name.clone()); + + // Find and fill dependencies of crates. + for package in packages { + if package.dependencies.is_empty() { + continue; + } + + for dependency in package.dependencies { + if let Some(index) = + ra_project.crates.iter().position(|c| c.display_name == package.name) + { + if let Some(dependency_index) = + ra_project.crates.iter().position(|c| c.display_name == dependency.name) + { + // no need to find indirect dependencies of direct dependencies, just continue + if ra_project.crates[index].root_module.contains(".cargo/registry") { + continue; + } + + let dependency_name = dependency.name.replace('-', "_").to_lowercase(); + + ra_project.crates[index] + .deps + .insert(Dep { crate_index: dependency_index, name: dependency_name }); + } + } + } + } + + ra_project + } + + #[allow(dead_code)] // FIXME(before-merge): remove this + pub(crate) fn generate_file(&self, path: &Path) -> io::Result<()> { + if path.exists() { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + format!("File '{}' already exists.", path.display()), + )); + } + + let mut file = std::fs::File::create(path)?; + serde_json::to_writer_pretty(&mut file, self)?; + + Ok(()) + } +} From e1259243f3a34bb24b3663bf442239e88938dd96 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Feb 2024 20:55:34 +0300 Subject: [PATCH 3/7] add setup step for generating `rust-project.json` Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/setup.rs | 52 +++++++++++++++++++++ src/bootstrap/src/core/builder.rs | 8 +++- src/bootstrap/src/core/metadata.rs | 16 +++---- src/bootstrap/src/utils/ra_project.rs | 17 +++---- 4 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 9c897ae1bb784..1f56329cbcd07 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -2,6 +2,7 @@ use crate::core::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::t; use crate::utils::change_tracker::CONFIG_CHANGE_HISTORY; use crate::utils::helpers::hex_encode; +use crate::utils::ra_project::RustAnalyzerProject; use crate::Config; use sha2::Digest; use std::env::consts::EXE_SUFFIX; @@ -624,3 +625,54 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result { } Ok(should_create) } + +/// Sets up `rust-project.json` +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct RustProjectJson; + +impl Step for RustProjectJson { + type Output = (); + const DEFAULT: bool = true; + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("rust-project") + } + fn make_run(run: RunConfig<'_>) { + if run.builder.config.dry_run() { + return; + } + + if let [cmd] = &run.paths[..] { + if cmd.assert_single_path().path.as_path().as_os_str() == "rust-project" { + run.builder.ensure(RustProjectJson); + } + } + } + fn run(self, builder: &Builder<'_>) -> Self::Output { + let config = &builder.config; + if config.dry_run() { + return; + } + + while !t!(create_ra_project_json_maybe(&config)) {} + } +} + +fn create_ra_project_json_maybe(config: &Config) -> io::Result { + println!("\nx.py can automatically generate `rust-project.json` file for rust-analyzer"); + + let should_create = match prompt_user("Would you like to create rust-project.json?: [y/N]")? { + Some(PromptResult::Yes) => true, + _ => { + println!("Ok, skipping rust-project.json!"); + return Ok(true); + } + }; + + if should_create { + let ra_project = RustAnalyzerProject::collect_ra_project_data(&config); + ra_project.generate_file(&config.src.join("rust-project.json"))?; + println!("Created `rust-project.json`"); + } + + Ok(should_create) +} diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index 4e20babc55a68..6451c1dc3076b 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -880,7 +880,13 @@ impl<'a> Builder<'a> { run::GenerateWindowsSys, run::GenerateCompletions, ), - Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode), + Kind::Setup => describe!( + setup::Profile, + setup::Hook, + setup::Link, + setup::Vscode, + setup::RustProjectJson + ), Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std), // special-cased in Build::build() Kind::Format | Kind::Suggest => vec![], diff --git a/src/bootstrap/src/core/metadata.rs b/src/bootstrap/src/core/metadata.rs index 66372eeb3f652..ce3f1a8fde6cf 100644 --- a/src/bootstrap/src/core/metadata.rs +++ b/src/bootstrap/src/core/metadata.rs @@ -5,7 +5,7 @@ use serde_derive::Deserialize; use crate::utils::cache::INTERNER; use crate::utils::helpers::output; -use crate::{t, Build, Crate}; +use crate::{t, Build, Config, Crate}; /// For more information, see the output of /// @@ -45,7 +45,7 @@ pub(crate) struct Target { /// Collects and stores package metadata of each workspace members into `build`, /// by executing `cargo metadata` commands. pub fn build(build: &mut Build) { - for package in workspace_members(build) { + for package in workspace_members(&build.config) { if package.source.is_none() { let name = INTERNER.intern_string(package.name); let mut path = PathBuf::from(package.manifest_path); @@ -74,9 +74,9 @@ pub fn build(build: &mut Build) { /// /// Note that `src/tools/cargo` is no longer a workspace member but we still /// treat it as one here, by invoking an additional `cargo metadata` command. -pub(crate) fn workspace_members(build: &Build) -> impl Iterator { +pub(crate) fn workspace_members(config: &Config) -> impl Iterator { let collect_metadata = |manifest_path| { - let mut cargo = Command::new(&build.initial_cargo); + let mut cargo = Command::new(&config.initial_cargo); cargo // Will read the libstd Cargo.toml // which uses the unstable `public-dependency` feature. @@ -86,7 +86,7 @@ pub(crate) fn workspace_members(build: &Build) -> impl Iterator .arg("1") .arg("--no-deps") .arg("--manifest-path") - .arg(build.src.join(manifest_path)); + .arg(config.src.join(manifest_path)); let metadata_output = output(&mut cargo); let Output { packages, .. } = t!(serde_json::from_str(&metadata_output)); packages @@ -105,9 +105,9 @@ pub(crate) fn workspace_members(build: &Build) -> impl Iterator } /// Invokes `cargo metadata` to get package metadata of whole workspace including the dependencies. -pub(crate) fn project_metadata(build: &Build) -> impl Iterator { +pub(crate) fn project_metadata(config: &Config) -> impl Iterator { let collect_metadata = |manifest_path| { - let mut cargo = Command::new(&build.initial_cargo); + let mut cargo = Command::new(&config.initial_cargo); cargo // Will read the libstd Cargo.toml // which uses the unstable `public-dependency` feature. @@ -116,7 +116,7 @@ pub(crate) fn project_metadata(build: &Build) -> impl Iterator { .arg("--format-version") .arg("1") .arg("--manifest-path") - .arg(build.src.join(manifest_path)); + .arg(config.src.join(manifest_path)); let metadata_output = output(&mut cargo); let Output { packages, .. } = t!(serde_json::from_str(&metadata_output)); packages diff --git a/src/bootstrap/src/utils/ra_project.rs b/src/bootstrap/src/utils/ra_project.rs index 74b57f6f67df4..7afd2bace4be6 100644 --- a/src/bootstrap/src/utils/ra_project.rs +++ b/src/bootstrap/src/utils/ra_project.rs @@ -15,11 +15,11 @@ use std::io; use std::path::Path; use crate::core::metadata::{project_metadata, workspace_members, Dependency}; -use crate::Build; +use crate::Config; #[derive(Debug, Deserialize, Serialize)] /// FIXME(before-merge): doc-comment -struct RustAnalyzerProject { +pub(crate) struct RustAnalyzerProject { crates: Vec, sysroot: String, sysroot_src: String, @@ -33,6 +33,7 @@ struct Crate { edition: String, env: BTreeMap, is_proc_macro: bool, + #[serde(skip_serializing_if = "Option::is_none")] proc_macro_dylib_path: Option, is_workspace_member: bool, root_module: String, @@ -47,15 +48,15 @@ struct Dep { impl RustAnalyzerProject { #[allow(dead_code)] // FIXME(before-merge): remove this - pub(crate) fn collect_ra_project_data(build: &mut Build) -> Self { + pub(crate) fn collect_ra_project_data(config: &Config) -> Self { let mut ra_project = RustAnalyzerProject { crates: vec![], - sysroot: format!("{}", build.out.join("host").join("stage0").display()), - sysroot_src: format!("{}", build.src.join("library").display()), + sysroot: format!("{}", config.out.join("host").join("stage0").display()), + sysroot_src: format!("{}", config.src.join("library").display()), }; - let packages: Vec<_> = project_metadata(build).collect(); - let workspace_members: Vec<_> = workspace_members(build).collect(); + let packages: Vec<_> = project_metadata(config).collect(); + let workspace_members: Vec<_> = workspace_members(config).collect(); for package in &packages { let is_not_indirect_dependency = packages @@ -94,7 +95,7 @@ impl RustAnalyzerProject { if target .src_path - .starts_with(&build.src.join("library").to_string_lossy().to_string()) + .starts_with(&config.src.join("library").to_string_lossy().to_string()) { krate.cfg.push("bootstrap".into()); } From c78ef011d31949022370044f956ec74943c2114e Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Feb 2024 20:56:00 +0300 Subject: [PATCH 4/7] ignore `rust-project.json` Signed-off-by: onur-ozkan --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 485968d9c56ff..efafdba30ec06 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ Session.vim config.mk config.stamp no_llvm_build +rust-project.json ## Build /dl/ From 8bbdb075131020f80cc979449bcdacbb6d8f0ce9 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 3 Feb 2024 21:01:51 +0300 Subject: [PATCH 5/7] no need for `Serialize` on ra_project types Signed-off-by: onur-ozkan --- src/bootstrap/src/utils/ra_project.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/src/utils/ra_project.rs b/src/bootstrap/src/utils/ra_project.rs index 7afd2bace4be6..53342ba72017b 100644 --- a/src/bootstrap/src/utils/ra_project.rs +++ b/src/bootstrap/src/utils/ra_project.rs @@ -9,7 +9,7 @@ //! //! [zulip thread]: https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/r-a.20support.20for.20rust-lang.2Frust.20via.20project-rust.2Ejson/near/412505824 -use serde_derive::{Deserialize, Serialize}; +use serde_derive::Serialize; use std::collections::{BTreeMap, BTreeSet}; use std::io; use std::path::Path; @@ -17,7 +17,7 @@ use std::path::Path; use crate::core::metadata::{project_metadata, workspace_members, Dependency}; use crate::Config; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Serialize)] /// FIXME(before-merge): doc-comment pub(crate) struct RustAnalyzerProject { crates: Vec, @@ -25,7 +25,7 @@ pub(crate) struct RustAnalyzerProject { sysroot_src: String, } -#[derive(Debug, Default, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Default, Serialize, PartialEq)] struct Crate { cfg: Vec, deps: BTreeSet, @@ -39,7 +39,7 @@ struct Crate { root_module: String, } -#[derive(Debug, Default, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Debug, Default, Serialize, PartialEq, PartialOrd, Ord, Eq)] struct Dep { #[serde(rename = "crate")] crate_index: usize, From 9219a80fe1b7c91078c07af49752506af0ed470c Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 5 Feb 2024 18:47:37 +0300 Subject: [PATCH 6/7] compile proc-macro crates and fill dylibs for ra Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/setup.rs | 11 ++-- src/bootstrap/src/core/builder.rs | 14 ++++- src/bootstrap/src/utils/ra_project.rs | 66 ++++++++++++++++----- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 1f56329cbcd07..b0e8398f0f892 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -648,16 +648,15 @@ impl Step for RustProjectJson { } } fn run(self, builder: &Builder<'_>) -> Self::Output { - let config = &builder.config; - if config.dry_run() { + if builder.config.dry_run() { return; } - while !t!(create_ra_project_json_maybe(&config)) {} + while !t!(create_ra_project_json_maybe(builder)) {} } } -fn create_ra_project_json_maybe(config: &Config) -> io::Result { +fn create_ra_project_json_maybe(builder: &Builder<'_>) -> io::Result { println!("\nx.py can automatically generate `rust-project.json` file for rust-analyzer"); let should_create = match prompt_user("Would you like to create rust-project.json?: [y/N]")? { @@ -669,8 +668,8 @@ fn create_ra_project_json_maybe(config: &Config) -> io::Result { }; if should_create { - let ra_project = RustAnalyzerProject::collect_ra_project_data(&config); - ra_project.generate_file(&config.src.join("rust-project.json"))?; + let ra_project = RustAnalyzerProject::collect_ra_project_data(builder); + ra_project.generate_file(&builder.config.src.join("rust-project.json"))?; println!("Created `rust-project.json`"); } diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index 6451c1dc3076b..30d52b1a20aa6 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -2274,7 +2274,7 @@ impl<'a> Builder<'a> { /// /// `-Z crate-attr` flags will be applied recursively on the target code using the `rustc_parse::parser::Parser`. /// See `rustc_builtin_macros::cmdline_attrs::inject` for more information. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] struct Rustflags(String, TargetSelection); impl Rustflags { @@ -2429,3 +2429,15 @@ impl From for Command { cargo.command } } + +impl From for Cargo { + fn from(command: Command) -> Cargo { + Cargo { + command, + rustflags: Default::default(), + rustdocflags: Default::default(), + hostflags: Default::default(), + allow_features: Default::default(), + } + } +} diff --git a/src/bootstrap/src/utils/ra_project.rs b/src/bootstrap/src/utils/ra_project.rs index 53342ba72017b..9c281e3689d47 100644 --- a/src/bootstrap/src/utils/ra_project.rs +++ b/src/bootstrap/src/utils/ra_project.rs @@ -13,9 +13,11 @@ use serde_derive::Serialize; use std::collections::{BTreeMap, BTreeSet}; use std::io; use std::path::Path; +use std::process::Command; +use crate::core::build_steps::compile::{stream_cargo, CargoMessage}; +use crate::core::builder::Builder; use crate::core::metadata::{project_metadata, workspace_members, Dependency}; -use crate::Config; #[derive(Debug, Serialize)] /// FIXME(before-merge): doc-comment @@ -47,8 +49,9 @@ struct Dep { } impl RustAnalyzerProject { - #[allow(dead_code)] // FIXME(before-merge): remove this - pub(crate) fn collect_ra_project_data(config: &Config) -> Self { + pub(crate) fn collect_ra_project_data(builder: &Builder<'_>) -> Self { + let config = &builder.config; + let mut ra_project = RustAnalyzerProject { crates: vec![], sysroot: format!("{}", config.out.join("host").join("stage0").display()), @@ -86,11 +89,6 @@ impl RustAnalyzerProject { krate.is_workspace_member = workspace_members.iter().any(|p| p.name == target.name); krate.is_proc_macro = target.crate_types.contains(&"proc-macro".to_string()); - // FIXME(before-merge): We need to figure out how to find proc-macro dylibs. - // if krate.is_proc_macro { - // krate.proc_macro_dylib_path = - // } - krate.env.insert("RUSTC_BOOTSTRAP".into(), "1".into()); if target @@ -109,14 +107,51 @@ impl RustAnalyzerProject { // Find and fill dependencies of crates. for package in packages { - if package.dependencies.is_empty() { - continue; - } + if let Some(index) = + ra_project.crates.iter().position(|c| c.display_name == package.name) + { + assert!( + !package.manifest_path.is_empty(), + "manifest_path must be valid for proc-macro crates." + ); + + let mut cargo = Command::new(&builder.initial_cargo); + cargo + .env("RUSTC_BOOTSTRAP", "1") + .arg("build") + .arg("--manifest-path") + .arg(package.manifest_path); + + if ra_project.crates[index].is_proc_macro { + // FIXME(before-merge): use `CARGO_TARGET_DIR` to place shared libraries in the build output directory. + let ok = stream_cargo(builder, cargo.into(), vec![], &mut |msg| { + let filenames = match msg { + CargoMessage::CompilerArtifact { filenames, .. } => filenames, + _ => return, + }; + + for filename in filenames { + let snake_case_name = ra_project.crates[index] + .display_name + .replace('-', "_") + .to_lowercase(); + + if filename.ends_with(".so") + && (filename.contains(&format!( + "lib{}", + ra_project.crates[index].display_name + )) || filename.contains(&format!("lib{}", snake_case_name))) + { + ra_project.crates[index].proc_macro_dylib_path = + Some(filename.to_string()); + } + } + }); - for dependency in package.dependencies { - if let Some(index) = - ra_project.crates.iter().position(|c| c.display_name == package.name) - { + assert!(ok); + } + + for dependency in package.dependencies { if let Some(dependency_index) = ra_project.crates.iter().position(|c| c.display_name == dependency.name) { @@ -138,7 +173,6 @@ impl RustAnalyzerProject { ra_project } - #[allow(dead_code)] // FIXME(before-merge): remove this pub(crate) fn generate_file(&self, path: &Path) -> io::Result<()> { if path.exists() { return Err(io::Error::new( From 558bb770fea5fd28a69c716dd2b277328e737799 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 5 Feb 2024 23:33:22 +0300 Subject: [PATCH 7/7] set `CARGO_TARGET_DIR` on proc-macro builds Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/setup.rs | 2 +- src/bootstrap/src/utils/ra_project.rs | 62 ++++++++++++++------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index b0e8398f0f892..c3fab717524d2 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -669,7 +669,7 @@ fn create_ra_project_json_maybe(builder: &Builder<'_>) -> io::Result { if should_create { let ra_project = RustAnalyzerProject::collect_ra_project_data(builder); - ra_project.generate_file(&builder.config.src.join("rust-project.json"))?; + ra_project.generate_json_file(&builder.config.src.join("rust-project.json"))?; println!("Created `rust-project.json`"); } diff --git a/src/bootstrap/src/utils/ra_project.rs b/src/bootstrap/src/utils/ra_project.rs index 9c281e3689d47..213da5ccb6d68 100644 --- a/src/bootstrap/src/utils/ra_project.rs +++ b/src/bootstrap/src/utils/ra_project.rs @@ -20,13 +20,14 @@ use crate::core::builder::Builder; use crate::core::metadata::{project_metadata, workspace_members, Dependency}; #[derive(Debug, Serialize)] -/// FIXME(before-merge): doc-comment +/// Represents the root object in `rust-project.json` pub(crate) struct RustAnalyzerProject { crates: Vec, sysroot: String, sysroot_src: String, } +/// Represents the crate object in `rust-project.json` #[derive(Debug, Default, Serialize, PartialEq)] struct Crate { cfg: Vec, @@ -42,6 +43,7 @@ struct Crate { } #[derive(Debug, Default, Serialize, PartialEq, PartialOrd, Ord, Eq)] +/// Represents the dependency object in `rust-project.json` struct Dep { #[serde(rename = "crate")] crate_index: usize, @@ -49,6 +51,10 @@ struct Dep { } impl RustAnalyzerProject { + /// Gathers data for `rust-project.json` from `cargo metadata`. + /// + /// Skips the indirect dependency crates since we don't need to + /// run LSP on them. pub(crate) fn collect_ra_project_data(builder: &Builder<'_>) -> Self { let config = &builder.config; @@ -61,6 +67,7 @@ impl RustAnalyzerProject { let packages: Vec<_> = project_metadata(config).collect(); let workspace_members: Vec<_> = workspace_members(config).collect(); + // Handle crates in the workspace for package in &packages { let is_not_indirect_dependency = packages .iter() @@ -105,25 +112,40 @@ impl RustAnalyzerProject { ra_project.crates.sort_by_key(|c| c.display_name.clone()); ra_project.crates.dedup_by_key(|c| c.display_name.clone()); - // Find and fill dependencies of crates. + let mut info_is_printed = false; + + // Handle dependencies and proc-macro dylibs for package in packages { if let Some(index) = ra_project.crates.iter().position(|c| c.display_name == package.name) { - assert!( - !package.manifest_path.is_empty(), - "manifest_path must be valid for proc-macro crates." - ); - - let mut cargo = Command::new(&builder.initial_cargo); - cargo - .env("RUSTC_BOOTSTRAP", "1") - .arg("build") - .arg("--manifest-path") - .arg(package.manifest_path); - if ra_project.crates[index].is_proc_macro { - // FIXME(before-merge): use `CARGO_TARGET_DIR` to place shared libraries in the build output directory. + let date = &builder.config.stage0_metadata.compiler.date; + + let cargo_target_dir = builder + .out + .join("cache") + .join("proc-macro-artifacts-for-ra") + // Although it's rare (when the stage0 compiler changes while proc-macro artifacts under + // `proc-macro-artifacts-for-ra` directory exist), there is a chance of ABI mismatch between + // the stage0 compiler and dynamic libraries. Therefore, we want to trigger compilations + // when the stage0 compiler changes. + .join(format!("{date}_{}", package.name)); + + let mut cargo = Command::new(&builder.initial_cargo); + cargo + .env("RUSTC_BOOTSTRAP", "1") + .env("CARGO_TARGET_DIR", cargo_target_dir) + .arg("build") + .arg("--manifest-path") + .arg(package.manifest_path); + + if !info_is_printed { + builder.info("Building proc-macro artifacts to be used for rust-analyzer"); + } + + info_is_printed = true; + let ok = stream_cargo(builder, cargo.into(), vec![], &mut |msg| { let filenames = match msg { CargoMessage::CompilerArtifact { filenames, .. } => filenames, @@ -131,16 +153,15 @@ impl RustAnalyzerProject { }; for filename in filenames { + let kebab_case = &ra_project.crates[index].display_name; let snake_case_name = ra_project.crates[index] .display_name .replace('-', "_") .to_lowercase(); if filename.ends_with(".so") - && (filename.contains(&format!( - "lib{}", - ra_project.crates[index].display_name - )) || filename.contains(&format!("lib{}", snake_case_name))) + && (filename.contains(&format!("lib{}", kebab_case)) + || filename.contains(&format!("lib{}", snake_case_name))) { ra_project.crates[index].proc_macro_dylib_path = Some(filename.to_string()); @@ -173,7 +194,8 @@ impl RustAnalyzerProject { ra_project } - pub(crate) fn generate_file(&self, path: &Path) -> io::Result<()> { + /// Generates a json file on the given path. + pub(crate) fn generate_json_file(&self, path: &Path) -> io::Result<()> { if path.exists() { return Err(io::Error::new( io::ErrorKind::AlreadyExists,