Skip to content

Commit be45e3d

Browse files
author
Paolo Tranquilli
committed
Rust: allow to specify more cargo configuration options
This allows to tweak via extractor options some aspects of the cargo configuration: * the target architecture * features (including `*` for all, which we must understand whether to set by default) * cfg overrides Integration tests will be added in a follow-up commit.
1 parent 0833940 commit be45e3d

File tree

7 files changed

+155
-33
lines changed

7 files changed

+155
-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
@@ -21,6 +21,8 @@ ra_ap_syntax = "0.0.232"
2121
ra_ap_vfs = "0.0.232"
2222
ra_ap_parser = "0.0.232"
2323
ra_ap_span = "0.0.232"
24+
ra_ap_cfg = "0.0.232"
25+
ra_ap_intern = "0.0.232"
2426
serde = "1.0.209"
2527
serde_with = "3.9.0"
2628
stderrlog = "0.6.0"

rust/extractor/macros/src/lib.rs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,69 @@
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 get_type_tip(&f.ty).is_some_and(|i| i == "Vec") {
23+
quote! {
24+
#[serde(deserialize_with="deserialize_newline_or_comma_separated")]
25+
#f
26+
}
27+
} else {
28+
quote! { #f }
29+
}
30+
})
31+
.collect::<Vec<_>>();
932
let cli_name = format_ident!("Cli{}", name);
1033
let cli_fields = ast
1134
.fields
1235
.iter()
1336
.map(|f| {
1437
let id = f.ident.as_ref().unwrap();
1538
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-
};
23-
}
24-
if p.path.segments.len() == 1 && p.path.segments[0].ident == "Option" {
25-
return quote! {
26-
#[arg(long)]
27-
#id: #ty,
28-
};
29-
}
39+
let type_tip = get_type_tip(ty);
40+
if type_tip.is_some_and(|i| i == "bool") {
41+
return quote! {
42+
#[arg(long)]
43+
#[serde(skip_serializing_if="<&bool>::not")]
44+
#id: bool
45+
};
46+
}
47+
if type_tip.is_some_and(|i| i == "Option") {
48+
return quote! {
49+
#[arg(long)]
50+
#f
51+
};
3052
}
3153
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
4062
}
4163
} else {
4264
quote! {
4365
#[arg(long)]
44-
#id: Option<#ty>,
66+
#id: Option<#ty>
4567
}
4668
}
4769
})
@@ -66,7 +88,9 @@ pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStrea
6688
let gen = quote! {
6789
#[serde_with::apply(_ => #[serde(default)])]
6890
#[derive(Deserialize, Default)]
69-
#ast
91+
pub struct #name {
92+
#(#fields),*
93+
}
7094

7195
impl Debug for #name {
7296
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -80,7 +104,7 @@ pub fn extractor_cli_config(_attr: TokenStream, item: TokenStream) -> TokenStrea
80104
#[derive(clap::Parser, Serialize)]
81105
#[command(about, long_about = None)]
82106
struct #cli_name {
83-
#(#cli_fields)*
107+
#(#cli_fields),*
84108
}
85109
};
86110
gen.into()

rust/extractor/src/config.rs

Lines changed: 84 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>,
@@ -51,7 +67,7 @@ impl Config {
5167
.context("expanding parameter files")?;
5268
let cli_args = CliConfig::parse_from(args);
5369
let mut figment = Figment::new()
54-
.merge(Env::prefixed("CODEQL_"))
70+
.merge(Env::raw().only(["CODEQL_VERBOSE"].as_slice()))
5571
.merge(Env::prefixed("CODEQL_EXTRACTOR_RUST_"))
5672
.merge(Env::prefixed("CODEQL_EXTRACTOR_RUST_OPTION_"))
5773
.merge(Serialized::defaults(cli_args));
@@ -71,4 +87,70 @@ impl Config {
7187
}
7288
figment.extract().context("loading configuration")
7389
}
90+
91+
pub fn to_cargo_config(&self) -> CargoConfig {
92+
let sysroot = Some(RustLibSource::Discover);
93+
94+
let target_dir = self
95+
.cargo_target_dir
96+
.clone()
97+
.unwrap_or_else(|| self.scratch_dir.join("target"));
98+
let target_dir = Utf8PathBuf::from_path_buf(target_dir).ok();
99+
100+
let features = if self.cargo_features.is_empty() {
101+
Default::default()
102+
} else if self.cargo_features.contains(&"*".to_string()) {
103+
CargoFeatures::All
104+
} else {
105+
CargoFeatures::Selected {
106+
features: self.cargo_features.clone(),
107+
no_default_features: false,
108+
}
109+
};
110+
111+
let target = self.cargo_target.clone();
112+
113+
let cfg_overrides = to_cfg_overrides(&self.cargo_cfg_overrides);
114+
115+
CargoConfig {
116+
sysroot,
117+
target_dir,
118+
features,
119+
target,
120+
cfg_overrides,
121+
..Default::default()
122+
}
123+
}
124+
}
125+
126+
fn to_cfg_override(spec: &str) -> CfgAtom {
127+
if let Some((key, value)) = spec.split_once("=") {
128+
CfgAtom::KeyValue {
129+
key: Symbol::intern(key),
130+
value: Symbol::intern(value),
131+
}
132+
} else {
133+
CfgAtom::Flag(Symbol::intern(spec))
134+
}
135+
}
136+
137+
fn to_cfg_overrides(specs: &Vec<String>) -> CfgOverrides {
138+
let mut enabled_cfgs = Vec::new();
139+
let mut disabled_cfgs = Vec::new();
140+
for spec in specs {
141+
if spec.starts_with("-") {
142+
disabled_cfgs.push(to_cfg_override(&spec[1..]));
143+
} else {
144+
enabled_cfgs.push(to_cfg_override(spec));
145+
}
146+
}
147+
if let Some(global) = CfgDiff::new(enabled_cfgs, disabled_cfgs) {
148+
CfgOverrides {
149+
global,
150+
..Default::default()
151+
}
152+
} else {
153+
warn!("non-disjoint cfg overrides, ignoring: {}", specs.join(", "));
154+
CfgOverrides::default()
155+
}
74156
}

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);

0 commit comments

Comments
 (0)