Skip to content

Commit fa52d03

Browse files
authored
Merge pull request #17937 from github/redsun82/rust-cargo-options
Rust: allow to specify more cargo configuration options
2 parents b84b687 + 5ef92a2 commit fa52d03

28 files changed

+313
-33
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/codeql-extractor.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,22 @@ options:
3535
reduce execution time of consecutive extractor runs. By default, a new scratch
3636
directory is used for each run.
3737
type: string
38+
cargo_target:
39+
title: Target architecture
40+
description: >
41+
Target architecture to use for analysis, analogous to `cargo --target`. By
42+
default the host architecture is used.
43+
type: string
44+
cargo_features:
45+
title: Cargo features to turn on
46+
description: >
47+
Comma-separated list of features to turn on. If any value is `*` all features
48+
are turned on. By default only default cargo features are enabled. Can be
49+
repeated.
50+
type: array
51+
cargo_cfg_overrides:
52+
title: Cargo cfg overrides
53+
description: >
54+
Comma-separated list of cfg settings to enable, or disable if prefixed with `-`.
55+
Can be repeated.
56+
type: array

rust/extractor/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ ra_ap_syntax = "0.0.232"
2222
ra_ap_vfs = "0.0.232"
2323
ra_ap_parser = "0.0.232"
2424
ra_ap_span = "0.0.232"
25+
ra_ap_cfg = "0.0.232"
26+
ra_ap_intern = "0.0.232"
2527
serde = "1.0.209"
2628
serde_with = "3.9.0"
2729
stderrlog = "0.6.0"

rust/extractor/macros/src/lib.rs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,74 @@
11
use proc_macro::TokenStream;
22
use quote::{format_ident, quote};
3+
use syn::{Ident, Type};
4+
5+
fn get_type_tip(t: &Type) -> Option<&Ident> {
6+
let syn::Type::Path(path) = t else {
7+
return None;
8+
};
9+
let segment = path.path.segments.last()?;
10+
Some(&segment.ident)
11+
}
312

413
/// Allow all fields in the extractor config to be also overrideable by extractor CLI flags
514
#[proc_macro_attribute]
615
pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStream {
716
let ast = syn::parse_macro_input!(item as syn::ItemStruct);
817
let name = &ast.ident;
18+
let fields = ast
19+
.fields
20+
.iter()
21+
.map(|f| {
22+
if f.ident.as_ref().is_some_and(|i| i != "inputs")
23+
&& get_type_tip(&f.ty).is_some_and(|i| i == "Vec")
24+
{
25+
quote! {
26+
#[serde(deserialize_with="deserialize_newline_or_comma_separated")]
27+
#f
28+
}
29+
} else {
30+
quote! { #f }
31+
}
32+
})
33+
.collect::<Vec<_>>();
934
let cli_name = format_ident!("Cli{}", name);
1035
let cli_fields = ast
1136
.fields
1237
.iter()
1338
.map(|f| {
1439
let id = f.ident.as_ref().unwrap();
1540
let ty = &f.ty;
16-
if let syn::Type::Path(p) = ty {
17-
if p.path.is_ident(&format_ident!("bool")) {
18-
return quote! {
19-
#[arg(long)]
20-
#[serde(skip_serializing_if="<&bool>::not")]
21-
#id: bool,
22-
};
41+
let type_tip = get_type_tip(ty);
42+
if type_tip.is_some_and(|i| i == "bool") {
43+
quote! {
44+
#[arg(long)]
45+
#[serde(skip_serializing_if="<&bool>::not")]
46+
#id: bool
2347
}
24-
if p.path.segments.len() == 1 && p.path.segments[0].ident == "Option" {
25-
return quote! {
26-
#[arg(long)]
27-
#id: #ty,
28-
};
48+
} else if type_tip.is_some_and(|i| i == "Option") {
49+
quote! {
50+
#[arg(long)]
51+
#f
2952
}
30-
}
31-
if id == &format_ident!("verbose") {
53+
} else if id == &format_ident!("verbose") {
3254
quote! {
3355
#[arg(long, short, action=clap::ArgAction::Count)]
3456
#[serde(skip_serializing_if="u8::is_zero")]
35-
#id: u8,
57+
#id: u8
3658
}
3759
} else if id == &format_ident!("inputs") {
3860
quote! {
39-
#id: #ty,
61+
#f
62+
}
63+
} else if type_tip.is_some_and(|i| i == "Vec") {
64+
quote! {
65+
#[arg(long)]
66+
#id: Option<String>
4067
}
4168
} else {
4269
quote! {
4370
#[arg(long)]
44-
#id: Option<#ty>,
71+
#id: Option<#ty>
4572
}
4673
}
4774
})
@@ -66,7 +93,9 @@ pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStrea
6693
let gen = quote! {
6794
#[serde_with::apply(_ => #[serde(default)])]
6895
#[derive(Deserialize, Default)]
69-
#ast
96+
pub struct #name {
97+
#(#fields),*
98+
}
7099

71100
impl Debug for #name {
72101
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -80,7 +109,7 @@ pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStrea
80109
#[derive(clap::Parser, Serialize)]
81110
#[command(about, long_about = None)]
82111
struct #cli_name {
83-
#(#cli_fields)*
112+
#(#cli_fields),*
84113
}
85114
};
86115
gen.into()

rust/extractor/src/config.rs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ use figment::{
77
Figment,
88
};
99
use itertools::Itertools;
10+
use log::warn;
1011
use num_traits::Zero;
12+
use ra_ap_cfg::{CfgAtom, CfgDiff};
13+
use ra_ap_intern::Symbol;
14+
use ra_ap_paths::Utf8PathBuf;
15+
use ra_ap_project_model::{CargoConfig, CargoFeatures, CfgOverrides, RustLibSource};
1116
use rust_extractor_macros::extractor_cli_config;
12-
use serde::{Deserialize, Serialize};
17+
use serde::{Deserialize, Deserializer, Serialize};
1318
use std::fmt::Debug;
1419
use std::ops::Not;
1520
use std::path::PathBuf;
@@ -32,12 +37,23 @@ impl From<Compression> for trap::Compression {
3237
}
3338
}
3439

40+
// required by the extractor_cli_config macro.
41+
fn deserialize_newline_or_comma_separated<'a, D: Deserializer<'a>, T: for<'b> From<&'b str>>(
42+
deserializer: D,
43+
) -> Result<Vec<T>, D::Error> {
44+
let value = String::deserialize(deserializer)?;
45+
Ok(value.split(['\n', ',']).map(T::from).collect())
46+
}
47+
3548
#[extractor_cli_config]
3649
pub struct Config {
3750
pub scratch_dir: PathBuf,
3851
pub trap_dir: PathBuf,
3952
pub source_archive_dir: PathBuf,
4053
pub cargo_target_dir: Option<PathBuf>,
54+
pub cargo_target: Option<String>,
55+
pub cargo_features: Vec<String>,
56+
pub cargo_cfg_overrides: Vec<String>,
4157
pub verbose: u8,
4258
pub compression: Compression,
4359
pub inputs: Vec<PathBuf>,
@@ -52,7 +68,7 @@ impl Config {
5268
.context("expanding parameter files")?;
5369
let cli_args = CliConfig::parse_from(args);
5470
let mut figment = Figment::new()
55-
.merge(Env::prefixed("CODEQL_"))
71+
.merge(Env::raw().only(["CODEQL_VERBOSE"].as_slice()))
5672
.merge(Env::prefixed("CODEQL_EXTRACTOR_RUST_"))
5773
.merge(Env::prefixed("CODEQL_EXTRACTOR_RUST_OPTION_"))
5874
.merge(Serialized::defaults(cli_args));
@@ -78,4 +94,81 @@ impl Config {
7894
}
7995
figment.extract().context("loading configuration")
8096
}
97+
98+
pub fn to_cargo_config(&self) -> CargoConfig {
99+
let sysroot = Some(RustLibSource::Discover);
100+
101+
let target_dir = self
102+
.cargo_target_dir
103+
.clone()
104+
.unwrap_or_else(|| self.scratch_dir.join("target"));
105+
let target_dir = Utf8PathBuf::from_path_buf(target_dir).ok();
106+
107+
let features = if self.cargo_features.is_empty() {
108+
Default::default()
109+
} else if self.cargo_features.contains(&"*".to_string()) {
110+
CargoFeatures::All
111+
} else {
112+
CargoFeatures::Selected {
113+
features: self.cargo_features.clone(),
114+
no_default_features: false,
115+
}
116+
};
117+
118+
let target = self.cargo_target.clone();
119+
120+
let cfg_overrides = to_cfg_overrides(&self.cargo_cfg_overrides);
121+
122+
CargoConfig {
123+
sysroot,
124+
target_dir,
125+
features,
126+
target,
127+
cfg_overrides,
128+
..Default::default()
129+
}
130+
}
131+
}
132+
133+
fn to_cfg_override(spec: &str) -> CfgAtom {
134+
if let Some((key, value)) = spec.split_once("=") {
135+
CfgAtom::KeyValue {
136+
key: Symbol::intern(key),
137+
value: Symbol::intern(value),
138+
}
139+
} else {
140+
CfgAtom::Flag(Symbol::intern(spec))
141+
}
142+
}
143+
144+
fn to_cfg_overrides(specs: &Vec<String>) -> CfgOverrides {
145+
let mut enabled_cfgs = Vec::new();
146+
let mut disabled_cfgs = Vec::new();
147+
let mut has_test_explicitly_enabled = false;
148+
for spec in specs {
149+
if spec.starts_with("-") {
150+
disabled_cfgs.push(to_cfg_override(&spec[1..]));
151+
} else {
152+
enabled_cfgs.push(to_cfg_override(spec));
153+
if spec == "test" {
154+
has_test_explicitly_enabled = true;
155+
}
156+
}
157+
}
158+
if !has_test_explicitly_enabled {
159+
disabled_cfgs.push(to_cfg_override("test"));
160+
}
161+
if let Some(global) = CfgDiff::new(enabled_cfgs, disabled_cfgs) {
162+
CfgOverrides {
163+
global,
164+
..Default::default()
165+
}
166+
} else {
167+
warn!("non-disjoint cfg overrides, ignoring: {}", specs.join(", "));
168+
CfgOverrides {
169+
global: CfgDiff::new(Vec::new(), vec![to_cfg_override("test")])
170+
.expect("disabling one cfg should always succeed"),
171+
..Default::default()
172+
}
173+
}
81174
}

rust/extractor/src/main.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,9 @@ fn main() -> anyhow::Result<()> {
130130
}
131131
extractor.extract_without_semantics(file, "no manifest found");
132132
}
133-
let target_dir = &cfg
134-
.cargo_target_dir
135-
.unwrap_or_else(|| cfg.scratch_dir.join("target"));
133+
let cargo_config = cfg.to_cargo_config();
136134
for (manifest, files) in map.values().filter(|(_, files)| !files.is_empty()) {
137-
if let Some((ref db, ref vfs)) = RustAnalyzer::load_workspace(manifest, target_dir) {
135+
if let Some((ref db, ref vfs)) = RustAnalyzer::load_workspace(manifest, &cargo_config) {
138136
let semantics = Semantics::new(db);
139137
for file in files {
140138
let Some(id) = path_to_file_id(file, vfs) else {

rust/extractor/src/rust_analyzer.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use ra_ap_load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice
77
use ra_ap_paths::Utf8PathBuf;
88
use ra_ap_project_model::CargoConfig;
99
use ra_ap_project_model::ProjectManifest;
10-
use ra_ap_project_model::RustLibSource;
1110
use ra_ap_span::Edition;
1211
use ra_ap_span::EditionedFileId;
1312
use ra_ap_span::TextRange;
@@ -20,6 +19,7 @@ use ra_ap_vfs::{AbsPathBuf, FileId};
2019
use std::borrow::Cow;
2120
use std::path::{Path, PathBuf};
2221
use triomphe::Arc;
22+
2323
pub enum RustAnalyzer<'a> {
2424
WithSemantics {
2525
vfs: &'a Vfs,
@@ -45,13 +45,8 @@ pub struct ParseResult<'a> {
4545
impl<'a> RustAnalyzer<'a> {
4646
pub fn load_workspace(
4747
project: &ProjectManifest,
48-
target_dir: &Path,
48+
config: &CargoConfig,
4949
) -> Option<(RootDatabase, Vfs)> {
50-
let config = CargoConfig {
51-
sysroot: Some(RustLibSource::Discover),
52-
target_dir: ra_ap_paths::Utf8PathBuf::from_path_buf(target_dir.to_path_buf()).ok(),
53-
..Default::default()
54-
};
5550
let progress = |t| (log::trace!("progress: {}", t));
5651
let load_config = LoadCargoConfig {
5752
load_out_dirs_from_check: true,
@@ -60,7 +55,7 @@ impl<'a> RustAnalyzer<'a> {
6055
};
6156
let manifest = project.manifest_path();
6257

63-
match load_workspace_at(manifest.as_ref(), &config, &load_config, &progress) {
58+
match load_workspace_at(manifest.as_ref(), config, &load_config, &progress) {
6459
Ok((db, vfs, _macro_server)) => Some((db, vfs)),
6560
Err(err) => {
6661
log::error!("failed to load workspace for {}: {}", manifest, err);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
[package]
3+
name = "cfg"
4+
version = "0.1.0"
5+
edition = "2021"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| src/lib.rs:7:1:8:19 | cfg_no_flag |
2+
| src/lib.rs:10:1:11:18 | cfg_no_key |
3+
| src/lib.rs:16:1:17:24 | pointer_width_64 |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| src/lib.rs:1:1:2:16 | cfg_flag |
2+
| src/lib.rs:4:1:5:15 | cfg_key |
3+
| src/lib.rs:13:1:14:12 | test |
4+
| src/lib.rs:19:1:20:24 | pointer_width_32 |

0 commit comments

Comments
 (0)