Skip to content

Commit

Permalink
Allow for dependency-specific build configuration (#52)
Browse files Browse the repository at this point in the history
## What Changed?

The analyzer now reads a `Paralegal.toml` file in which you can provide
additional build configuration.

At the moment this consists of dependency-specific rust features you can
enable. For instance

```toml
[dep.cfg-if]
rust_features = ["is_terminal"]
```

It also changes the structure if arguments a bit, now the arguments read
from the command line (`ClapPargs`) are converted into a compressed,
enriched `Args` struct for use internally. During the conversion we read
the env variable for dump options, compress dump option, resolve the
logging configuration and read the `Paralegal.toml` configuration file.

## Why Does It Need To?

Because we are on nightly the default features enabled in rustc are not
the same as they are on stable. Therefore we need a way to pass those
features is. I tried doing this with
`RUSTFLAGS=-Zcrate-attr=feature(my_feature)` before but that doesn't
work because these flags are global is a dependency compiles with
`no-std` and the feature is a library feature then we get an "unknown
feature" error which fails the build.

## Checklist

- [x] Above description has been filled out so that upon quash merge we
have a
  good record of what changed.
- [x] New functions, methods, types are documented. Old documentation is
updated
  if necessary
- [x] Documentation in Notion has been updated
- [ ] Tests for new behaviors are provided
  - [ ] New test suites (if any) ave been added to the CI tests (in
`.github/workflows/rust.yml`) either as compiler test or integration
test.
*Or* justification for their omission from CI has been provided in this
PR
    description.
  • Loading branch information
JustusAdam authored Sep 27, 2023
1 parent 0e6881b commit 96bc5bf
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 99 deletions.
4 changes: 2 additions & 2 deletions crates/paralegal-flow/src/ana/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl<'tcx> CollectingVisitor<'tcx> {
fn analyze(mut self) -> Result<ProgramDescription> {
let mut targets = std::mem::take(&mut self.functions_to_analyze);

if let LogLevelConfig::Targeted(s) = &*self.opts.debug() {
if let LogLevelConfig::Targeted(s) = self.opts.debug() {
assert!(
targets.iter().any(|target| target.name().as_str() == s),
"Debug output option specified a specific target '{s}', but no such target was found in [{}]",
Expand Down Expand Up @@ -377,7 +377,7 @@ fn def_ids_from_controllers(map: &HashMap<DefId, Ctrl>, tcx: TyCtxt) -> HashSet<
/// matches the one selected with the `debug` flag on the command line (and
/// reset it afterward).
fn with_reset_level_if_target<R, F: FnOnce() -> R>(opts: &crate::Args, target: Symbol, f: F) -> R {
if matches!(&*opts.debug(), LogLevelConfig::Targeted(s) if target.as_str() == s) {
if matches!(opts.debug(), LogLevelConfig::Targeted(s) if target.as_str() == s) {
with_temporary_logging_level(log::LevelFilter::Debug, f)
} else {
f()
Expand Down
178 changes: 94 additions & 84 deletions crates/paralegal-flow/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,89 @@
//! allow us to change the name and default value of the argument without having
//! to migrate the code using that argument.

use std::{borrow::Cow, str::FromStr};

use anyhow::Error;
use clap::ValueEnum;
use std::str::FromStr;

use crate::utils::TinyBitSet;

use crate::{num_derive, num_traits::FromPrimitive};

#[derive(serde::Deserialize, serde::Serialize)]
pub struct Args(GArgs<DumpArgs>);

#[derive(clap::Args)]
pub struct ParseableArgs {
#[clap(flatten)]
ignored: GArgs<ParseableDumpArgs>,
}

impl Args {
pub fn from_parseable(value: ParseableArgs) -> Result<Self, String> {
let ParseableArgs {
ignored:
GArgs {
verbose,
debug,
debug_target,
result_path,
relaxed,
target,
abort_after_analysis,
anactrl,
modelctrl,
dump,
},
impl TryFrom<ClapArgs> for Args {
type Error = Error;
fn try_from(value: ClapArgs) -> Result<Self, Self::Error> {
let ClapArgs {
verbose,
debug,
debug_target,
result_path,
relaxed,
target,
abort_after_analysis,
anactrl,
modelctrl,
dump,
} = value;
let mut dump: DumpArgs = dump.into();
if let Ok(from_env) = std::env::var("PARALEGAL_DUMP") {
let from_env = DumpArgs::from_str(&from_env, false)?;
let from_env =
DumpArgs::from_str(&from_env, false).map_err(|s| anyhow::anyhow!("{}", s))?;
dump.0 |= from_env.0;
}
Ok(Args(GArgs {
let build_config_file = std::path::Path::new("Paralegal.toml");
let build_config = if build_config_file.exists() {
toml::from_str(&std::fs::read_to_string(build_config_file)?)?
} else {
Default::default()
};
let log_level_config = match debug_target {
Some(target) if !target.is_empty() => LogLevelConfig::Targeted(target),
_ if debug => LogLevelConfig::Enabled,
_ => LogLevelConfig::Disabled,
};
Ok(Args {
verbose,
debug,
debug_target,
log_level_config,
result_path,
relaxed,
target,
abort_after_analysis,
anactrl,
modelctrl,
dump,
}))
build_config,
})
}
}

/// Top level command line arguments
///
/// There are some shenanigans going on here wrt the `DA` type variable. This is
/// because as of writing these docs Justus can't figure out how to directly
/// collect the dump options into a set, so I first collect them into a vector
/// and then compress it into a set.
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Args {
/// Print additional logging output (up to the "info" level)
verbose: bool,
log_level_config: LogLevelConfig,
/// Where to write the resulting forge code to (defaults to `analysis_result.frg`)
result_path: std::path::PathBuf,
/// Emit warnings instead of aborting the analysis on sanity checks
relaxed: bool,

target: Option<String>,
/// Abort the compilation after finishing the analysis
abort_after_analysis: bool,
/// Additional arguments that control the flow analysis specifically
anactrl: AnalysisCtrl,
/// Additional arguments that control the generation and composition of the model
modelctrl: ModelCtrl,
/// Additional arguments that control debug output specifically
dump: DumpArgs,
/// Additional configuration for the build process/rustc
build_config: BuildConfig,
}

/// Arguments as exposed on the command line.
///
/// This is what the `Parseable*` structs are trying to hide from the user.
#[derive(serde::Serialize, serde::Deserialize, clap::Args)]
struct GArgs<DA: clap::FromArgMatches + clap::Args> {
/// You should then use `try_into` to convert this to [`Args`], the argument
/// structure used internally.
#[derive(clap::Args)]
pub struct ClapArgs {
/// Print additional logging output (up to the "info" level)
#[clap(short, long, env = "PARALEGAL_VERBOSE")]
verbose: bool,
Expand Down Expand Up @@ -106,10 +125,10 @@ struct GArgs<DA: clap::FromArgMatches + clap::Args> {
modelctrl: ModelCtrl,
/// Additional arguments that control debug args specifically
#[clap(flatten)]
dump: DA,
dump: ParseableDumpArgs,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, clap::Args)]
#[derive(Clone, clap::Args)]
pub struct ParseableDumpArgs {
/// Generate intermediate of various formats and at various stages of
/// compilation. A short description of each value is provided here, for a
Expand Down Expand Up @@ -160,26 +179,6 @@ impl From<ParseableDumpArgs> for DumpArgs {
}
}

/// See [`DumpArgs`]
impl clap::FromArgMatches for DumpArgs {
fn from_arg_matches(_: &clap::ArgMatches) -> Result<Self, clap::Error> {
unimplemented!()
}
fn update_from_arg_matches(&mut self, _: &clap::ArgMatches) -> Result<(), clap::Error> {
unimplemented!()
}
}

/// See [`DumpArgs`]
impl clap::Args for DumpArgs {
fn augment_args(_: clap::Command) -> clap::Command {
unimplemented!()
}
fn augment_args_for_update(_: clap::Command) -> clap::Command {
unimplemented!()
}
}

/// Collection of the [`DumpOption`]s a user has set.
///
/// Separates the cli and the internal api. Users set [`DumpOption`]s in the
Expand Down Expand Up @@ -262,66 +261,63 @@ enum DumpOption {
/// How a specific logging level was configured. (currently only used for the
/// `--debug` level)
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub enum LogLevelConfig<'a> {
pub enum LogLevelConfig {
/// Logging for this level is only enabled for a specific target function
Targeted(Cow<'a, str>),
Targeted(String),
/// Logging for this level is not directly enabled
Disabled,
/// Logging for this level was directly enabled
Enabled,
}

impl std::fmt::Display for LogLevelConfig<'_> {
impl std::fmt::Display for LogLevelConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}

impl<'a> LogLevelConfig<'a> {
impl LogLevelConfig {
pub fn is_enabled(&self) -> bool {
matches!(self, LogLevelConfig::Targeted(..) | LogLevelConfig::Enabled)
}
}

impl Args {
pub fn target(&self) -> Option<&str> {
self.0.target.as_deref()
self.target.as_deref()
}
/// Returns the configuration specified for the `--debug` option
pub fn debug(&self) -> Cow<'_, LogLevelConfig<'_>> {
Cow::Owned(match &self.0.debug_target {
Some(target) if !target.is_empty() => {
LogLevelConfig::Targeted(Cow::Borrowed(target.as_str()))
}
_ if self.0.debug => LogLevelConfig::Enabled,
_ => LogLevelConfig::Disabled,
})
pub fn debug(&self) -> &LogLevelConfig {
&self.log_level_config
}
/// Access the debug arguments
pub fn dbg(&self) -> &DumpArgs {
&self.0.dump
&self.dump
}
/// Access the argument controlling the analysis
pub fn anactrl(&self) -> &AnalysisCtrl {
&self.0.anactrl
&self.anactrl
}
pub fn modelctrl(&self) -> &ModelCtrl {
&self.0.modelctrl
&self.modelctrl
}
/// the file to write results to
pub fn result_path(&self) -> &std::path::Path {
self.0.result_path.as_path()
self.result_path.as_path()
}
/// Should we output additional log messages (level `info`)
pub fn verbose(&self) -> bool {
self.0.verbose
self.verbose
}
/// Warn instead of crashing the program in case of non-fatal errors
pub fn relaxed(&self) -> bool {
self.0.relaxed
self.relaxed
}
pub fn abort_after_analysis(&self) -> bool {
self.0.abort_after_analysis
self.abort_after_analysis
}
pub fn build_config(&self) -> &BuildConfig {
&self.build_config
}
}

Expand Down Expand Up @@ -553,3 +549,17 @@ impl DumpArgs {
self.has(DumpOption::LocalsGraph)
}
}

/// Dependency specific configuration
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
pub struct DepConfig {
/// Additional rust features to enable
pub rust_features: Vec<String>,
}

/// Additional configuration for the build process/rustc
#[derive(serde::Deserialize, serde::Serialize, Default, Debug)]
pub struct BuildConfig {
/// Dependency specific configuration
pub dep: crate::HashMap<String, DepConfig>,
}
41 changes: 28 additions & 13 deletions crates/paralegal-flow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub mod rust {
pub use mir::Location;
}

use args::{LogLevelConfig, ParseableArgs};
use args::{ClapArgs, LogLevelConfig};
use pretty::DocBuilder;
use rust::*;

Expand Down Expand Up @@ -102,7 +102,7 @@ pub mod test_utils;

pub use paralegal_spdg as desc;

pub use args::{AnalysisCtrl, Args, DumpArgs, ModelCtrl};
pub use args::{AnalysisCtrl, Args, BuildConfig, DepConfig, DumpArgs, ModelCtrl};

use crate::{
frg::{call_site_to_string, ForgeConverter},
Expand All @@ -128,7 +128,7 @@ struct ArgWrapper {

/// The actual arguments
#[clap(flatten)]
args: ParseableArgs,
args: ClapArgs,

/// Pass through for additional cargo arguments (like --features)
#[clap(last = true)]
Expand Down Expand Up @@ -245,14 +245,14 @@ impl rustc_plugin::RustcPlugin for DfppPlugin {
std::env::set_var("SYSROOT", env!("SYSROOT_PATH"));

rustc_plugin::RustcPluginArgs {
args: Args::from_parseable(args.args).unwrap(),
filter: CrateFilter::OnlyWorkspace,
args: args.args.try_into().unwrap(),
filter: CrateFilter::AllCrates,
}
}

fn run(
self,
compiler_args: Vec<String>,
mut compiler_args: Vec<String>,
plugin_args: Self::Args,
) -> rustc_interface::interface::Result<()> {
// return rustc_driver::RunCompiler::new(&compiler_args, &mut NoopCallbacks { }).run();
Expand All @@ -268,17 +268,32 @@ impl rustc_plugin::RustcPlugin for DfppPlugin {
// `log::set_max_level`.
//println!("compiling {compiler_args:?}");

if let Some(k) = compiler_args
let crate_name = compiler_args
.iter()
.enumerate()
.find_map(|(i, s)| (s == "--crate-name").then_some(i))
.and_then(|i| compiler_args.get(i + 1))
.cloned();

if let Some(dep_config) = crate_name
.as_ref()
.and_then(|s| plugin_args.build_config().dep.get(s))
{
if let Some(s) = compiler_args.get(k + 1) {
if plugin_args.target().map_or(false, |t| t != s) {
return rustc_driver::RunCompiler::new(&compiler_args, &mut NoopCallbacks {})
.run();
}
}
compiler_args.extend(
dep_config
.rust_features
.iter()
.map(|f| format!("-Zcrate-attr=feature({})", f)),
);
}

let is_target = crate_name
.as_ref()
.and_then(|s| plugin_args.target().map(|t| s == t))
.unwrap_or(false);

if !is_target && std::env::var("CARGO_PRIMARY_PACKAGE").is_err() {
return rustc_driver::RunCompiler::new(&compiler_args, &mut NoopCallbacks {}).run();
}

let lvl = if plugin_args.debug().is_enabled() {
Expand Down

0 comments on commit 96bc5bf

Please sign in to comment.