Add cargo.extraEnv setting

This commit is contained in:
Daniel Paoliello 2022-08-18 14:41:17 -07:00
parent f64c95600c
commit c407cc554e
18 changed files with 155 additions and 43 deletions

1
Cargo.lock generated
View file

@ -394,6 +394,7 @@ dependencies = [
"crossbeam-channel",
"jod-thread",
"paths",
"rustc-hash",
"serde",
"serde_json",
"stdx",

View file

@ -13,6 +13,7 @@ doctest = false
crossbeam-channel = "0.5.5"
tracing = "0.1.35"
cargo_metadata = "0.15.0"
rustc-hash = "1.1.0"
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"
jod-thread = "0.1.2"

View file

@ -12,6 +12,7 @@ use std::{
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stdx::{process::streaming_output, JodChild};
@ -30,10 +31,12 @@ pub enum FlycheckConfig {
all_features: bool,
features: Vec<String>,
extra_args: Vec<String>,
extra_env: FxHashMap<String, String>,
},
CustomCommand {
command: String,
args: Vec<String>,
extra_env: FxHashMap<String, String>,
},
}
@ -41,7 +44,7 @@ impl fmt::Display for FlycheckConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
FlycheckConfig::CustomCommand { command, args } => {
FlycheckConfig::CustomCommand { command, args, .. } => {
write!(f, "{} {}", command, args.join(" "))
}
}
@ -256,6 +259,7 @@ impl FlycheckActor {
all_features,
extra_args,
features,
extra_env,
} => {
let mut cmd = Command::new(toolchain::cargo());
cmd.arg(command);
@ -281,11 +285,13 @@ impl FlycheckActor {
}
}
cmd.args(extra_args);
cmd.envs(extra_env);
cmd
}
FlycheckConfig::CustomCommand { command, args } => {
FlycheckConfig::CustomCommand { command, args, extra_env } => {
let mut cmd = Command::new(command);
cmd.args(args);
cmd.envs(extra_env);
cmd
}
};

View file

@ -43,10 +43,12 @@ impl WorkspaceBuildScripts {
if let Some([program, args @ ..]) = config.run_build_script_command.as_deref() {
let mut cmd = Command::new(program);
cmd.args(args);
cmd.envs(&config.extra_env);
return cmd;
}
let mut cmd = Command::new(toolchain::cargo());
cmd.envs(&config.extra_env);
cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);

View file

@ -2,6 +2,7 @@
use std::iter;
use std::path::PathBuf;
use std::str::from_utf8;
use std::{ops, process::Command};
use anyhow::{Context, Result};
@ -98,6 +99,8 @@ pub struct CargoConfig {
pub wrap_rustc_in_build_scripts: bool,
pub run_build_script_command: Option<Vec<String>>,
pub extra_env: FxHashMap<String, String>,
}
impl CargoConfig {
@ -263,8 +266,8 @@ impl CargoWorkspace {
let target = config
.target
.clone()
.or_else(|| cargo_config_build_target(cargo_toml))
.or_else(|| rustc_discover_host_triple(cargo_toml));
.or_else(|| cargo_config_build_target(cargo_toml, config))
.or_else(|| rustc_discover_host_triple(cargo_toml, config));
let mut meta = MetadataCommand::new();
meta.cargo_path(toolchain::cargo());
@ -292,8 +295,27 @@ impl CargoWorkspace {
// unclear whether cargo itself supports it.
progress("metadata".to_string());
let meta =
meta.exec().with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))?;
fn exec_with_env(
command: &cargo_metadata::MetadataCommand,
extra_env: &FxHashMap<String, String>,
) -> Result<cargo_metadata::Metadata, cargo_metadata::Error> {
let mut command = command.cargo_command();
command.envs(extra_env);
let output = command.output()?;
if !output.status.success() {
return Err(cargo_metadata::Error::CargoMetadata {
stderr: String::from_utf8(output.stderr)?,
});
}
let stdout = from_utf8(&output.stdout)?
.lines()
.find(|line| line.starts_with('{'))
.ok_or(cargo_metadata::Error::NoJson)?;
cargo_metadata::MetadataCommand::parse(stdout)
}
let meta = exec_with_env(&meta, &config.extra_env)
.with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))?;
Ok(meta)
}
@ -463,8 +485,9 @@ impl CargoWorkspace {
}
}
fn rustc_discover_host_triple(cargo_toml: &ManifestPath) -> Option<String> {
fn rustc_discover_host_triple(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option<String> {
let mut rustc = Command::new(toolchain::rustc());
rustc.envs(&config.extra_env);
rustc.current_dir(cargo_toml.parent()).arg("-vV");
tracing::debug!("Discovering host platform by {:?}", rustc);
match utf8_stdout(rustc) {
@ -486,8 +509,9 @@ fn rustc_discover_host_triple(cargo_toml: &ManifestPath) -> Option<String> {
}
}
fn cargo_config_build_target(cargo_toml: &ManifestPath) -> Option<String> {
fn cargo_config_build_target(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option<String> {
let mut cargo_config = Command::new(toolchain::cargo());
cargo_config.envs(&config.extra_env);
cargo_config
.current_dir(cargo_toml.parent())
.args(&["-Z", "unstable-options", "config", "get", "build.target"])

View file

@ -4,9 +4,13 @@ use std::process::Command;
use anyhow::Result;
use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath};
use crate::{cfg_flag::CfgFlag, utf8_stdout, CargoConfig, ManifestPath};
pub(crate) fn get(cargo_toml: Option<&ManifestPath>, target: Option<&str>) -> Vec<CfgFlag> {
pub(crate) fn get(
cargo_toml: Option<&ManifestPath>,
target: Option<&str>,
config: &CargoConfig,
) -> Vec<CfgFlag> {
let _p = profile::span("rustc_cfg::get");
let mut res = Vec::with_capacity(6 * 2 + 1);
@ -18,7 +22,7 @@ pub(crate) fn get(cargo_toml: Option<&ManifestPath>, target: Option<&str>) -> Ve
}
}
match get_rust_cfgs(cargo_toml, target) {
match get_rust_cfgs(cargo_toml, target, config) {
Ok(rustc_cfgs) => {
tracing::debug!(
"rustc cfgs found: {:?}",
@ -35,9 +39,14 @@ pub(crate) fn get(cargo_toml: Option<&ManifestPath>, target: Option<&str>) -> Ve
res
}
fn get_rust_cfgs(cargo_toml: Option<&ManifestPath>, target: Option<&str>) -> Result<String> {
fn get_rust_cfgs(
cargo_toml: Option<&ManifestPath>,
target: Option<&str>,
config: &CargoConfig,
) -> Result<String> {
if let Some(cargo_toml) = cargo_toml {
let mut cargo_config = Command::new(toolchain::cargo());
cargo_config.envs(&config.extra_env);
cargo_config
.current_dir(cargo_toml.parent())
.args(&["-Z", "unstable-options", "rustc", "--print", "cfg"])
@ -52,6 +61,7 @@ fn get_rust_cfgs(cargo_toml: Option<&ManifestPath>, target: Option<&str>) -> Res
}
// using unstable cargo features failed, fall back to using plain rustc
let mut cmd = Command::new(toolchain::rustc());
cmd.envs(&config.extra_env);
cmd.args(&["--print", "cfg", "-O"]);
if let Some(target) = target {
cmd.args(&["--target", target]);

View file

@ -10,7 +10,7 @@ use anyhow::{format_err, Result};
use la_arena::{Arena, Idx};
use paths::{AbsPath, AbsPathBuf};
use crate::{utf8_stdout, ManifestPath};
use crate::{utf8_stdout, CargoConfig, ManifestPath};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Sysroot {
@ -67,18 +67,20 @@ impl Sysroot {
self.crates.iter().map(|(id, _data)| id)
}
pub fn discover(dir: &AbsPath) -> Result<Sysroot> {
pub fn discover(dir: &AbsPath, config: &CargoConfig) -> Result<Sysroot> {
tracing::debug!("Discovering sysroot for {}", dir.display());
let sysroot_dir = discover_sysroot_dir(dir)?;
let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, dir)?;
let sysroot_dir = discover_sysroot_dir(dir, config)?;
let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, dir, config)?;
let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?;
Ok(res)
}
pub fn discover_rustc(cargo_toml: &ManifestPath) -> Option<ManifestPath> {
pub fn discover_rustc(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option<ManifestPath> {
tracing::debug!("Discovering rustc source for {}", cargo_toml.display());
let current_dir = cargo_toml.parent();
discover_sysroot_dir(current_dir).ok().and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
discover_sysroot_dir(current_dir, config)
.ok()
.and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
}
pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf) -> Result<Sysroot> {
@ -144,8 +146,9 @@ impl Sysroot {
}
}
fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
fn discover_sysroot_dir(current_dir: &AbsPath, config: &CargoConfig) -> Result<AbsPathBuf> {
let mut rustc = Command::new(toolchain::rustc());
rustc.envs(&config.extra_env);
rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
tracing::debug!("Discovering sysroot by {:?}", rustc);
let stdout = utf8_stdout(rustc)?;
@ -155,6 +158,7 @@ fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
fn discover_sysroot_src_dir(
sysroot_path: &AbsPathBuf,
current_dir: &AbsPath,
config: &CargoConfig,
) -> Result<AbsPathBuf> {
if let Ok(path) = env::var("RUST_SRC_PATH") {
let path = AbsPathBuf::try_from(path.as_str())
@ -170,6 +174,7 @@ fn discover_sysroot_src_dir(
get_rust_src(sysroot_path)
.or_else(|| {
let mut rustup = Command::new(toolchain::rustup());
rustup.envs(&config.extra_env);
rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
utf8_stdout(rustup).ok()?;
get_rust_src(sysroot_path)

View file

@ -10,8 +10,8 @@ use paths::{AbsPath, AbsPathBuf};
use serde::de::DeserializeOwned;
use crate::{
CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot,
WorkspaceBuildScripts,
CargoConfig, CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace,
Sysroot, WorkspaceBuildScripts,
};
fn load_cargo(file: &str) -> CrateGraph {
@ -92,13 +92,17 @@ fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
}
fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
project_workspace.to_crate_graph(&mut |_, _| Ok(Vec::new()), &mut {
let mut counter = 0;
move |_path| {
counter += 1;
Some(FileId(counter))
}
})
project_workspace.to_crate_graph(
&mut |_, _| Ok(Vec::new()),
&mut {
let mut counter = 0;
move |_path| {
counter += 1;
Some(FileId(counter))
}
},
&CargoConfig::default(),
)
}
fn check_crate_graph(crate_graph: CrateGraph, expect: Expect) {

View file

@ -156,11 +156,12 @@ impl ProjectWorkspace {
})?;
let project_location = project_json.parent().to_path_buf();
let project_json = ProjectJson::new(&project_location, data);
ProjectWorkspace::load_inline(project_json, config.target.as_deref())?
ProjectWorkspace::load_inline(project_json, config.target.as_deref(), config)?
}
ProjectManifest::CargoToml(cargo_toml) => {
let cargo_version = utf8_stdout({
let mut cmd = Command::new(toolchain::cargo());
cmd.envs(&config.extra_env);
cmd.arg("--version");
cmd
})?;
@ -186,7 +187,7 @@ impl ProjectWorkspace {
let sysroot = if config.no_sysroot {
None
} else {
Some(Sysroot::discover(cargo_toml.parent()).with_context(|| {
Some(Sysroot::discover(cargo_toml.parent(), config).with_context(|| {
format!(
"Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
cargo_toml.display()
@ -196,7 +197,7 @@ impl ProjectWorkspace {
let rustc_dir = match &config.rustc_source {
Some(RustcSource::Path(path)) => ManifestPath::try_from(path.clone()).ok(),
Some(RustcSource::Discover) => Sysroot::discover_rustc(&cargo_toml),
Some(RustcSource::Discover) => Sysroot::discover_rustc(&cargo_toml, config),
None => None,
};
@ -216,7 +217,7 @@ impl ProjectWorkspace {
None => None,
};
let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), config);
let cfg_overrides = config.cfg_overrides();
ProjectWorkspace::Cargo {
@ -237,6 +238,7 @@ impl ProjectWorkspace {
pub fn load_inline(
project_json: ProjectJson,
target: Option<&str>,
config: &CargoConfig,
) -> Result<ProjectWorkspace> {
let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
(Some(sysroot), Some(sysroot_src)) => Some(Sysroot::load(sysroot, sysroot_src)?),
@ -258,7 +260,7 @@ impl ProjectWorkspace {
(None, None) => None,
};
let rustc_cfg = rustc_cfg::get(None, target);
let rustc_cfg = rustc_cfg::get(None, target, config);
Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
}
@ -268,8 +270,9 @@ impl ProjectWorkspace {
.first()
.and_then(|it| it.parent())
.ok_or_else(|| format_err!("No detached files to load"))?,
&CargoConfig::default(),
)?;
let rustc_cfg = rustc_cfg::get(None, None);
let rustc_cfg = rustc_cfg::get(None, None, &CargoConfig::default());
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
}
@ -416,6 +419,7 @@ impl ProjectWorkspace {
&self,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
config: &CargoConfig,
) -> CrateGraph {
let _p = profile::span("ProjectWorkspace::to_crate_graph");
@ -426,6 +430,7 @@ impl ProjectWorkspace {
load,
project,
sysroot,
config,
),
ProjectWorkspace::Cargo {
cargo,
@ -464,6 +469,7 @@ fn project_json_to_crate_graph(
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
project: &ProjectJson,
sysroot: &Option<Sysroot>,
config: &CargoConfig,
) -> CrateGraph {
let mut crate_graph = CrateGraph::default();
let sysroot_deps = sysroot
@ -489,9 +495,9 @@ fn project_json_to_crate_graph(
};
let target_cfgs = match krate.target.as_deref() {
Some(target) => {
cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(None, Some(target)))
}
Some(target) => cfg_cache
.entry(target)
.or_insert_with(|| rustc_cfg::get(None, Some(target), config)),
None => &rustc_cfg,
};

View file

@ -80,7 +80,8 @@ impl flags::AnalysisStats {
Some(build_scripts_sw.elapsed())
};
let (host, vfs, _proc_macro) = load_workspace(workspace, &load_cargo_config)?;
let (host, vfs, _proc_macro) =
load_workspace(workspace, &cargo_config, &load_cargo_config)?;
let db = host.raw_database();
eprint!("{:<20} {}", "Database loaded:", db_load_sw.elapsed());
eprint!(" (metadata {}", metadata_time);

View file

@ -38,7 +38,7 @@ pub fn load_workspace_at(
workspace.set_build_scripts(build_scripts)
}
load_workspace(workspace, load_config)
load_workspace(workspace, cargo_config, load_config)
}
// Note: Since this function is used by external tools that use rust-analyzer as a library
@ -48,6 +48,7 @@ pub fn load_workspace_at(
// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
pub fn load_workspace(
ws: ProjectWorkspace,
cargo_config: &CargoConfig,
load_config: &LoadCargoConfig,
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
let (sender, receiver) = unbounded();
@ -75,6 +76,7 @@ pub fn load_workspace(
vfs.set_file_contents(path.clone(), contents);
vfs.file_id(&path)
},
cargo_config,
);
let project_folders = ProjectFolders::new(&[ws], &[]);

View file

@ -299,7 +299,8 @@ impl flags::Lsif {
let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
let (host, vfs, _proc_macro) = load_workspace(workspace, &load_cargo_config)?;
let (host, vfs, _proc_macro) =
load_workspace(workspace, &cargo_config, &load_cargo_config)?;
let db = host.raw_database();
let analysis = host.analysis();

View file

@ -40,7 +40,7 @@ impl flags::Scip {
let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
let (host, vfs, _) = load_workspace(workspace, &load_cargo_config)?;
let (host, vfs, _) = load_workspace(workspace, &cargo_config, &load_cargo_config)?;
let db = host.raw_database();
let analysis = host.analysis();

View file

@ -84,6 +84,9 @@ config_data! {
/// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
/// avoid checking unnecessary things.
cargo_buildScripts_useRustcWrapper: bool = "true",
/// Extra environment variables that will be set when running cargo, rustc
/// or other commands within the workspace. Useful for setting RUSTFLAGS.
cargo_extraEnv: FxHashMap<String, String> = "{}",
/// List of features to activate.
///
/// Set this to `"all"` to pass `--all-features` to cargo.
@ -105,6 +108,8 @@ config_data! {
checkOnSave_enable: bool = "true",
/// Extra arguments for `cargo check`.
checkOnSave_extraArgs: Vec<String> = "[]",
/// Extra environment variables that will be set when running `cargo check`.
checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
/// List of features to activate. Defaults to
/// `#rust-analyzer.cargo.features#`.
///
@ -934,6 +939,16 @@ impl Config {
}
}
pub fn extra_env(&self) -> &FxHashMap<String, String> {
&self.data.cargo_extraEnv
}
pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> {
let mut extra_env = self.data.cargo_extraEnv.clone();
extra_env.extend(self.data.checkOnSave_extraEnv.clone());
extra_env
}
pub fn lru_capacity(&self) -> Option<usize> {
self.data.lru_capacity
}
@ -1003,6 +1018,7 @@ impl Config {
unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
extra_env: self.data.cargo_extraEnv.clone(),
}
}
@ -1028,7 +1044,11 @@ impl Config {
Some(args) if !args.is_empty() => {
let mut args = args.clone();
let command = args.remove(0);
FlycheckConfig::CustomCommand { command, args }
FlycheckConfig::CustomCommand {
command,
args,
extra_env: self.check_on_save_extra_env(),
}
}
Some(_) | None => FlycheckConfig::CargoCommand {
command: self.data.checkOnSave_command.clone(),
@ -1056,6 +1076,7 @@ impl Config {
CargoFeatures::Listed(it) => it,
},
extra_args: self.data.checkOnSave_extraArgs.clone(),
extra_env: self.check_on_save_extra_env(),
},
};
Some(flycheck_config)

View file

@ -1786,6 +1786,7 @@ fn run_rustfmt(
let mut command = match snap.config.rustfmt() {
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
let mut cmd = process::Command::new(toolchain::rustfmt());
cmd.envs(snap.config.extra_env());
cmd.args(extra_args);
// try to chdir to the file so we can respect `rustfmt.toml`
// FIXME: use `rustfmt --config-path` once
@ -1843,6 +1844,7 @@ fn run_rustfmt(
}
RustfmtConfig::CustomCommand { command, args } => {
let mut cmd = process::Command::new(command);
cmd.envs(snap.config.extra_env());
cmd.args(args);
cmd
}

View file

@ -143,6 +143,7 @@ impl GlobalState {
project_model::ProjectWorkspace::load_inline(
it.clone(),
cargo_config.target.as_deref(),
&cargo_config,
)
}
})
@ -398,7 +399,11 @@ impl GlobalState {
dummy_replacements.get(crate_name).map(|v| &**v).unwrap_or_default(),
)
};
crate_graph.extend(ws.to_crate_graph(&mut load_proc_macro, &mut load));
crate_graph.extend(ws.to_crate_graph(
&mut load_proc_macro,
&mut load,
&self.config.cargo(),
));
}
crate_graph
};

View file

@ -46,6 +46,12 @@ cargo check --quiet --workspace --message-format=json --all-targets
Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
avoid checking unnecessary things.
--
[[rust-analyzer.cargo.extraEnv]]rust-analyzer.cargo.extraEnv (default: `{}`)::
+
--
Extra environment variables that will be set when running cargo, rustc
or other commands within the workspace. Useful for setting RUSTFLAGS.
--
[[rust-analyzer.cargo.features]]rust-analyzer.cargo.features (default: `[]`)::
+
--
@ -93,6 +99,11 @@ Run specified `cargo check` command for diagnostics on save.
--
Extra arguments for `cargo check`.
--
[[rust-analyzer.checkOnSave.extraEnv]]rust-analyzer.checkOnSave.extraEnv (default: `{}`)::
+
--
Extra environment variables that will be set when running `cargo check`.
--
[[rust-analyzer.checkOnSave.features]]rust-analyzer.checkOnSave.features (default: `null`)::
+
--

View file

@ -437,6 +437,11 @@
"default": true,
"type": "boolean"
},
"rust-analyzer.cargo.extraEnv": {
"markdownDescription": "Extra environment variables that will be set when running cargo, rustc\nor other commands within the workspace. Useful for setting RUSTFLAGS.",
"default": {},
"type": "object"
},
"rust-analyzer.cargo.features": {
"markdownDescription": "List of features to activate.\n\nSet this to `\"all\"` to pass `--all-features` to cargo.",
"default": [],
@ -509,6 +514,11 @@
"type": "string"
}
},
"rust-analyzer.checkOnSave.extraEnv": {
"markdownDescription": "Extra environment variables that will be set when running `cargo check`.",
"default": {},
"type": "object"
},
"rust-analyzer.checkOnSave.features": {
"markdownDescription": "List of features to activate. Defaults to\n`#rust-analyzer.cargo.features#`.\n\nSet to `\"all\"` to pass `--all-features` to Cargo.",
"default": null,