Remove unnecessary CfgFlag definition in project-model

This commit is contained in:
Lukas Wirth 2024-08-07 14:27:59 +02:00
parent ee10731c31
commit d2fe906a62
8 changed files with 367 additions and 376 deletions

View file

@ -690,6 +690,14 @@ impl Env {
pub fn extend_from_other(&mut self, other: &Env) { pub fn extend_from_other(&mut self, other: &Env) {
self.entries.extend(other.entries.iter().map(|(x, y)| (x.to_owned(), y.to_owned()))); self.entries.extend(other.entries.iter().map(|(x, y)| (x.to_owned(), y.to_owned())));
} }
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn insert(&mut self, k: impl Into<String>, v: impl Into<String>) -> Option<String> {
self.entries.insert(k.into(), v.into())
}
} }
impl From<Env> for Vec<(String, String)> { impl From<Env> for Vec<(String, String)> {
@ -700,6 +708,15 @@ impl From<Env> for Vec<(String, String)> {
} }
} }
impl<'a> IntoIterator for &'a Env {
type Item = (&'a String, &'a String);
type IntoIter = std::collections::hash_map::Iter<'a, String, String>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter()
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct CyclicDependenciesError { pub struct CyclicDependenciesError {
path: Vec<(CrateId, Option<CrateDisplayName>)>, path: Vec<(CrateId, Option<CrateDisplayName>)>,

View file

@ -108,6 +108,14 @@ impl<'a> IntoIterator for &'a CfgOptions {
} }
} }
impl FromIterator<CfgAtom> for CfgOptions {
fn from_iter<T: IntoIterator<Item = CfgAtom>>(iter: T) -> Self {
let mut options = CfgOptions::default();
options.extend(iter);
options
}
}
#[derive(Default, Clone, Debug, PartialEq, Eq)] #[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CfgDiff { pub struct CfgDiff {
// Invariants: No duplicates, no atom that's both in `enable` and `disable`. // Invariants: No duplicates, no atom that's both in `enable` and `disable`.

View file

@ -1,14 +1,16 @@
//! Workspace information we get from cargo consists of two pieces. The first is //! Logic to invoke `cargo` for building build-dependencies (build scripts and proc-macros) as well as
//! the output of `cargo metadata`. The second is the output of running //! executing the build scripts to fetch required dependency information (`OUT_DIR` env var, extra
//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc //! cfg flags, etc).
//! macro.
//! //!
//! This module implements this second part. We use "build script" terminology //! In essence this just invokes `cargo` with the appropriate output format which we consume,
//! here, but it covers procedural macros as well. //! but if enabled we will also use `RUSTC_WRAPPER` to only compile the build scripts and
//! proc-macros and skip everything else.
use std::{cell::RefCell, io, mem, path, process::Command}; use std::{cell::RefCell, io, mem, process::Command};
use base_db::Env;
use cargo_metadata::{camino::Utf8Path, Message}; use cargo_metadata::{camino::Utf8Path, Message};
use cfg::CfgAtom;
use itertools::Itertools; use itertools::Itertools;
use la_arena::ArenaMap; use la_arena::ArenaMap;
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
@ -17,7 +19,7 @@ use serde::Deserialize;
use toolchain::Tool; use toolchain::Tool;
use crate::{ use crate::{
cfg::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
InvocationStrategy, ManifestPath, Package, Sysroot, TargetKind, InvocationStrategy, ManifestPath, Package, Sysroot, TargetKind,
}; };
@ -32,12 +34,12 @@ pub struct WorkspaceBuildScripts {
#[derive(Debug, Clone, Default, PartialEq, Eq)] #[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct BuildScriptOutput { pub(crate) struct BuildScriptOutput {
/// List of config flags defined by this package's build script. /// List of config flags defined by this package's build script.
pub(crate) cfgs: Vec<CfgFlag>, pub(crate) cfgs: Vec<CfgAtom>,
/// List of cargo-related environment variables with their value. /// List of cargo-related environment variables with their value.
/// ///
/// If the package has a build script which defines environment variables, /// If the package has a build script which defines environment variables,
/// they can also be found here. /// they can also be found here.
pub(crate) envs: Vec<(String, String)>, pub(crate) envs: Env,
/// Directory where a build script might place its output. /// Directory where a build script might place its output.
pub(crate) out_dir: Option<AbsPathBuf>, pub(crate) out_dir: Option<AbsPathBuf>,
/// Path to the proc-macro library file if this package exposes proc-macros. /// Path to the proc-macro library file if this package exposes proc-macros.
@ -45,7 +47,7 @@ pub(crate) struct BuildScriptOutput {
} }
impl BuildScriptOutput { impl BuildScriptOutput {
fn is_unchanged(&self) -> bool { fn is_empty(&self) -> bool {
self.cfgs.is_empty() self.cfgs.is_empty()
&& self.envs.is_empty() && self.envs.is_empty()
&& self.out_dir.is_none() && self.out_dir.is_none()
@ -54,85 +56,6 @@ impl BuildScriptOutput {
} }
impl WorkspaceBuildScripts { impl WorkspaceBuildScripts {
fn build_command(
config: &CargoConfig,
allowed_features: &FxHashSet<String>,
manifest_path: &ManifestPath,
sysroot: &Sysroot,
) -> io::Result<Command> {
let mut cmd = match config.run_build_script_command.as_deref() {
Some([program, args @ ..]) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
_ => {
let mut cmd = sysroot.tool(Tool::Cargo);
cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
cmd.args(&config.extra_args);
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
if let Some(target_dir) = &config.target_dir {
cmd.arg("--target-dir").arg(target_dir);
}
// --all-targets includes tests, benches and examples in addition to the
// default lib and bins. This is an independent concept from the --target
// flag below.
if config.all_targets {
cmd.arg("--all-targets");
}
if let Some(target) = &config.target {
cmd.args(["--target", target]);
}
match &config.features {
CargoFeatures::All => {
cmd.arg("--all-features");
}
CargoFeatures::Selected { features, no_default_features } => {
if *no_default_features {
cmd.arg("--no-default-features");
}
if !features.is_empty() {
cmd.arg("--features");
cmd.arg(
features
.iter()
.filter(|&feat| allowed_features.contains(feat))
.join(","),
);
}
}
}
if manifest_path.is_rust_manifest() {
cmd.arg("-Zscript");
}
cmd.arg("--keep-going");
cmd
}
};
cmd.envs(&config.extra_env);
if config.wrap_rustc_in_build_scripts {
// 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");
}
Ok(cmd)
}
/// Runs the build scripts for the given workspace /// Runs the build scripts for the given workspace
pub(crate) fn run_for_workspace( pub(crate) fn run_for_workspace(
config: &CargoConfig, config: &CargoConfig,
@ -141,17 +64,19 @@ impl WorkspaceBuildScripts {
sysroot: &Sysroot, sysroot: &Sysroot,
) -> io::Result<WorkspaceBuildScripts> { ) -> io::Result<WorkspaceBuildScripts> {
let current_dir = match &config.invocation_location { let current_dir = match &config.invocation_location {
InvocationLocation::Root(root) if config.run_build_script_command.is_some() => { InvocationLocation::Root(root) if config.run_build_script_command.is_some() => root,
root.as_path()
}
_ => workspace.workspace_root(), _ => workspace.workspace_root(),
} };
.as_ref();
let allowed_features = workspace.workspace_features(); let allowed_features = workspace.workspace_features();
let cmd = let cmd = Self::build_command(
Self::build_command(config, &allowed_features, workspace.manifest_path(), sysroot)?; config,
Self::run_per_ws(cmd, workspace, current_dir, progress) &allowed_features,
workspace.manifest_path(),
current_dir,
sysroot,
)?;
Self::run_per_ws(cmd, workspace, progress)
} }
/// Runs the build scripts by invoking the configured command *once*. /// Runs the build scripts by invoking the configured command *once*.
@ -178,6 +103,7 @@ impl WorkspaceBuildScripts {
&Default::default(), &Default::default(),
// This is not gonna be used anyways, so just construct a dummy here // This is not gonna be used anyways, so just construct a dummy here
&ManifestPath::try_from(workspace_root.clone()).unwrap(), &ManifestPath::try_from(workspace_root.clone()).unwrap(),
current_dir,
&Sysroot::empty(), &Sysroot::empty(),
)?; )?;
// NB: Cargo.toml could have been modified between `cargo metadata` and // NB: Cargo.toml could have been modified between `cargo metadata` and
@ -206,7 +132,6 @@ impl WorkspaceBuildScripts {
let errors = Self::run_command( let errors = Self::run_command(
cmd, cmd,
current_dir.as_path().as_ref(),
|package, cb| { |package, cb| {
if let Some(&(package, workspace)) = by_id.get(package) { if let Some(&(package, workspace)) = by_id.get(package) {
cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]); cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]);
@ -225,7 +150,7 @@ impl WorkspaceBuildScripts {
for (idx, workspace) in workspaces.iter().enumerate() { for (idx, workspace) in workspaces.iter().enumerate() {
for package in workspace.packages() { for package in workspace.packages() {
let package_build_data = &mut res[idx].outputs[package]; let package_build_data = &mut res[idx].outputs[package];
if !package_build_data.is_unchanged() { if !package_build_data.is_empty() {
tracing::info!( tracing::info!(
"{}: {package_build_data:?}", "{}: {package_build_data:?}",
workspace[package].manifest.parent(), workspace[package].manifest.parent(),
@ -238,151 +163,6 @@ impl WorkspaceBuildScripts {
Ok(res) Ok(res)
} }
fn run_per_ws(
cmd: Command,
workspace: &CargoWorkspace,
current_dir: &path::Path,
progress: &dyn Fn(String),
) -> io::Result<WorkspaceBuildScripts> {
let mut res = WorkspaceBuildScripts::default();
let outputs = &mut res.outputs;
// 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`.
let mut by_id: FxHashMap<String, Package> = FxHashMap::default();
for package in workspace.packages() {
outputs.insert(package, BuildScriptOutput::default());
by_id.insert(workspace[package].id.clone(), package);
}
res.error = Self::run_command(
cmd,
current_dir,
|package, cb| {
if let Some(&package) = by_id.get(package) {
cb(&workspace[package].name, &mut outputs[package]);
}
},
progress,
)?;
if tracing::enabled!(tracing::Level::INFO) {
for package in workspace.packages() {
let package_build_data = &outputs[package];
if !package_build_data.is_unchanged() {
tracing::info!(
"{}: {package_build_data:?}",
workspace[package].manifest.parent(),
);
}
}
}
Ok(res)
}
fn run_command(
mut cmd: Command,
current_dir: &path::Path,
// ideally this would be something like:
// with_output_for: impl FnMut(&str, dyn FnOnce(&mut BuildScriptOutput)),
// but owned trait objects aren't a thing
mut with_output_for: impl FnMut(&str, &mut dyn FnMut(&str, &mut BuildScriptOutput)),
progress: &dyn Fn(String),
) -> io::Result<Option<String>> {
let errors = RefCell::new(String::new());
let push_err = |err: &str| {
let mut e = errors.borrow_mut();
e.push_str(err);
e.push('\n');
};
tracing::info!("Running build scripts in {}: {:?}", current_dir.display(), cmd);
cmd.current_dir(current_dir);
let output = stdx::process::spawn_with_streaming_output(
cmd,
&mut |line| {
// Copy-pasted from existing cargo_metadata. It seems like we
// should be using serde_stacker here?
let mut deserializer = serde_json::Deserializer::from_str(line);
deserializer.disable_recursion_limit();
let message = Message::deserialize(&mut deserializer)
.unwrap_or_else(|_| Message::TextLine(line.to_owned()));
match message {
Message::BuildScriptExecuted(mut message) => {
with_output_for(&message.package_id.repr, &mut |name, data| {
progress(format!("running build-script: {name}"));
let cfgs = {
let mut acc = Vec::new();
for cfg in &message.cfgs {
match cfg.parse::<CfgFlag>() {
Ok(it) => acc.push(it),
Err(err) => {
push_err(&format!(
"invalid cfg from cargo-metadata: {err}"
));
return;
}
};
}
acc
};
if !message.env.is_empty() {
data.envs = mem::take(&mut message.env);
}
// cargo_metadata crate returns default (empty) path for
// older cargos, which is not absolute, so work around that.
let out_dir = mem::take(&mut message.out_dir);
if !out_dir.as_str().is_empty() {
let out_dir = AbsPathBuf::assert(out_dir);
// inject_cargo_env(package, package_build_data);
data.envs.push(("OUT_DIR".to_owned(), out_dir.as_str().to_owned()));
data.out_dir = Some(out_dir);
data.cfgs = cfgs;
}
});
}
Message::CompilerArtifact(message) => {
with_output_for(&message.package_id.repr, &mut |name, data| {
progress(format!("building proc-macros: {name}"));
if message.target.kind.iter().any(|k| k == "proc-macro") {
// Skip rmeta file
if let Some(filename) =
message.filenames.iter().find(|name| is_dylib(name))
{
let filename = AbsPath::assert(filename);
data.proc_macro_dylib_path = Some(filename.to_owned());
}
}
});
}
Message::CompilerMessage(message) => {
progress(message.target.name);
if let Some(diag) = message.message.rendered.as_deref() {
push_err(diag);
}
}
Message::BuildFinished(_) => {}
Message::TextLine(_) => {}
_ => {}
}
},
&mut |line| {
push_err(line);
},
)?;
let errors = if !output.status.success() {
let errors = errors.into_inner();
Some(if errors.is_empty() { "cargo check failed".to_owned() } else { errors })
} else {
None
};
Ok(errors)
}
pub fn error(&self) -> Option<&str> { pub fn error(&self) -> Option<&str> {
self.error.as_deref() self.error.as_deref()
} }
@ -391,6 +171,7 @@ impl WorkspaceBuildScripts {
self.outputs.get(idx) self.outputs.get(idx)
} }
/// Assembles build script outputs for the rustc crates via `--print target-libdir`.
pub(crate) fn rustc_crates( pub(crate) fn rustc_crates(
rustc: &CargoWorkspace, rustc: &CargoWorkspace,
current_dir: &AbsPath, current_dir: &AbsPath,
@ -457,7 +238,7 @@ impl WorkspaceBuildScripts {
if tracing::enabled!(tracing::Level::INFO) { if tracing::enabled!(tracing::Level::INFO) {
for package in rustc.packages() { for package in rustc.packages() {
let package_build_data = &bs.outputs[package]; let package_build_data = &bs.outputs[package];
if !package_build_data.is_unchanged() { if !package_build_data.is_empty() {
tracing::info!( tracing::info!(
"{}: {package_build_data:?}", "{}: {package_build_data:?}",
rustc[package].manifest.parent(), rustc[package].manifest.parent(),
@ -472,6 +253,226 @@ impl WorkspaceBuildScripts {
} }
bs bs
} }
fn run_per_ws(
cmd: Command,
workspace: &CargoWorkspace,
progress: &dyn Fn(String),
) -> io::Result<WorkspaceBuildScripts> {
let mut res = WorkspaceBuildScripts::default();
let outputs = &mut res.outputs;
// 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`.
let mut by_id: FxHashMap<String, Package> = FxHashMap::default();
for package in workspace.packages() {
outputs.insert(package, BuildScriptOutput::default());
by_id.insert(workspace[package].id.clone(), package);
}
res.error = Self::run_command(
cmd,
|package, cb| {
if let Some(&package) = by_id.get(package) {
cb(&workspace[package].name, &mut outputs[package]);
}
},
progress,
)?;
if tracing::enabled!(tracing::Level::INFO) {
for package in workspace.packages() {
let package_build_data = &outputs[package];
if !package_build_data.is_empty() {
tracing::info!(
"{}: {package_build_data:?}",
workspace[package].manifest.parent(),
);
}
}
}
Ok(res)
}
fn run_command(
cmd: Command,
// ideally this would be something like:
// with_output_for: impl FnMut(&str, dyn FnOnce(&mut BuildScriptOutput)),
// but owned trait objects aren't a thing
mut with_output_for: impl FnMut(&str, &mut dyn FnMut(&str, &mut BuildScriptOutput)),
progress: &dyn Fn(String),
) -> io::Result<Option<String>> {
let errors = RefCell::new(String::new());
let push_err = |err: &str| {
let mut e = errors.borrow_mut();
e.push_str(err);
e.push('\n');
};
tracing::info!("Running build scripts: {:?}", cmd);
let output = stdx::process::spawn_with_streaming_output(
cmd,
&mut |line| {
// Copy-pasted from existing cargo_metadata. It seems like we
// should be using serde_stacker here?
let mut deserializer = serde_json::Deserializer::from_str(line);
deserializer.disable_recursion_limit();
let message = Message::deserialize(&mut deserializer)
.unwrap_or_else(|_| Message::TextLine(line.to_owned()));
match message {
Message::BuildScriptExecuted(mut message) => {
with_output_for(&message.package_id.repr, &mut |name, data| {
progress(format!("running build-script: {name}"));
let cfgs = {
let mut acc = Vec::new();
for cfg in &message.cfgs {
match crate::parse_cfg(cfg) {
Ok(it) => acc.push(it),
Err(err) => {
push_err(&format!(
"invalid cfg from cargo-metadata: {err}"
));
return;
}
};
}
acc
};
data.envs.extend(message.env.drain(..));
// cargo_metadata crate returns default (empty) path for
// older cargos, which is not absolute, so work around that.
let out_dir = mem::take(&mut message.out_dir);
if !out_dir.as_str().is_empty() {
let out_dir = AbsPathBuf::assert(out_dir);
// inject_cargo_env(package, package_build_data);
data.envs.insert("OUT_DIR", out_dir.as_str());
data.out_dir = Some(out_dir);
data.cfgs = cfgs;
}
});
}
Message::CompilerArtifact(message) => {
with_output_for(&message.package_id.repr, &mut |name, data| {
progress(format!("building proc-macros: {name}"));
if message.target.kind.iter().any(|k| k == "proc-macro") {
// Skip rmeta file
if let Some(filename) =
message.filenames.iter().find(|file| is_dylib(file))
{
let filename = AbsPath::assert(filename);
data.proc_macro_dylib_path = Some(filename.to_owned());
}
}
});
}
Message::CompilerMessage(message) => {
progress(message.target.name);
if let Some(diag) = message.message.rendered.as_deref() {
push_err(diag);
}
}
Message::BuildFinished(_) => {}
Message::TextLine(_) => {}
_ => {}
}
},
&mut |line| {
push_err(line);
},
)?;
let errors = if !output.status.success() {
let errors = errors.into_inner();
Some(if errors.is_empty() { "cargo check failed".to_owned() } else { errors })
} else {
None
};
Ok(errors)
}
fn build_command(
config: &CargoConfig,
allowed_features: &FxHashSet<String>,
manifest_path: &ManifestPath,
current_dir: &AbsPath,
sysroot: &Sysroot,
) -> io::Result<Command> {
let mut cmd = match config.run_build_script_command.as_deref() {
Some([program, args @ ..]) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
_ => {
let mut cmd = sysroot.tool(Tool::Cargo);
cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
cmd.args(&config.extra_args);
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
if let Some(target_dir) = &config.target_dir {
cmd.arg("--target-dir").arg(target_dir);
}
// --all-targets includes tests, benches and examples in addition to the
// default lib and bins. This is an independent concept from the --target
// flag below.
if config.all_targets {
cmd.arg("--all-targets");
}
if let Some(target) = &config.target {
cmd.args(["--target", target]);
}
match &config.features {
CargoFeatures::All => {
cmd.arg("--all-features");
}
CargoFeatures::Selected { features, no_default_features } => {
if *no_default_features {
cmd.arg("--no-default-features");
}
if !features.is_empty() {
cmd.arg("--features");
cmd.arg(
features
.iter()
.filter(|&feat| allowed_features.contains(feat))
.join(","),
);
}
}
}
if manifest_path.is_rust_manifest() {
cmd.arg("-Zscript");
}
cmd.arg("--keep-going");
cmd
}
};
cmd.current_dir(current_dir);
cmd.envs(&config.extra_env);
if config.wrap_rustc_in_build_scripts {
// 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");
}
Ok(cmd)
}
} }
// FIXME: Find a better way to know if it is a dylib. // FIXME: Find a better way to know if it is a dylib.

View file

@ -1,100 +0,0 @@
//! Parsing of CfgFlags as command line arguments, as in
//!
//! rustc main.rs --cfg foo --cfg 'feature="bar"'
use std::{fmt, str::FromStr};
use cfg::{CfgDiff, CfgOptions};
use intern::Symbol;
use rustc_hash::FxHashMap;
use serde::Serialize;
#[derive(Clone, Eq, PartialEq, Debug, Serialize)]
pub enum CfgFlag {
Atom(String),
KeyValue { key: String, value: String },
}
impl FromStr for CfgFlag {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let res = match s.split_once('=') {
Some((key, value)) => {
if !(value.starts_with('"') && value.ends_with('"')) {
return Err(format!("Invalid cfg ({s:?}), value should be in quotes"));
}
let key = key.to_owned();
let value = value[1..value.len() - 1].to_string();
CfgFlag::KeyValue { key, value }
}
None => CfgFlag::Atom(s.into()),
};
Ok(res)
}
}
impl<'de> serde::Deserialize<'de> for CfgFlag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
}
}
impl Extend<CfgFlag> for CfgOptions {
fn extend<T: IntoIterator<Item = CfgFlag>>(&mut self, iter: T) {
for cfg_flag in iter {
match cfg_flag {
CfgFlag::Atom(it) => self.insert_atom(Symbol::intern(&it)),
CfgFlag::KeyValue { key, value } => {
self.insert_key_value(Symbol::intern(&key), Symbol::intern(&value))
}
}
}
}
}
impl FromIterator<CfgFlag> for CfgOptions {
fn from_iter<T: IntoIterator<Item = CfgFlag>>(iter: T) -> Self {
let mut this = CfgOptions::default();
this.extend(iter);
this
}
}
impl fmt::Display for CfgFlag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CfgFlag::Atom(atom) => f.write_str(atom),
CfgFlag::KeyValue { key, value } => {
f.write_str(key)?;
f.write_str("=")?;
f.write_str(value)
}
}
}
}
/// A set of cfg-overrides per crate.
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct CfgOverrides {
/// A global set of overrides matching all crates.
pub global: CfgDiff,
/// A set of overrides matching specific crates.
pub selective: FxHashMap<String, CfgDiff>,
}
impl CfgOverrides {
pub fn len(&self) -> usize {
self.global.len() + self.selective.values().map(|it| it.len()).sum::<usize>()
}
pub fn apply(&self, cfg_options: &mut CfgOptions, name: &str) {
if !self.global.is_empty() {
cfg_options.apply_diff(self.global.clone());
};
if let Some(diff) = self.selective.get(name) {
cfg_options.apply_diff(diff.clone());
};
}
}

View file

@ -15,9 +15,8 @@
//! procedural macros). //! procedural macros).
//! * Lowering of concrete model to a [`base_db::CrateGraph`] //! * Lowering of concrete model to a [`base_db::CrateGraph`]
mod build_scripts; mod build_dependencies;
mod cargo_workspace; mod cargo_workspace;
mod cfg;
mod env; mod env;
mod manifest_path; mod manifest_path;
pub mod project_json; pub mod project_json;
@ -41,12 +40,11 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
pub use crate::{ pub use crate::{
build_scripts::WorkspaceBuildScripts, build_dependencies::WorkspaceBuildScripts,
cargo_workspace::{ cargo_workspace::{
CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency, CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency,
RustLibSource, Target, TargetData, TargetKind, RustLibSource, Target, TargetData, TargetKind,
}, },
cfg::CfgOverrides,
manifest_path::ManifestPath, manifest_path::ManifestPath,
project_json::{ProjectJson, ProjectJsonData}, project_json::{ProjectJson, ProjectJsonData},
sysroot::Sysroot, sysroot::Sysroot,
@ -201,3 +199,42 @@ pub enum InvocationLocation {
#[default] #[default]
Workspace, Workspace,
} }
/// A set of cfg-overrides per crate.
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct CfgOverrides {
/// A global set of overrides matching all crates.
pub global: cfg::CfgDiff,
/// A set of overrides matching specific crates.
pub selective: rustc_hash::FxHashMap<String, cfg::CfgDiff>,
}
impl CfgOverrides {
pub fn len(&self) -> usize {
self.global.len() + self.selective.values().map(|it| it.len()).sum::<usize>()
}
pub fn apply(&self, cfg_options: &mut cfg::CfgOptions, name: &str) {
if !self.global.is_empty() {
cfg_options.apply_diff(self.global.clone());
};
if let Some(diff) = self.selective.get(name) {
cfg_options.apply_diff(diff.clone());
};
}
}
fn parse_cfg(s: &str) -> Result<cfg::CfgAtom, String> {
let res = match s.split_once('=') {
Some((key, value)) => {
if !(value.starts_with('"') && value.ends_with('"')) {
return Err(format!("Invalid cfg ({s:?}), value should be in quotes"));
}
let key = intern::Symbol::intern(key);
let value = intern::Symbol::intern(&value[1..value.len() - 1]);
cfg::CfgAtom::KeyValue { key, value }
}
None => cfg::CfgAtom::Flag(intern::Symbol::intern(s)),
};
Ok(res)
}

View file

@ -50,12 +50,13 @@
//! rust-project.json over time via configuration request!) //! rust-project.json over time via configuration request!)
use base_db::{CrateDisplayName, CrateName}; use base_db::{CrateDisplayName, CrateName};
use cfg::CfgAtom;
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::{de, Deserialize, Serialize}; use serde::{de, Deserialize, Serialize};
use span::Edition; use span::Edition;
use crate::{cfg::CfgFlag, ManifestPath, TargetKind}; use crate::{ManifestPath, TargetKind};
/// Roots and crates that compose this Rust project. /// Roots and crates that compose this Rust project.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -82,7 +83,7 @@ pub struct Crate {
pub(crate) edition: Edition, pub(crate) edition: Edition,
pub(crate) version: Option<String>, pub(crate) version: Option<String>,
pub(crate) deps: Vec<Dep>, pub(crate) deps: Vec<Dep>,
pub(crate) cfg: Vec<CfgFlag>, pub(crate) cfg: Vec<CfgAtom>,
pub(crate) target: Option<String>, pub(crate) target: Option<String>,
pub(crate) env: FxHashMap<String, String>, pub(crate) env: FxHashMap<String, String>,
pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>, pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
@ -319,7 +320,8 @@ struct CrateData {
version: Option<semver::Version>, version: Option<semver::Version>,
deps: Vec<Dep>, deps: Vec<Dep>,
#[serde(default)] #[serde(default)]
cfg: Vec<CfgFlag>, #[serde(with = "cfg_")]
cfg: Vec<CfgAtom>,
target: Option<String>, target: Option<String>,
#[serde(default)] #[serde(default)]
env: FxHashMap<String, String>, env: FxHashMap<String, String>,
@ -334,6 +336,33 @@ struct CrateData {
build: Option<BuildData>, build: Option<BuildData>,
} }
mod cfg_ {
use cfg::CfgAtom;
use serde::{Deserialize, Serialize};
pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Vec<CfgAtom>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cfg: Vec<String> = Vec::deserialize(deserializer)?;
cfg.into_iter().map(|it| crate::parse_cfg(&it).map_err(serde::de::Error::custom)).collect()
}
pub(super) fn serialize<S>(cfg: &[CfgAtom], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
cfg.iter()
.map(|cfg| match cfg {
CfgAtom::Flag(flag) => flag.as_str().to_owned(),
CfgAtom::KeyValue { key, value } => {
format!("{}=\"{}\"", key.as_str(), value.as_str())
}
})
.collect::<Vec<String>>()
.serialize(serializer)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename = "edition")] #[serde(rename = "edition")]
enum EditionData { enum EditionData {

View file

@ -1,10 +1,12 @@
//! Runs `rustc --print cfg` to get built-in cfg flags. //! Runs `rustc --print cfg` to get built-in cfg flags.
use anyhow::Context; use anyhow::Context;
use cfg::CfgAtom;
use intern::Symbol;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use toolchain::Tool; use toolchain::Tool;
use crate::{cfg::CfgFlag, utf8_stdout, ManifestPath, Sysroot}; use crate::{utf8_stdout, ManifestPath, Sysroot};
/// Determines how `rustc --print cfg` is discovered and invoked. /// Determines how `rustc --print cfg` is discovered and invoked.
pub(crate) enum RustcCfgConfig<'a> { pub(crate) enum RustcCfgConfig<'a> {
@ -20,15 +22,15 @@ pub(crate) fn get(
target: Option<&str>, target: Option<&str>,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
config: RustcCfgConfig<'_>, config: RustcCfgConfig<'_>,
) -> Vec<CfgFlag> { ) -> Vec<CfgAtom> {
let _p = tracing::info_span!("rustc_cfg::get").entered(); let _p = tracing::info_span!("rustc_cfg::get").entered();
let mut res = Vec::with_capacity(6 * 2 + 1); let mut res: Vec<_> = Vec::with_capacity(6 * 2 + 1);
// Some nightly-only cfgs, which are required for stdlib // Some nightly-only cfgs, which are required for stdlib
res.push(CfgFlag::Atom("target_thread_local".into())); res.push(CfgAtom::Flag(Symbol::intern("target_thread_local")));
for ty in ["8", "16", "32", "64", "cas", "ptr"] { for ty in ["8", "16", "32", "64", "cas", "ptr"] {
for key in ["target_has_atomic", "target_has_atomic_load_store"] { for key in ["target_has_atomic", "target_has_atomic_load_store"] {
res.push(CfgFlag::KeyValue { key: key.to_owned(), value: ty.into() }); res.push(CfgAtom::KeyValue { key: Symbol::intern(key), value: Symbol::intern(ty) });
} }
} }
@ -42,8 +44,7 @@ pub(crate) fn get(
} }
}; };
let rustc_cfgs = let rustc_cfgs = rustc_cfgs.lines().map(crate::parse_cfg).collect::<Result<Vec<_>, _>>();
rustc_cfgs.lines().map(|it| it.parse::<CfgFlag>()).collect::<Result<Vec<_>, _>>();
match rustc_cfgs { match rustc_cfgs {
Ok(rustc_cfgs) => { Ok(rustc_cfgs) => {

View file

@ -20,16 +20,15 @@ use tracing::instrument;
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
build_scripts::BuildScriptOutput, build_dependencies::BuildScriptOutput,
cargo_workspace::{DepKind, PackageData, RustLibSource}, cargo_workspace::{DepKind, PackageData, RustLibSource},
cfg::{CfgFlag, CfgOverrides},
env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env}, env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env},
project_json::{Crate, CrateArrayIdx}, project_json::{Crate, CrateArrayIdx},
rustc_cfg::{self, RustcCfgConfig}, rustc_cfg::{self, RustcCfgConfig},
sysroot::{SysrootCrate, SysrootMode}, sysroot::{SysrootCrate, SysrootMode},
target_data_layout::{self, RustcDataLayoutConfig}, target_data_layout::{self, RustcDataLayoutConfig},
utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package, utf8_stdout, CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath,
ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
}; };
use tracing::{debug, error, info}; use tracing::{debug, error, info};
@ -55,7 +54,7 @@ pub struct ProjectWorkspace {
/// `rustc --print cfg`. /// `rustc --print cfg`.
// FIXME: make this a per-crate map, as, eg, build.rs might have a // FIXME: make this a per-crate map, as, eg, build.rs might have a
// different target. // different target.
pub rustc_cfg: Vec<CfgFlag>, pub rustc_cfg: Vec<CfgAtom>,
/// The toolchain version used by this workspace. /// The toolchain version used by this workspace.
pub toolchain: Option<Version>, pub toolchain: Option<Version>,
/// The target data layout queried for workspace. /// The target data layout queried for workspace.
@ -842,7 +841,7 @@ impl ProjectWorkspace {
#[instrument(skip_all)] #[instrument(skip_all)]
fn project_json_to_crate_graph( fn project_json_to_crate_graph(
rustc_cfg: Vec<CfgFlag>, rustc_cfg: Vec<CfgAtom>,
load: FileLoader<'_>, load: FileLoader<'_>,
project: &ProjectJson, project: &ProjectJson,
sysroot: &Sysroot, sysroot: &Sysroot,
@ -854,8 +853,8 @@ fn project_json_to_crate_graph(
let (public_deps, libproc_macro) = let (public_deps, libproc_macro) =
sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load); sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load);
let r_a_cfg_flag = CfgFlag::Atom("rust_analyzer".to_owned()); let r_a_cfg_flag = CfgAtom::Flag(sym::rust_analyzer.clone());
let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default(); let mut cfg_cache: FxHashMap<&str, Vec<CfgAtom>> = FxHashMap::default();
let idx_to_crate_id: FxHashMap<CrateArrayIdx, CrateId> = project let idx_to_crate_id: FxHashMap<CrateArrayIdx, CrateId> = project
.crates() .crates()
@ -962,7 +961,7 @@ fn cargo_to_crate_graph(
rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>, rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
cargo: &CargoWorkspace, cargo: &CargoWorkspace,
sysroot: &Sysroot, sysroot: &Sysroot,
rustc_cfg: Vec<CfgFlag>, rustc_cfg: Vec<CfgAtom>,
override_cfg: &CfgOverrides, override_cfg: &CfgOverrides,
build_scripts: &WorkspaceBuildScripts, build_scripts: &WorkspaceBuildScripts,
) -> (CrateGraph, ProcMacroPaths) { ) -> (CrateGraph, ProcMacroPaths) {
@ -1145,7 +1144,7 @@ fn cargo_to_crate_graph(
} }
fn detached_file_to_crate_graph( fn detached_file_to_crate_graph(
rustc_cfg: Vec<CfgFlag>, rustc_cfg: Vec<CfgAtom>,
load: FileLoader<'_>, load: FileLoader<'_>,
detached_file: &ManifestPath, detached_file: &ManifestPath,
sysroot: &Sysroot, sysroot: &Sysroot,
@ -1308,11 +1307,10 @@ fn add_target_crate_root(
None None
} else { } else {
let mut potential_cfg_options = cfg_options.clone(); let mut potential_cfg_options = cfg_options.clone();
potential_cfg_options.extend( potential_cfg_options.extend(pkg.features.iter().map(|feat| CfgAtom::KeyValue {
pkg.features key: sym::feature.clone(),
.iter() value: Symbol::intern(feat.0),
.map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }), }));
);
Some(potential_cfg_options) Some(potential_cfg_options)
}; };
let cfg_options = { let cfg_options = {
@ -1378,7 +1376,7 @@ impl SysrootPublicDeps {
fn sysroot_to_crate_graph( fn sysroot_to_crate_graph(
crate_graph: &mut CrateGraph, crate_graph: &mut CrateGraph,
sysroot: &Sysroot, sysroot: &Sysroot,
rustc_cfg: Vec<CfgFlag>, rustc_cfg: Vec<CfgAtom>,
load: FileLoader<'_>, load: FileLoader<'_>,
) -> (SysrootPublicDeps, Option<CrateId>) { ) -> (SysrootPublicDeps, Option<CrateId>) {
let _p = tracing::info_span!("sysroot_to_crate_graph").entered(); let _p = tracing::info_span!("sysroot_to_crate_graph").entered();