feat(solc): support for solc io json output (#1043)
* feat(solc): support for solc io json output * chore: wording
This commit is contained in:
parent
b579dc183a
commit
01d4fceaee
|
@ -0,0 +1,189 @@
|
||||||
|
//! Additional logging [CompilerInput] and [CompilerOutput]
|
||||||
|
//!
|
||||||
|
//! Useful for debugging purposes.
|
||||||
|
//! As solc compiler input and output can become quite large (in the tens of MB) we still want a way
|
||||||
|
//! to get this info when debugging an issue. Most convenient way to look at these object is as a
|
||||||
|
//! separate json file
|
||||||
|
|
||||||
|
use crate::{CompilerInput, CompilerOutput};
|
||||||
|
use std::{env, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
/// Debug Helper type that can be used to write the [Solc] [CompilerInput] and [CompilerOutput] to
|
||||||
|
/// disk if configured.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// If `ETHERS_SOLC_LOG=in=in.json,out=out.json` is then the reporter will be configured to write
|
||||||
|
/// the compiler input as pretty formatted json to `in.json` and the compiler output to `out.json`
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ethers_solc::report::SolcCompilerIoReporter;
|
||||||
|
/// std::env::set_var("ETHERS_SOLC_LOG", "in=in.json,out=out.json");
|
||||||
|
/// let rep = SolcCompilerIoReporter::from_default_env();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct SolcCompilerIoReporter {
|
||||||
|
/// where to write the output to, `None` if not enabled
|
||||||
|
target: Option<Target>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolcCompilerIoReporter {
|
||||||
|
/// Returns a new `SolcCompilerIOLayer` from the fields in the given string,
|
||||||
|
/// ignoring any that are invalid.
|
||||||
|
pub fn new(value: impl AsRef<str>) -> Self {
|
||||||
|
Self { target: Some(value.as_ref().parse().unwrap_or_default()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `ETHERS_SOLC_LOG` is the default environment variable used by
|
||||||
|
/// [`SolcCompilerIOLayer::from_default_env`]
|
||||||
|
///
|
||||||
|
/// [`SolcCompilerIOLayer::from_default_env`]: #method.from_default_env
|
||||||
|
pub const DEFAULT_ENV: &'static str = "ETHERS_SOLC_LOG";
|
||||||
|
|
||||||
|
/// Returns a new `SolcCompilerIOLayer` from the value of the `ETHERS_SOLC_LOG` environment
|
||||||
|
/// variable, ignoring any invalid filter directives.
|
||||||
|
pub fn from_default_env() -> Self {
|
||||||
|
Self::from_env(Self::DEFAULT_ENV)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new `SolcCompilerIOLayer` from the value of the given environment
|
||||||
|
/// variable, ignoring any invalid filter directives.
|
||||||
|
pub fn from_env<A: AsRef<str>>(env: A) -> Self {
|
||||||
|
env::var(env.as_ref()).map(Self::new).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback to write the input to disk if target is set
|
||||||
|
pub fn log_compiler_input(&self, input: &CompilerInput) {
|
||||||
|
if let Some(ref target) = self.target {
|
||||||
|
target.write_input(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback to write the input to disk if target is set
|
||||||
|
pub fn log_compiler_output(&self, output: &CompilerOutput) {
|
||||||
|
if let Some(ref target) = self.target {
|
||||||
|
target.write_output(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> From<S> for SolcCompilerIoReporter
|
||||||
|
where
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
fn from(s: S) -> Self {
|
||||||
|
Self::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the `in=<path>,out=<path>` value
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
struct Target {
|
||||||
|
/// path where the compiler input file should be written to
|
||||||
|
dest_input: PathBuf,
|
||||||
|
/// path where the compiler output file should be written to
|
||||||
|
dest_output: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target {
|
||||||
|
fn write_input(&self, input: &CompilerInput) {
|
||||||
|
tracing::trace!("logging compiler input to {}", self.dest_input.display());
|
||||||
|
match serde_json::to_string_pretty(input) {
|
||||||
|
Ok(json) => {
|
||||||
|
if let Err(err) = std::fs::write(&self.dest_input, json) {
|
||||||
|
tracing::error!("Failed to write compiler input: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to serialize compiler input: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_output(&self, output: &CompilerOutput) {
|
||||||
|
tracing::trace!("logging compiler output to {}", self.dest_output.display());
|
||||||
|
match serde_json::to_string_pretty(output) {
|
||||||
|
Ok(json) => {
|
||||||
|
if let Err(err) = std::fs::write(&self.dest_output, json) {
|
||||||
|
tracing::error!("Failed to write compiler output: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to serialize compiler output: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Target {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
dest_input: "compiler-input.json".into(),
|
||||||
|
dest_output: "compiler-output.json".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Target {
|
||||||
|
type Err = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut dest_input = None;
|
||||||
|
let mut dest_output = None;
|
||||||
|
for part in s.split(',') {
|
||||||
|
let (name, val) =
|
||||||
|
part.split_once('=').ok_or_else(|| BadName { name: part.to_string() })?;
|
||||||
|
match name {
|
||||||
|
"i" | "in" | "input" | "compilerinput" => {
|
||||||
|
dest_input = Some(PathBuf::from(val));
|
||||||
|
}
|
||||||
|
"o" | "out" | "output" | "compileroutput" => {
|
||||||
|
dest_output = Some(PathBuf::from(val));
|
||||||
|
}
|
||||||
|
_ => return Err(BadName { name: part.to_string() }.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
dest_input: dest_input.unwrap_or_else(|| "compiler-input.json".into()),
|
||||||
|
dest_output: dest_output.unwrap_or_else(|| "compiler-output.json".into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates that a field name specified in the env value was invalid.
|
||||||
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
|
#[error("{}", self.name)]
|
||||||
|
pub struct BadName {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_target() {
|
||||||
|
let target: Target = "in=in.json,out=out.json".parse().unwrap();
|
||||||
|
assert_eq!(target, Target { dest_input: "in.json".into(), dest_output: "out.json".into() });
|
||||||
|
|
||||||
|
let target: Target = "in=in.json".parse().unwrap();
|
||||||
|
assert_eq!(target, Target { dest_input: "in.json".into(), ..Default::default() });
|
||||||
|
|
||||||
|
let target: Target = "out=out.json".parse().unwrap();
|
||||||
|
assert_eq!(target, Target { dest_output: "out.json".into(), ..Default::default() });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_init_reporter_from_env() {
|
||||||
|
let rep = SolcCompilerIoReporter::from_default_env();
|
||||||
|
assert!(rep.target.is_none());
|
||||||
|
std::env::set_var("ETHERS_SOLC_LOG", "in=in.json,out=out.json");
|
||||||
|
let rep = SolcCompilerIoReporter::from_default_env();
|
||||||
|
assert!(rep.target.is_some());
|
||||||
|
assert_eq!(
|
||||||
|
rep.target.unwrap(),
|
||||||
|
Target { dest_input: "in.json".into(), dest_output: "out.json".into() }
|
||||||
|
);
|
||||||
|
std::env::remove_var("ETHERS_SOLC_LOG");
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,9 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod compiler;
|
||||||
|
pub use compiler::SolcCompilerIoReporter;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static CURRENT_STATE: State = State {
|
static CURRENT_STATE: State = State {
|
||||||
scoped: RefCell::new(Report::none()),
|
scoped: RefCell::new(Report::none()),
|
Loading…
Reference in New Issue