foundry_compilers/
filter.rs1use crate::{
4 compilers::{multi::MultiCompilerParsedSource, CompilerSettings, ParsedSource},
5 resolver::{parse::SolData, GraphEdges},
6 Sources,
7};
8use foundry_compilers_artifacts::output_selection::OutputSelection;
9use std::{
10 collections::HashSet,
11 fmt,
12 path::{Path, PathBuf},
13};
14
15pub trait FileFilter: dyn_clone::DynClone + Send + Sync {
17 fn is_match(&self, file: &Path) -> bool;
19}
20
21dyn_clone::clone_trait_object!(FileFilter);
22
23impl<F: Fn(&Path) -> bool + Clone + Send + Sync> FileFilter for F {
24 fn is_match(&self, file: &Path) -> bool {
25 (self)(file)
26 }
27}
28
29#[derive(Clone, Default)]
31pub struct TestFileFilter {
32 _priv: (),
33}
34
35impl fmt::Debug for TestFileFilter {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("TestFileFilter").finish()
38 }
39}
40
41impl fmt::Display for TestFileFilter {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.write_str("TestFileFilter")
44 }
45}
46
47impl FileFilter for TestFileFilter {
48 fn is_match(&self, file: &Path) -> bool {
49 file.file_name().and_then(|s| s.to_str()).map(|s| s.ends_with(".t.sol")).unwrap_or_default()
50 }
51}
52
53pub trait MaybeSolData {
54 fn sol_data(&self) -> Option<&SolData>;
55}
56
57impl MaybeSolData for SolData {
58 fn sol_data(&self) -> Option<&SolData> {
59 Some(self)
60 }
61}
62
63impl MaybeSolData for MultiCompilerParsedSource {
64 fn sol_data(&self) -> Option<&SolData> {
65 match self {
66 Self::Solc(data) => Some(data),
67 _ => None,
68 }
69 }
70}
71
72#[derive(Default)]
75pub enum SparseOutputFilter<'a> {
76 #[default]
81 Optimized,
82 Custom(&'a dyn FileFilter),
84}
85
86impl<'a> SparseOutputFilter<'a> {
87 pub fn new(filter: Option<&'a dyn FileFilter>) -> Self {
88 if let Some(f) = filter {
89 SparseOutputFilter::Custom(f)
90 } else {
91 SparseOutputFilter::Optimized
92 }
93 }
94
95 pub fn sparse_sources<D: ParsedSource, S: CompilerSettings>(
105 &self,
106 sources: &Sources,
107 settings: &mut S,
108 graph: &GraphEdges<D>,
109 ) -> Vec<PathBuf> {
110 let mut full_compilation: HashSet<PathBuf> = sources
111 .dirty_files()
112 .flat_map(|file| {
113 if let Self::Custom(f) = self {
115 if !f.is_match(file) {
116 return vec![];
117 }
118 }
119
120 let mut required_sources = vec![file.clone()];
122 if let Some(data) = graph.get_parsed_source(file) {
123 let imports = graph.imports(file).into_iter().filter_map(|import| {
124 graph.get_parsed_source(import).map(|data| (import, data))
125 });
126 for import in data.compilation_dependencies(imports) {
127 let import = import.to_path_buf();
128
129 #[cfg(windows)]
130 let import = {
131 use path_slash::PathBufExt;
132
133 PathBuf::from(import.to_slash_lossy().to_string())
134 };
135
136 required_sources.push(import);
137 }
138 }
139
140 required_sources
141 })
142 .collect();
143
144 full_compilation.retain(|file| sources.0.get(file).is_some_and(|s| s.is_dirty()));
146
147 settings.update_output_selection(|selection| {
148 trace!(
149 "optimizing output selection for {} sources",
150 sources.len() - full_compilation.len()
151 );
152 let default_selection = selection
153 .as_mut()
154 .remove("*")
155 .unwrap_or_else(OutputSelection::default_file_output_selection);
156
157 for file in sources.0.keys() {
159 let key = file.display().to_string();
160 let output = if full_compilation.contains(file) {
161 default_selection.clone()
162 } else {
163 OutputSelection::empty_file_output_select()
164 };
165 selection.as_mut().insert(key, output);
166 }
167 });
168
169 full_compilation.into_iter().collect()
170 }
171}
172
173impl fmt::Debug for SparseOutputFilter<'_> {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match self {
176 SparseOutputFilter::Optimized => f.write_str("Optimized"),
177 SparseOutputFilter::Custom(_) => f.write_str("Custom"),
178 }
179 }
180}