Skip to content

Add a config setting to disable the 'test' cfg in specified crates #9227

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
Jun 21, 2021
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
17 changes: 16 additions & 1 deletion crates/cfg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator
//! cfg defines conditional compiling options, `cfg` attribute parser and evaluator

mod cfg_expr;
mod dnf;
Expand Down Expand Up @@ -52,13 +52,28 @@ impl CfgOptions {
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CfgDiff {
// Invariants: No duplicates, no atom that's both in `enable` and `disable`.
enable: Vec<CfgAtom>,
disable: Vec<CfgAtom>,
}

impl CfgDiff {
/// Create a new CfgDiff. Will return None if the same item appears more than once in the set
/// of both.
pub fn new(enable: Vec<CfgAtom>, disable: Vec<CfgAtom>) -> Option<CfgDiff> {
let mut occupied = FxHashSet::default();
for item in enable.iter().chain(disable.iter()) {
if !occupied.insert(item) {
// was present
return None;
}
}

Some(CfgDiff { enable, disable })
}

/// Returns the total number of atoms changed by this diff.
pub fn len(&self) -> usize {
self.enable.len() + self.disable.len()
Expand Down
17 changes: 17 additions & 0 deletions crates/project_model/src/cargo_workspace.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! See [`CargoWorkspace`].

use std::iter;
use std::path::PathBuf;
use std::{convert::TryInto, ops, process::Command, sync::Arc};

Expand All @@ -12,6 +13,7 @@ use rustc_hash::FxHashMap;
use serde::Deserialize;
use serde_json::from_value;

use crate::CfgOverrides;
use crate::{build_data::BuildDataConfig, utf8_stdout};

/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
Expand Down Expand Up @@ -76,6 +78,21 @@ pub struct CargoConfig {

/// rustc private crate source
pub rustc_source: Option<RustcSource>,

/// crates to disable `#[cfg(test)]` on
pub unset_test_crates: Vec<String>,
}

impl CargoConfig {
pub fn cfg_overrides(&self) -> CfgOverrides {
self.unset_test_crates
.iter()
.cloned()
.zip(iter::repeat_with(|| {
cfg::CfgDiff::new(Vec::new(), vec![cfg::CfgAtom::Flag("test".into())]).unwrap()
}))
.collect()
}
}

pub type Package = Idx<PackageData>;
Expand Down
2 changes: 1 addition & 1 deletion crates/project_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub use crate::{
},
project_json::{ProjectJson, ProjectJsonData},
sysroot::Sysroot,
workspace::{PackageRoot, ProjectWorkspace},
workspace::{CfgOverrides, PackageRoot, ProjectWorkspace},
};

pub use proc_macro_api::ProcMacroClient;
Expand Down
124 changes: 77 additions & 47 deletions crates/project_model/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
use anyhow::{format_err, Context, Result};
use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
use cargo_workspace::DepKind;
use cfg::CfgOptions;
use cfg::{CfgDiff, CfgOptions};
use paths::{AbsPath, AbsPathBuf};
use proc_macro_api::ProcMacroClient;
use rustc_hash::{FxHashMap, FxHashSet};
Expand All @@ -22,6 +22,8 @@ use crate::{
Sysroot, TargetKind,
};

pub type CfgOverrides = FxHashMap<String, CfgDiff>;

/// `PackageRoot` describes a package root folder.
/// Which may be an external dependency, or a member of
/// the current workspace.
Expand All @@ -46,6 +48,7 @@ pub enum ProjectWorkspace {
/// FIXME: make this a per-crate map, as, eg, build.rs might have a
/// different target.
rustc_cfg: Vec<CfgFlag>,
cfg_overrides: CfgOverrides,
},
/// Project workspace was manually specified using a `rust-project.json` file.
Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
Expand All @@ -67,7 +70,7 @@ impl fmt::Debug for ProjectWorkspace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Make sure this isn't too verbose.
match self {
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => f
.debug_struct("Cargo")
.field("root", &cargo.workspace_root().file_name())
.field("n_packages", &cargo.packages().len())
Expand All @@ -77,6 +80,7 @@ impl fmt::Debug for ProjectWorkspace {
&rustc.as_ref().map_or(0, |rc| rc.packages().len()),
)
.field("n_rustc_cfg", &rustc_cfg.len())
.field("n_cfg_overrides", &cfg_overrides.len())
.finish(),
ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
let mut debug_struct = f.debug_struct("Json");
Expand Down Expand Up @@ -164,7 +168,9 @@ impl ProjectWorkspace {
};

let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg }

let cfg_overrides = config.cfg_overrides();
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides }
}
};

Expand Down Expand Up @@ -213,43 +219,45 @@ impl ProjectWorkspace {
})
}))
.collect::<Vec<_>>(),
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _ } => cargo
.packages()
.map(|pkg| {
let is_member = cargo[pkg].is_member;
let pkg_root = cargo[pkg].root().to_path_buf();

let mut include = vec![pkg_root.clone()];
include.extend(
build_data
.and_then(|it| it.get(cargo.workspace_root()))
.and_then(|map| map.get(&cargo[pkg].id))
.and_then(|it| it.out_dir.clone()),
);
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _, cfg_overrides: _ } => {
cargo
.packages()
.map(|pkg| {
let is_member = cargo[pkg].is_member;
let pkg_root = cargo[pkg].root().to_path_buf();

let mut include = vec![pkg_root.clone()];
include.extend(
build_data
.and_then(|it| it.get(cargo.workspace_root()))
.and_then(|map| map.get(&cargo[pkg].id))
.and_then(|it| it.out_dir.clone()),
);

let mut exclude = vec![pkg_root.join(".git")];
if is_member {
exclude.push(pkg_root.join("target"));
} else {
exclude.push(pkg_root.join("tests"));
exclude.push(pkg_root.join("examples"));
exclude.push(pkg_root.join("benches"));
}
PackageRoot { is_member, include, exclude }
})
.chain(sysroot.crates().map(|krate| PackageRoot {
is_member: false,
include: vec![sysroot[krate].root_dir().to_path_buf()],
exclude: Vec::new(),
}))
.chain(rustc.into_iter().flat_map(|rustc| {
rustc.packages().map(move |krate| PackageRoot {
let mut exclude = vec![pkg_root.join(".git")];
if is_member {
exclude.push(pkg_root.join("target"));
} else {
exclude.push(pkg_root.join("tests"));
exclude.push(pkg_root.join("examples"));
exclude.push(pkg_root.join("benches"));
}
PackageRoot { is_member, include, exclude }
})
.chain(sysroot.crates().map(|krate| PackageRoot {
is_member: false,
include: vec![rustc[krate].root().to_path_buf()],
include: vec![sysroot[krate].root_dir().to_path_buf()],
exclude: Vec::new(),
})
}))
.collect(),
}))
.chain(rustc.into_iter().flat_map(|rustc| {
rustc.packages().map(move |krate| PackageRoot {
is_member: false,
include: vec![rustc[krate].root().to_path_buf()],
exclude: Vec::new(),
})
}))
.collect()
}
ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
.into_iter()
.map(|detached_file| PackageRoot {
Expand Down Expand Up @@ -299,16 +307,22 @@ impl ProjectWorkspace {
project,
sysroot,
),
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => cargo_to_crate_graph(
rustc_cfg.clone(),
&proc_macro_loader,
load,
cargo,
build_data.and_then(|it| it.get(cargo.workspace_root())),
sysroot,
rustc,
rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())),
),
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => {
cargo_to_crate_graph(
rustc_cfg.clone(),
cfg_overrides,
&proc_macro_loader,
load,
cargo,
build_data.and_then(|it| it.get(cargo.workspace_root())),
sysroot,
rustc,
rustc
.as_ref()
.zip(build_data)
.and_then(|(it, map)| map.get(it.workspace_root())),
)
}
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
}
Expand Down Expand Up @@ -398,6 +412,7 @@ fn project_json_to_crate_graph(

fn cargo_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
cargo: &CargoWorkspace,
Expand Down Expand Up @@ -425,6 +440,21 @@ fn cargo_to_crate_graph(
let mut has_private = false;
// Next, create crates for each package, target pair
for pkg in cargo.packages() {
let mut cfg_options = &cfg_options;
let mut replaced_cfg_options;
if let Some(overrides) = override_cfg.get(&cargo[pkg].name) {
// FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
// in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
// working on rust-lang/rust as that's the only time it appears outside sysroot).
//
// A more ideal solution might be to reanalyze crates based on where the cursor is and
// figure out the set of cfgs that would have to apply to make it active.

replaced_cfg_options = cfg_options.clone();
replaced_cfg_options.apply_diff(overrides.clone());
cfg_options = &replaced_cfg_options;
};

has_private |= cargo[pkg].metadata.rustc_private;
let mut lib_tgt = None;
for &tgt in cargo[pkg].targets.iter() {
Expand Down
4 changes: 4 additions & 0 deletions crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ config_data! {
cargo_autoreload: bool = "true",
/// Activate all available features (`--all-features`).
cargo_allFeatures: bool = "false",
/// Unsets `#[cfg(test)]` for the specified crates.
cargo_unsetTest: Vec<String> = "[\"core\"]",
/// List of features to activate.
cargo_features: Vec<String> = "[]",
/// Run build scripts (`build.rs`) for more precise code analysis.
Expand Down Expand Up @@ -595,8 +597,10 @@ impl Config {
target: self.data.cargo_target.clone(),
rustc_source,
no_sysroot: self.data.cargo_noSysroot,
unset_test_crates: self.data.cargo_unsetTest.clone(),
}
}

pub fn rustfmt(&self) -> RustfmtConfig {
match &self.data.rustfmt_overrideCommand {
Some(args) if !args.is_empty() => {
Expand Down
5 changes: 5 additions & 0 deletions docs/user/generated_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ Automatically refresh project info via `cargo metadata` on
--
Activate all available features (`--all-features`).
--
[[rust-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest (default: `["core"]`)::
+
--
Unsets `#[cfg(test)]` for the specified crates.
--
[[rust-analyzer.cargo.features]]rust-analyzer.cargo.features (default: `[]`)::
+
--
Expand Down
10 changes: 10 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,16 @@
"default": false,
"type": "boolean"
},
"rust-analyzer.cargo.unsetTest": {
"markdownDescription": "Unsets `#[cfg(test)]` for the specified crates.",
"default": [
"core"
],
"type": "array",
"items": {
"type": "string"
}
},
"rust-analyzer.cargo.features": {
"markdownDescription": "List of features to activate.",
"default": [],
Expand Down