Skip to content

Commit 8ef2029

Browse files
authored
Merge pull request #18756 from github/redsun82/rust-config
Rust: expose more rust-analyzer config knobs
2 parents 81b6848 + d0461e2 commit 8ef2029

File tree

6 files changed

+213
-101
lines changed

6 files changed

+213
-101
lines changed

rust/extractor/macros/src/lib.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStrea
1919
.fields
2020
.iter()
2121
.map(|f| {
22+
let ty_tip = get_type_tip(&f.ty);
2223
if f.ident.as_ref().is_some_and(|i| i != "inputs")
23-
&& get_type_tip(&f.ty).is_some_and(|i| i == "Vec")
24+
&& ty_tip.is_some_and(|i| i == "Vec")
2425
{
2526
quote! {
26-
#[serde(deserialize_with="deserialize_newline_or_comma_separated")]
27+
#[serde(deserialize_with="deserialize::deserialize_newline_or_comma_separated_vec")]
28+
#f
29+
}
30+
} else if ty_tip.is_some_and(|i| i == "FxHashMap" || i == "HashMap") {
31+
quote! {
32+
#[serde(deserialize_with="deserialize::deserialize_newline_or_comma_separated_map")]
2733
#f
2834
}
2935
} else {
@@ -60,7 +66,7 @@ pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStrea
6066
quote! {
6167
#f
6268
}
63-
} else if type_tip.is_some_and(|i| i == "Vec") {
69+
} else if type_tip.is_some_and(|i| i == "Vec" || i == "FxHashMap" || i == "HashMap") {
6470
quote! {
6571
#[arg(long)]
6672
#id: Option<String>

rust/extractor/src/config.rs

+96-38
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
mod deserialize_vec;
1+
mod deserialize;
22

33
use anyhow::Context;
44
use clap::Parser;
55
use codeql_extractor::trap;
6-
use deserialize_vec::deserialize_newline_or_comma_separated;
76
use figment::{
87
providers::{Env, Format, Serialized, Yaml},
98
value::Value,
@@ -13,14 +12,15 @@ use itertools::Itertools;
1312
use ra_ap_cfg::{CfgAtom, CfgDiff};
1413
use ra_ap_ide_db::FxHashMap;
1514
use ra_ap_intern::Symbol;
16-
use ra_ap_paths::{AbsPath, Utf8PathBuf};
15+
use ra_ap_load_cargo::{LoadCargoConfig, ProcMacroServerChoice};
16+
use ra_ap_paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
1717
use ra_ap_project_model::{CargoConfig, CargoFeatures, CfgOverrides, RustLibSource, Sysroot};
1818
use rust_extractor_macros::extractor_cli_config;
1919
use serde::{Deserialize, Serialize};
2020
use std::collections::HashSet;
2121
use std::fmt::Debug;
2222
use std::ops::Not;
23-
use std::path::PathBuf;
23+
use std::path::{Path, PathBuf};
2424

2525
#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize, Clone, Copy, clap::ValueEnum)]
2626
#[serde(rename_all = "lowercase")]
@@ -50,13 +50,22 @@ pub struct Config {
5050
pub cargo_target: Option<String>,
5151
pub cargo_features: Vec<String>,
5252
pub cargo_cfg_overrides: Vec<String>,
53+
pub cargo_extra_env: FxHashMap<String, String>,
54+
pub cargo_extra_args: Vec<String>,
55+
pub cargo_all_targets: bool,
5356
pub logging_flamegraph: Option<PathBuf>,
5457
pub logging_verbosity: Option<String>,
5558
pub compression: Compression,
5659
pub inputs: Vec<PathBuf>,
5760
pub qltest: bool,
5861
pub qltest_cargo_check: bool,
5962
pub qltest_dependencies: Vec<String>,
63+
pub sysroot: Option<PathBuf>,
64+
pub sysroot_src: Option<PathBuf>,
65+
pub rustc_src: Option<PathBuf>,
66+
pub build_script_command: Vec<String>,
67+
pub extra_includes: Vec<PathBuf>,
68+
pub proc_macro_server: Option<PathBuf>,
6069
}
6170

6271
impl Config {
@@ -92,44 +101,86 @@ impl Config {
92101
figment.extract().context("loading configuration")
93102
}
94103

95-
pub fn to_cargo_config(&self, dir: &AbsPath) -> CargoConfig {
96-
let sysroot = Sysroot::discover(dir, &FxHashMap::default());
97-
let sysroot_src = sysroot.src_root().map(ToOwned::to_owned);
98-
let sysroot = sysroot
99-
.root()
100-
.map(ToOwned::to_owned)
101-
.map(RustLibSource::Path);
102-
103-
let target_dir = self
104-
.cargo_target_dir
105-
.clone()
106-
.unwrap_or_else(|| self.scratch_dir.join("target"));
107-
let target_dir = Utf8PathBuf::from_path_buf(target_dir).ok();
108-
109-
let features = if self.cargo_features.is_empty() {
110-
Default::default()
111-
} else if self.cargo_features.contains(&"*".to_string()) {
112-
CargoFeatures::All
113-
} else {
114-
CargoFeatures::Selected {
115-
features: self.cargo_features.clone(),
116-
no_default_features: false,
104+
fn sysroot(&self, dir: &AbsPath) -> Sysroot {
105+
let sysroot_input = self.sysroot.as_ref().map(|p| join_path_buf(dir, p));
106+
let sysroot_src_input = self.sysroot_src.as_ref().map(|p| join_path_buf(dir, p));
107+
match (sysroot_input, sysroot_src_input) {
108+
(None, None) => Sysroot::discover(dir, &self.cargo_extra_env),
109+
(Some(sysroot), None) => Sysroot::discover_sysroot_src_dir(sysroot),
110+
(None, Some(sysroot_src)) => {
111+
Sysroot::discover_with_src_override(dir, &self.cargo_extra_env, sysroot_src)
117112
}
118-
};
113+
(Some(sysroot), Some(sysroot_src)) => Sysroot::new(Some(sysroot), Some(sysroot_src)),
114+
}
115+
}
119116

120-
let target = self.cargo_target.clone();
117+
fn proc_macro_server_choice(&self, dir: &AbsPath) -> ProcMacroServerChoice {
118+
match &self.proc_macro_server {
119+
Some(path) => match path.to_str() {
120+
Some("none") => ProcMacroServerChoice::None,
121+
Some("sysroot") => ProcMacroServerChoice::Sysroot,
122+
_ => ProcMacroServerChoice::Explicit(join_path_buf(dir, path)),
123+
},
124+
None => ProcMacroServerChoice::Sysroot,
125+
}
126+
}
121127

122-
let cfg_overrides = to_cfg_overrides(&self.cargo_cfg_overrides);
128+
pub fn to_cargo_config(&self, dir: &AbsPath) -> (CargoConfig, LoadCargoConfig) {
129+
let sysroot = self.sysroot(dir);
130+
(
131+
CargoConfig {
132+
all_targets: self.cargo_all_targets,
133+
sysroot_src: sysroot.src_root().map(ToOwned::to_owned),
134+
rustc_source: self
135+
.rustc_src
136+
.as_ref()
137+
.map(|p| join_path_buf(dir, p))
138+
.or_else(|| sysroot.discover_rustc_src().map(AbsPathBuf::from))
139+
.map(RustLibSource::Path),
140+
sysroot: sysroot
141+
.root()
142+
.map(ToOwned::to_owned)
143+
.map(RustLibSource::Path),
123144

124-
CargoConfig {
125-
sysroot,
126-
sysroot_src,
127-
target_dir,
128-
features,
129-
target,
130-
cfg_overrides,
131-
..Default::default()
132-
}
145+
extra_env: self.cargo_extra_env.clone(),
146+
extra_args: self.cargo_extra_args.clone(),
147+
extra_includes: self
148+
.extra_includes
149+
.iter()
150+
.map(|p| join_path_buf(dir, p))
151+
.collect(),
152+
target_dir: Utf8PathBuf::from_path_buf(
153+
self.cargo_target_dir
154+
.clone()
155+
.unwrap_or_else(|| self.scratch_dir.join("target")),
156+
)
157+
.ok(),
158+
features: if self.cargo_features.is_empty() {
159+
Default::default()
160+
} else if self.cargo_features.contains(&"*".to_string()) {
161+
CargoFeatures::All
162+
} else {
163+
CargoFeatures::Selected {
164+
features: self.cargo_features.clone(),
165+
no_default_features: false,
166+
}
167+
},
168+
target: self.cargo_target.clone(),
169+
cfg_overrides: to_cfg_overrides(&self.cargo_cfg_overrides),
170+
wrap_rustc_in_build_scripts: false,
171+
run_build_script_command: if self.build_script_command.is_empty() {
172+
None
173+
} else {
174+
Some(self.build_script_command.clone())
175+
},
176+
..Default::default()
177+
},
178+
LoadCargoConfig {
179+
load_out_dirs_from_check: true,
180+
with_proc_macro_server: self.proc_macro_server_choice(dir),
181+
prefill_caches: false,
182+
},
183+
)
133184
}
134185
}
135186

@@ -168,3 +219,10 @@ fn to_cfg_overrides(specs: &Vec<String>) -> CfgOverrides {
168219
..Default::default()
169220
}
170221
}
222+
223+
fn join_path_buf(lhs: &AbsPath, rhs: &Path) -> AbsPathBuf {
224+
let Ok(path) = Utf8PathBuf::from_path_buf(rhs.into()) else {
225+
panic!("non utf8 input: {}", rhs.display())
226+
};
227+
lhs.join(path)
228+
}
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use serde::de::{Error, Unexpected, Visitor};
2+
use serde::Deserializer;
3+
use std::collections::HashMap;
4+
use std::fmt::Formatter;
5+
use std::hash::BuildHasher;
6+
use std::marker::PhantomData;
7+
8+
// phantom data is required to allow parametrizing on `T` without actual `T` data
9+
struct VectorVisitor<T: From<String>>(PhantomData<T>);
10+
struct MapVisitor<S: BuildHasher + Default>(PhantomData<S>);
11+
12+
impl<'de, T: From<String>> Visitor<'de> for VectorVisitor<T> {
13+
type Value = Vec<T>;
14+
15+
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
16+
formatter.write_str("either a sequence, or a comma or newline separated string")
17+
}
18+
19+
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Vec<T>, E> {
20+
Ok(value
21+
.split(['\n', ','])
22+
.map(|s| T::from(s.to_owned()))
23+
.collect())
24+
}
25+
26+
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<T>, A::Error>
27+
where
28+
A: serde::de::SeqAccess<'de>,
29+
{
30+
let mut ret = Vec::new();
31+
while let Some(el) = seq.next_element::<String>()? {
32+
ret.push(T::from(el));
33+
}
34+
Ok(ret)
35+
}
36+
}
37+
38+
impl<'de, S: BuildHasher + Default> Visitor<'de> for MapVisitor<S> {
39+
type Value = HashMap<String, String, S>;
40+
41+
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
42+
formatter.write_str(
43+
"either a sequence, or a comma or newline separated string of key=value entries",
44+
)
45+
}
46+
47+
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
48+
value
49+
.split(['\n', ','])
50+
.map(|s| {
51+
s.split_once('=')
52+
.ok_or_else(|| E::custom(format!("key=value expected, found {s}")))
53+
.map(|(key, value)| (key.to_owned(), value.to_owned()))
54+
})
55+
.collect()
56+
}
57+
58+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
59+
where
60+
A: serde::de::SeqAccess<'de>,
61+
{
62+
let mut ret = HashMap::with_hasher(Default::default());
63+
while let Some(el) = seq.next_element::<String>()? {
64+
let (key, value) = el
65+
.split_once('=')
66+
.ok_or_else(|| A::Error::invalid_value(Unexpected::Str(&el), &self))?;
67+
ret.insert(key.to_owned(), value.to_owned());
68+
}
69+
Ok(ret)
70+
}
71+
}
72+
73+
/// deserialize into a vector of `T` either of:
74+
/// * a sequence of elements serializable into `String`s, or
75+
/// * a single element serializable into `String`, then split on `,` and `\n`
76+
pub(crate) fn deserialize_newline_or_comma_separated_vec<
77+
'a,
78+
D: Deserializer<'a>,
79+
T: From<String>,
80+
>(
81+
deserializer: D,
82+
) -> Result<Vec<T>, D::Error> {
83+
deserializer.deserialize_seq(VectorVisitor(PhantomData))
84+
}
85+
86+
/// deserialize into a map of `String`s to `String`s either of:
87+
/// * a sequence of elements serializable into `String`s, or
88+
/// * a single element serializable into `String`, then split on `,` and `\n`
89+
pub(crate) fn deserialize_newline_or_comma_separated_map<
90+
'a,
91+
D: Deserializer<'a>,
92+
S: BuildHasher + Default,
93+
>(
94+
deserializer: D,
95+
) -> Result<HashMap<String, String, S>, D::Error> {
96+
deserializer.deserialize_map(MapVisitor(PhantomData))
97+
}

rust/extractor/src/config/deserialize_vec.rs

-50
This file was deleted.

rust/extractor/src/main.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use archive::Archiver;
66
use ra_ap_hir::Semantics;
77
use ra_ap_ide_db::line_index::{LineCol, LineIndex};
88
use ra_ap_ide_db::RootDatabase;
9+
use ra_ap_load_cargo::LoadCargoConfig;
910
use ra_ap_paths::{AbsPathBuf, Utf8PathBuf};
1011
use ra_ap_project_model::{CargoConfig, ProjectManifest};
1112
use ra_ap_vfs::Vfs;
@@ -114,9 +115,10 @@ impl<'a> Extractor<'a> {
114115
&mut self,
115116
project: &ProjectManifest,
116117
config: &CargoConfig,
118+
load_config: &LoadCargoConfig,
117119
) -> Option<(RootDatabase, Vfs)> {
118120
let before = Instant::now();
119-
let ret = RustAnalyzer::load_workspace(project, config);
121+
let ret = RustAnalyzer::load_workspace(project, config, load_config);
120122
self.steps
121123
.push(ExtractionStep::load_manifest(before, project));
122124
ret
@@ -235,9 +237,12 @@ fn main() -> anyhow::Result<()> {
235237
}
236238
extractor.extract_without_semantics(file, "no manifest found");
237239
}
238-
let cargo_config = cfg.to_cargo_config(&cwd()?);
240+
let cwd = cwd()?;
241+
let (cargo_config, load_cargo_config) = cfg.to_cargo_config(&cwd);
239242
for (manifest, files) in map.values().filter(|(_, files)| !files.is_empty()) {
240-
if let Some((ref db, ref vfs)) = extractor.load_manifest(manifest, &cargo_config) {
243+
if let Some((ref db, ref vfs)) =
244+
extractor.load_manifest(manifest, &cargo_config, &load_cargo_config)
245+
{
241246
let semantics = Semantics::new(db);
242247
for file in files {
243248
match extractor.load_source(file, &semantics, vfs) {

0 commit comments

Comments
 (0)