foundry_compilers_artifacts_solc/
sources.rs1use foundry_compilers_core::error::SolcIoError;
2use serde::{Deserialize, Serialize};
3use std::{
4 collections::BTreeMap,
5 fs,
6 path::{Path, PathBuf},
7 sync::Arc,
8};
9
10#[cfg(feature = "walkdir")]
11use foundry_compilers_core::utils;
12
13type SourcesInner = BTreeMap<PathBuf, Source>;
14
15#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub struct Sources(pub SourcesInner);
18
19impl Sources {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn all_dirty(&self) -> bool {
27 self.0.values().all(|s| s.is_dirty())
28 }
29
30 pub fn dirty(&self) -> impl Iterator<Item = (&PathBuf, &Source)> + '_ {
32 self.0.iter().filter(|(_, s)| s.is_dirty())
33 }
34
35 pub fn clean(&self) -> impl Iterator<Item = (&PathBuf, &Source)> + '_ {
37 self.0.iter().filter(|(_, s)| !s.is_dirty())
38 }
39
40 pub fn dirty_files(&self) -> impl Iterator<Item = &PathBuf> + '_ {
42 self.dirty().map(|(k, _)| k)
43 }
44}
45
46impl std::ops::Deref for Sources {
47 type Target = SourcesInner;
48
49 fn deref(&self) -> &Self::Target {
50 &self.0
51 }
52}
53
54impl std::ops::DerefMut for Sources {
55 fn deref_mut(&mut self) -> &mut Self::Target {
56 &mut self.0
57 }
58}
59
60impl<I> From<I> for Sources
61where
62 SourcesInner: From<I>,
63{
64 fn from(value: I) -> Self {
65 Self(From::from(value))
66 }
67}
68
69impl<I> FromIterator<I> for Sources
70where
71 SourcesInner: FromIterator<I>,
72{
73 fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
74 Self(FromIterator::from_iter(iter))
75 }
76}
77
78impl IntoIterator for Sources {
79 type Item = <SourcesInner as IntoIterator>::Item;
80 type IntoIter = <SourcesInner as IntoIterator>::IntoIter;
81
82 fn into_iter(self) -> Self::IntoIter {
83 self.0.into_iter()
84 }
85}
86
87impl<'a> IntoIterator for &'a Sources {
88 type Item = <&'a SourcesInner as IntoIterator>::Item;
89 type IntoIter = <&'a SourcesInner as IntoIterator>::IntoIter;
90
91 fn into_iter(self) -> Self::IntoIter {
92 self.0.iter()
93 }
94}
95
96impl<'a> IntoIterator for &'a mut Sources {
97 type Item = <&'a mut SourcesInner as IntoIterator>::Item;
98 type IntoIter = <&'a mut SourcesInner as IntoIterator>::IntoIter;
99
100 fn into_iter(self) -> Self::IntoIter {
101 self.0.iter_mut()
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
109pub struct Source {
110 pub content: Arc<String>,
116 #[serde(skip, default)]
117 pub kind: SourceCompilationKind,
118}
119
120impl Source {
121 pub fn new(content: impl Into<String>) -> Self {
123 Self { content: Arc::new(content.into()), kind: SourceCompilationKind::Complete }
124 }
125
126 #[instrument(name = "read_source", level = "debug", skip_all, err)]
128 pub fn read(file: &Path) -> Result<Self, SolcIoError> {
129 trace!(file=%file.display());
130 let mut content = fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))?;
131
132 if content.contains('\r') {
134 content = content.replace("\r\n", "\n");
135 }
136
137 Ok(Self::new(content))
138 }
139
140 pub fn is_dirty(&self) -> bool {
142 self.kind.is_dirty()
143 }
144
145 #[cfg(feature = "walkdir")]
147 pub fn read_all_from(dir: &Path, extensions: &[&str]) -> Result<Sources, SolcIoError> {
148 Self::read_all_files(utils::source_files(dir, extensions))
149 }
150
151 #[cfg(feature = "walkdir")]
153 pub fn read_sol_yul_from(dir: &Path) -> Result<Sources, SolcIoError> {
154 Self::read_all_from(dir, utils::SOLC_EXTENSIONS)
155 }
156
157 pub fn read_all_files(files: Vec<PathBuf>) -> Result<Sources, SolcIoError> {
161 Self::read_all(files)
162 }
163
164 pub fn read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
166 where
167 I: IntoIterator<Item = T>,
168 T: Into<PathBuf>,
169 {
170 files
171 .into_iter()
172 .map(Into::into)
173 .map(|file| Self::read(&file).map(|source| (file, source)))
174 .collect()
175 }
176
177 #[cfg(feature = "rayon")]
182 pub fn par_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
183 where
184 I: IntoIterator<Item = T>,
185 <I as IntoIterator>::IntoIter: Send,
186 T: Into<PathBuf> + Send,
187 {
188 use rayon::{iter::ParallelBridge, prelude::ParallelIterator};
189 files
190 .into_iter()
191 .par_bridge()
192 .map(Into::into)
193 .map(|file| Self::read(&file).map(|source| (file, source)))
194 .collect::<Result<BTreeMap<_, _>, _>>()
195 .map(Sources)
196 }
197
198 #[cfg(feature = "checksum")]
200 pub fn content_hash(&self) -> String {
201 alloy_primitives::hex::encode(<md5::Md5 as md5::Digest>::digest(self.content.as_bytes()))
202 }
203}
204
205#[cfg(feature = "async")]
206impl Source {
207 #[instrument(name = "async_read_source", level = "debug", skip_all, err)]
209 pub async fn async_read(file: &Path) -> Result<Self, SolcIoError> {
210 let mut content =
211 tokio::fs::read_to_string(file).await.map_err(|err| SolcIoError::new(err, file))?;
212
213 if content.contains('\r') {
215 content = content.replace("\r\n", "\n");
216 }
217
218 Ok(Self::new(content))
219 }
220
221 #[cfg(feature = "walkdir")]
223 pub async fn async_read_all_from(
224 dir: &Path,
225 extensions: &[&str],
226 ) -> Result<Sources, SolcIoError> {
227 Self::async_read_all(utils::source_files(dir, extensions)).await
228 }
229
230 pub async fn async_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
232 where
233 I: IntoIterator<Item = T>,
234 T: Into<PathBuf>,
235 {
236 futures_util::future::join_all(
237 files
238 .into_iter()
239 .map(Into::into)
240 .map(|file| async { Self::async_read(&file).await.map(|source| (file, source)) }),
241 )
242 .await
243 .into_iter()
244 .collect()
245 }
246}
247
248impl AsRef<str> for Source {
249 fn as_ref(&self) -> &str {
250 &self.content
251 }
252}
253
254impl AsRef<[u8]> for Source {
255 fn as_ref(&self) -> &[u8] {
256 self.content.as_bytes()
257 }
258}
259
260#[derive(Clone, Debug, Default, PartialEq, Eq)]
262pub enum SourceCompilationKind {
263 #[default]
265 Complete,
266 Optimized,
269}
270
271impl SourceCompilationKind {
272 pub fn is_dirty(&self) -> bool {
274 matches!(self, Self::Complete)
275 }
276}