Skip to content
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
236 changes: 129 additions & 107 deletions crates/project_model/src/build_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,28 +58,32 @@ impl PartialEq for BuildDataConfig {

impl Eq for BuildDataConfig {}

#[derive(Debug, Default)]
#[derive(Debug)]
pub struct BuildDataCollector {
wrap_rustc: bool,
configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
}

impl BuildDataCollector {
pub fn new(wrap_rustc: bool) -> Self {
Self { wrap_rustc, configs: FxHashMap::default() }
}

pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
self.configs.insert(workspace_root.to_path_buf(), config);
}

pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
let mut res = BuildDataResult::default();
for (path, config) in self.configs.iter() {
res.per_workspace.insert(
path.clone(),
collect_from_workspace(
&config.cargo_toml,
&config.cargo_features,
&config.packages,
progress,
)?,
);
let workspace_build_data = WorkspaceBuildData::collect(
&config.cargo_toml,
&config.cargo_features,
&config.packages,
self.wrap_rustc,
progress,
)?;
res.per_workspace.insert(path.clone(), workspace_build_data);
}
Ok(res)
}
Expand Down Expand Up @@ -120,119 +124,137 @@ impl BuildDataConfig {
}
}

fn collect_from_workspace(
cargo_toml: &AbsPath,
cargo_features: &CargoConfig,
packages: &Vec<cargo_metadata::Package>,
progress: &dyn Fn(String),
) -> Result<WorkspaceBuildData> {
let mut cmd = Command::new(toolchain::cargo());
cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
.arg(cargo_toml.as_ref());

// --all-targets includes tests, benches and examples in addition to the
// default lib and bins. This is an independent concept from the --targets
// flag below.
cmd.arg("--all-targets");

if let Some(target) = &cargo_features.target {
cmd.args(&["--target", target]);
}
impl WorkspaceBuildData {
fn collect(
cargo_toml: &AbsPath,
cargo_features: &CargoConfig,
packages: &Vec<cargo_metadata::Package>,
wrap_rustc: bool,
progress: &dyn Fn(String),
) -> Result<WorkspaceBuildData> {
let mut cmd = Command::new(toolchain::cargo());

if wrap_rustc {
// Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
// that to compile only proc macros and build scripts during the initial
// `cargo check`.
let myself = std::env::current_exe()?;
cmd.env("RUSTC_WRAPPER", myself);
cmd.env("RA_RUSTC_WRAPPER", "1");
}

cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
.arg(cargo_toml.as_ref());

if cargo_features.all_features {
cmd.arg("--all-features");
} else {
if cargo_features.no_default_features {
// FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
// https://github.com/oli-obk/cargo_metadata/issues/79
cmd.arg("--no-default-features");
// --all-targets includes tests, benches and examples in addition to the
// default lib and bins. This is an independent concept from the --targets
// flag below.
cmd.arg("--all-targets");

if let Some(target) = &cargo_features.target {
cmd.args(&["--target", target]);
}
if !cargo_features.features.is_empty() {
cmd.arg("--features");
cmd.arg(cargo_features.features.join(" "));

if cargo_features.all_features {
cmd.arg("--all-features");
} else {
if cargo_features.no_default_features {
// FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
// https://github.com/oli-obk/cargo_metadata/issues/79
cmd.arg("--no-default-features");
}
if !cargo_features.features.is_empty() {
cmd.arg("--features");
cmd.arg(cargo_features.features.join(" "));
}
}
}

cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());

let mut child = cmd.spawn().map(JodChild)?;
let child_stdout = child.stdout.take().unwrap();
let stdout = BufReader::new(child_stdout);

let mut res = WorkspaceBuildData::default();
for message in cargo_metadata::Message::parse_stream(stdout).flatten() {
match message {
Message::BuildScriptExecuted(BuildScript {
package_id, out_dir, cfgs, env, ..
}) => {
let cfgs = {
let mut acc = Vec::new();
for cfg in cfgs {
match cfg.parse::<CfgFlag>() {
Ok(it) => acc.push(it),
Err(err) => {
anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
}
};
cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());

let mut child = cmd.spawn().map(JodChild)?;
let child_stdout = child.stdout.take().unwrap();
let stdout = BufReader::new(child_stdout);

let mut res = WorkspaceBuildData::default();
for message in cargo_metadata::Message::parse_stream(stdout).flatten() {
match message {
Message::BuildScriptExecuted(BuildScript {
package_id,
out_dir,
cfgs,
env,
..
}) => {
let cfgs = {
let mut acc = Vec::new();
for cfg in cfgs {
match cfg.parse::<CfgFlag>() {
Ok(it) => acc.push(it),
Err(err) => {
anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
}
};
}
acc
};
let package_build_data =
res.per_package.entry(package_id.repr.clone()).or_default();
// cargo_metadata crate returns default (empty) path for
// older cargos, which is not absolute, so work around that.
if !out_dir.as_str().is_empty() {
let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
package_build_data.out_dir = Some(out_dir);
package_build_data.cfgs = cfgs;
}
acc
};
let package_build_data =
res.per_package.entry(package_id.repr.clone()).or_default();
// cargo_metadata crate returns default (empty) path for
// older cargos, which is not absolute, so work around that.
if !out_dir.as_str().is_empty() {
let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
package_build_data.out_dir = Some(out_dir);
package_build_data.cfgs = cfgs;
}

package_build_data.envs = env;
}
Message::CompilerArtifact(message) => {
progress(format!("metadata {}", message.target.name));

if message.target.kind.contains(&"proc-macro".to_string()) {
let package_id = message.package_id;
// Skip rmeta file
if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) {
let filename = AbsPathBuf::assert(PathBuf::from(&filename));
let package_build_data =
res.per_package.entry(package_id.repr.clone()).or_default();
package_build_data.proc_macro_dylib_path = Some(filename);
package_build_data.envs = env;
}
Message::CompilerArtifact(message) => {
progress(format!("metadata {}", message.target.name));

if message.target.kind.contains(&"proc-macro".to_string()) {
let package_id = message.package_id;
// Skip rmeta file
if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
{
let filename = AbsPathBuf::assert(PathBuf::from(&filename));
let package_build_data =
res.per_package.entry(package_id.repr.clone()).or_default();
package_build_data.proc_macro_dylib_path = Some(filename);
}
}
}
Message::CompilerMessage(message) => {
progress(message.target.name.clone());
}
Message::BuildFinished(_) => {}
Message::TextLine(_) => {}
_ => {}
}
Message::CompilerMessage(message) => {
progress(message.target.name.clone());
}
Message::BuildFinished(_) => {}
Message::TextLine(_) => {}
_ => {}
}
}

for package in packages {
let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
inject_cargo_env(package, package_build_data);
if let Some(out_dir) = &package_build_data.out_dir {
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
for package in packages {
let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
inject_cargo_env(package, package_build_data);
if let Some(out_dir) = &package_build_data.out_dir {
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
}
}
}
}

let output = child.into_inner().wait_with_output()?;
if !output.status.success() {
let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
if stderr.is_empty() {
stderr = "cargo check failed".to_string();
let output = child.into_inner().wait_with_output()?;
if !output.status.success() {
let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
if stderr.is_empty() {
stderr = "cargo check failed".to_string();
}
res.error = Some(stderr)
}
res.error = Some(stderr)
}

Ok(res)
Ok(res)
}
}

// FIXME: File a better way to know if it is a dylib
Expand Down
7 changes: 5 additions & 2 deletions crates/rust-analyzer/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ fn benchmark_integrated_highlighting() {
let file = "./crates/ide_db/src/apply_change.rs";

let cargo_config = Default::default();
let load_cargo_config =
LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro: false };
let load_cargo_config = LoadCargoConfig {
load_out_dirs_from_check: true,
wrap_rustc: false,
with_proc_macro: false,
};

let (mut host, vfs, _proc_macro) = {
let _it = stdx::timeit("workspace loading");
Expand Down
15 changes: 15 additions & 0 deletions crates/rust-analyzer/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
mod flags;
mod logger;
mod rustc_wrapper;

use std::{convert::TryFrom, env, fs, path::Path, process};

Expand All @@ -26,6 +27,20 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

fn main() {
if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
let mut args = std::env::args_os();
let _me = args.next().unwrap();
let rustc = args.next().unwrap();
let code = match rustc_wrapper::run_rustc_skipping_cargo_checking(rustc, args.collect()) {
Ok(rustc_wrapper::ExitCode(code)) => code.unwrap_or(102),
Err(err) => {
eprintln!("{}", err);
101
}
};
process::exit(code);
}

if let Err(err) = try_main() {
log::error!("Unexpected error: {}", err);
eprintln!("{}", err);
Expand Down
46 changes: 46 additions & 0 deletions crates/rust-analyzer/src/bin/rustc_wrapper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! We setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself during the
//! initial `cargo check`. That way, we avoid checking the actual project, and
//! only build proc macros and build.rs.
//!
//! Code taken from IntelliJ :0)
//! https://github.com/intellij-rust/intellij-rust/blob/master/native-helper/src/main.rs
use std::{
ffi::OsString,
io,
process::{Command, Stdio},
};

/// ExitCode/ExitStatus are impossible to create :(.
pub(crate) struct ExitCode(pub(crate) Option<i32>);

pub(crate) fn run_rustc_skipping_cargo_checking(
rustc_executable: OsString,
args: Vec<OsString>,
) -> io::Result<ExitCode> {
let is_cargo_check = args.iter().any(|arg| {
let arg = arg.to_string_lossy();
// `cargo check` invokes `rustc` with `--emit=metadata` argument.
//
// https://doc.rust-lang.org/rustc/command-line-arguments.html#--emit-specifies-the-types-of-output-files-to-generate
// link — Generates the crates specified by --crate-type. The default
// output filenames depend on the crate type and platform. This
// is the default if --emit is not specified.
// metadata — Generates a file containing metadata about the crate.
// The default output filename is CRATE_NAME.rmeta.
arg.starts_with("--emit=") && arg.contains("metadata") && !arg.contains("link")
});
if is_cargo_check {
return Ok(ExitCode(Some(0)));
}
run_rustc(rustc_executable, args)
}

fn run_rustc(rustc_executable: OsString, args: Vec<OsString>) -> io::Result<ExitCode> {
let mut child = Command::new(rustc_executable)
.args(args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
Ok(ExitCode(child.wait()?.code()))
}
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/cli/analysis_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl AnalysisStatsCmd {
cargo_config.no_sysroot = self.no_sysroot;
let load_cargo_config = LoadCargoConfig {
load_out_dirs_from_check: self.load_output_dirs,
wrap_rustc: false,
with_proc_macro: self.with_proc_macro,
};
let (host, vfs, _proc_macro) =
Expand Down
3 changes: 2 additions & 1 deletion crates/rust-analyzer/src/cli/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ pub fn diagnostics(
with_proc_macro: bool,
) -> Result<()> {
let cargo_config = Default::default();
let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check, with_proc_macro };
let load_cargo_config =
LoadCargoConfig { load_out_dirs_from_check, with_proc_macro, wrap_rustc: false };
let (host, _vfs, _proc_macro) =
load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {})?;
let db = host.raw_database();
Expand Down
Loading