foundry_compilers/report/
compiler.rs1use foundry_compilers_artifacts::{CompilerOutput, SolcInput};
9use semver::Version;
10use std::{env, path::PathBuf, str::FromStr};
11
12#[derive(Clone, Debug, Default)]
26pub struct SolcCompilerIoReporter {
27 target: Option<Target>,
29}
30
31impl SolcCompilerIoReporter {
32 pub fn new(value: &str) -> Self {
35 Self { target: Some(value.parse().unwrap_or_default()) }
36 }
37
38 pub const DEFAULT_ENV: &'static str = "foundry_compilers_LOG";
43
44 pub fn from_default_env() -> Self {
47 Self::from_env(Self::DEFAULT_ENV)
48 }
49
50 pub fn from_env(env: impl AsRef<std::ffi::OsStr>) -> Self {
53 env::var(env).map(|var| Self::new(&var)).unwrap_or_default()
54 }
55
56 pub fn log_compiler_input(&self, input: &SolcInput, version: &Version) {
58 if let Some(target) = &self.target {
59 target.write_input(input, version)
60 }
61 }
62
63 pub fn log_compiler_output(&self, output: &CompilerOutput, version: &Version) {
65 if let Some(target) = &self.target {
66 target.write_output(output, version)
67 }
68 }
69}
70
71impl<S: AsRef<str>> From<S> for SolcCompilerIoReporter {
72 fn from(s: S) -> Self {
73 Self::new(s.as_ref())
74 }
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
79struct Target {
80 dest_input: PathBuf,
82 dest_output: PathBuf,
84}
85
86impl Target {
87 fn write_input(&self, input: &SolcInput, version: &Version) {
88 trace!("logging compiler input to {}", self.dest_input.display());
89 match serde_json::to_string_pretty(input) {
90 Ok(json) => {
91 if let Err(err) = std::fs::write(get_file_name(&self.dest_input, version), json) {
92 error!("Failed to write compiler input: {}", err)
93 }
94 }
95 Err(err) => {
96 error!("Failed to serialize compiler input: {}", err)
97 }
98 }
99 }
100
101 fn write_output(&self, output: &CompilerOutput, version: &Version) {
102 trace!("logging compiler output to {}", self.dest_output.display());
103 match serde_json::to_string_pretty(output) {
104 Ok(json) => {
105 if let Err(err) = std::fs::write(get_file_name(&self.dest_output, version), json) {
106 error!("Failed to write compiler output: {}", err)
107 }
108 }
109 Err(err) => {
110 error!("Failed to serialize compiler output: {}", err)
111 }
112 }
113 }
114}
115
116impl Default for Target {
117 fn default() -> Self {
118 Self {
119 dest_input: "compiler-input.json".into(),
120 dest_output: "compiler-output.json".into(),
121 }
122 }
123}
124
125impl FromStr for Target {
126 type Err = Box<dyn std::error::Error + Send + Sync>;
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 let mut dest_input = None;
129 let mut dest_output = None;
130 for part in s.split(',') {
131 let (name, val) =
132 part.split_once('=').ok_or_else(|| BadName { name: part.to_string() })?;
133 match name {
134 "i" | "in" | "input" | "compilerinput" => {
135 dest_input = Some(PathBuf::from(val));
136 }
137 "o" | "out" | "output" | "compileroutput" => {
138 dest_output = Some(PathBuf::from(val));
139 }
140 _ => return Err(BadName { name: part.to_string() }.into()),
141 };
142 }
143
144 Ok(Self {
145 dest_input: dest_input.unwrap_or_else(|| "compiler-input.json".into()),
146 dest_output: dest_output.unwrap_or_else(|| "compiler-output.json".into()),
147 })
148 }
149}
150
151#[derive(Clone, Debug, thiserror::Error)]
153#[error("{}", self.name)]
154pub struct BadName {
155 name: String,
156}
157
158fn get_file_name(path: impl Into<PathBuf>, v: &Version) -> PathBuf {
160 let mut path = path.into();
161 if let Some(stem) = path.file_stem().and_then(|s| s.to_str().map(|s| s.to_string())) {
162 path.set_file_name(format!("{stem}.{}.{}.{}.json", v.major, v.minor, v.patch));
163 }
164 path
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use std::fs;
171 use tempfile::tempdir;
172
173 #[test]
174 fn can_set_file_name() {
175 let s = "/a/b/c/in.json";
176 let p = get_file_name(s, &Version::new(0, 8, 10));
177 assert_eq!(PathBuf::from("/a/b/c/in.0.8.10.json"), p);
178
179 let s = "abc.json";
180 let p = get_file_name(s, &Version::new(0, 8, 10));
181 assert_eq!(PathBuf::from("abc.0.8.10.json"), p);
182 }
183
184 #[test]
185 fn can_parse_target() {
186 let target: Target = "in=in.json,out=out.json".parse().unwrap();
187 assert_eq!(target, Target { dest_input: "in.json".into(), dest_output: "out.json".into() });
188
189 let target: Target = "in=in.json".parse().unwrap();
190 assert_eq!(target, Target { dest_input: "in.json".into(), ..Default::default() });
191
192 let target: Target = "out=out.json".parse().unwrap();
193 assert_eq!(target, Target { dest_output: "out.json".into(), ..Default::default() });
194 }
195
196 #[test]
197 fn can_init_reporter_from_env() {
198 let rep = SolcCompilerIoReporter::from_default_env();
199 assert!(rep.target.is_none());
200 unsafe { std::env::set_var("foundry_compilers_LOG", "in=in.json,out=out.json") };
202 let rep = SolcCompilerIoReporter::from_default_env();
203 assert!(rep.target.is_some());
204 assert_eq!(
205 rep.target.unwrap(),
206 Target { dest_input: "in.json".into(), dest_output: "out.json".into() }
207 );
208 unsafe { std::env::remove_var("foundry_compilers_LOG") };
210 }
211
212 #[test]
213 fn check_no_write_when_no_target() {
214 let reporter = SolcCompilerIoReporter::default();
215 let version = Version::parse("0.8.10").unwrap();
216 let input = SolcInput::default();
217 let output = CompilerOutput::default();
218
219 reporter.log_compiler_input(&input, &version);
220 reporter.log_compiler_output(&output, &version);
221 }
222
223 #[test]
224 fn serialize_and_write_to_file() {
225 let dir = tempdir().unwrap();
226 let input_path = dir.path().join("input.json");
227 let output_path = dir.path().join("output.json");
228 let version = Version::parse("0.8.10").unwrap();
229 let target = Target { dest_input: input_path.clone(), dest_output: output_path.clone() };
230
231 let input = SolcInput::default();
232 let output = CompilerOutput::default();
233
234 target.write_input(&input, &version);
235 target.write_output(&output, &version);
236
237 let input_content = fs::read_to_string(get_file_name(&input_path, &version)).unwrap();
238 let output_content = fs::read_to_string(get_file_name(&output_path, &version)).unwrap();
239
240 assert!(!input_content.is_empty());
241 assert!(!output_content.is_empty());
242
243 dir.close().unwrap();
244 }
245}