foundry_compilers_artifacts_solc/
sources.rs1use foundry_compilers_core::error::{SolcError, 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 make_absolute(&mut self, root: &Path) {
27 self.0 = std::mem::take(&mut self.0)
28 .into_iter()
29 .map(|(path, source)| (root.join(path), source))
30 .collect();
31 }
32
33 pub fn all_dirty(&self) -> bool {
35 self.0.values().all(|s| s.is_dirty())
36 }
37
38 pub fn dirty(&self) -> impl Iterator<Item = (&PathBuf, &Source)> + '_ {
40 self.0.iter().filter(|(_, s)| s.is_dirty())
41 }
42
43 pub fn clean(&self) -> impl Iterator<Item = (&PathBuf, &Source)> + '_ {
45 self.0.iter().filter(|(_, s)| !s.is_dirty())
46 }
47
48 pub fn dirty_files(&self) -> impl Iterator<Item = &PathBuf> + '_ {
50 self.dirty().map(|(k, _)| k)
51 }
52}
53
54impl std::ops::Deref for Sources {
55 type Target = SourcesInner;
56
57 fn deref(&self) -> &Self::Target {
58 &self.0
59 }
60}
61
62impl std::ops::DerefMut for Sources {
63 fn deref_mut(&mut self) -> &mut Self::Target {
64 &mut self.0
65 }
66}
67
68impl<I> From<I> for Sources
69where
70 SourcesInner: From<I>,
71{
72 fn from(value: I) -> Self {
73 Self(From::from(value))
74 }
75}
76
77impl<I> FromIterator<I> for Sources
78where
79 SourcesInner: FromIterator<I>,
80{
81 fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
82 Self(FromIterator::from_iter(iter))
83 }
84}
85
86impl IntoIterator for Sources {
87 type Item = <SourcesInner as IntoIterator>::Item;
88 type IntoIter = <SourcesInner as IntoIterator>::IntoIter;
89
90 fn into_iter(self) -> Self::IntoIter {
91 self.0.into_iter()
92 }
93}
94
95impl<'a> IntoIterator for &'a Sources {
96 type Item = <&'a SourcesInner as IntoIterator>::Item;
97 type IntoIter = <&'a SourcesInner as IntoIterator>::IntoIter;
98
99 fn into_iter(self) -> Self::IntoIter {
100 self.0.iter()
101 }
102}
103
104impl<'a> IntoIterator for &'a mut Sources {
105 type Item = <&'a mut SourcesInner as IntoIterator>::Item;
106 type IntoIter = <&'a mut SourcesInner as IntoIterator>::IntoIter;
107
108 fn into_iter(self) -> Self::IntoIter {
109 self.0.iter_mut()
110 }
111}
112
113#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
117pub struct Source {
118 pub content: Arc<String>,
124 #[serde(skip, default)]
125 pub kind: SourceCompilationKind,
126}
127
128impl Source {
129 pub fn new(content: impl Into<String>) -> Self {
131 Self { content: Arc::new(content.into()), kind: SourceCompilationKind::Complete }
132 }
133
134 #[instrument(name = "Source::read", skip_all, err)]
136 pub fn read(file: &Path) -> Result<Self, SolcIoError> {
137 trace!(file=%file.display());
138 let mut content = fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))?;
139
140 if content.contains('\r') {
142 content = content.replace("\r\n", "\n");
143 }
144
145 Ok(Self::new(content))
146 }
147
148 pub fn read_(file: &Path) -> Result<Self, SolcError> {
150 Self::read(file).map_err(|err| {
151 let exists = err.path().exists();
152 if !exists && err.path().is_symlink() {
153 return SolcError::ResolveBadSymlink(err);
154 }
155
156 #[cfg(feature = "walkdir")]
160 if !exists
161 && let Some(existing_file) =
162 foundry_compilers_core::utils::find_case_sensitive_existing_file(file)
163 {
164 return SolcError::ResolveCaseSensitiveFileName { error: err, existing_file };
165 }
166
167 SolcError::Resolve(err)
168 })
169 }
170
171 pub const fn is_dirty(&self) -> bool {
173 self.kind.is_dirty()
174 }
175
176 #[cfg(feature = "walkdir")]
178 pub fn read_all_from(dir: &Path, extensions: &[&str]) -> Result<Sources, SolcIoError> {
179 Self::read_all(utils::source_files_iter(dir, extensions))
180 }
181
182 #[cfg(feature = "walkdir")]
184 pub fn read_sol_yul_from(dir: &Path) -> Result<Sources, SolcIoError> {
185 Self::read_all_from(dir, utils::SOLC_EXTENSIONS)
186 }
187
188 pub fn read_all_files(files: Vec<PathBuf>) -> Result<Sources, SolcIoError> {
190 Self::read_all(files)
191 }
192
193 #[instrument(name = "Source::read_all", skip_all)]
195 pub fn read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
196 where
197 I: IntoIterator<Item = T>,
198 T: Into<PathBuf>,
199 {
200 files
201 .into_iter()
202 .map(Into::into)
203 .map(|file| Self::read(&file).map(|source| (file, source)))
204 .collect()
205 }
206
207 #[cfg(feature = "rayon")]
212 pub fn par_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
213 where
214 I: IntoIterator<Item = T>,
215 <I as IntoIterator>::IntoIter: Send,
216 T: Into<PathBuf> + Send,
217 {
218 use rayon::{iter::ParallelBridge, prelude::ParallelIterator};
219 files
220 .into_iter()
221 .par_bridge()
222 .map(Into::into)
223 .map(|file| Self::read(&file).map(|source| (file, source)))
224 .collect::<Result<BTreeMap<_, _>, _>>()
225 .map(Sources)
226 }
227
228 #[cfg(feature = "checksum")]
230 pub fn content_hash(&self) -> String {
231 Self::content_hash_of(&self.content)
232 }
233
234 #[cfg(feature = "checksum")]
236 pub fn content_hash_of(src: &str) -> String {
237 foundry_compilers_core::utils::unique_hash(src)
238 }
239}
240
241#[cfg(feature = "async")]
242impl Source {
243 #[instrument(name = "Source::async_read", skip_all, err)]
245 pub async fn async_read(file: &Path) -> Result<Self, SolcIoError> {
246 let mut content =
247 tokio::fs::read_to_string(file).await.map_err(|err| SolcIoError::new(err, file))?;
248
249 if content.contains('\r') {
251 content = content.replace("\r\n", "\n");
252 }
253
254 Ok(Self::new(content))
255 }
256
257 #[cfg(feature = "walkdir")]
259 pub async fn async_read_all_from(
260 dir: &Path,
261 extensions: &[&str],
262 ) -> Result<Sources, SolcIoError> {
263 Self::async_read_all(utils::source_files(dir, extensions)).await
264 }
265
266 pub async fn async_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
268 where
269 I: IntoIterator<Item = T>,
270 T: Into<PathBuf>,
271 {
272 futures_util::future::join_all(
273 files
274 .into_iter()
275 .map(Into::into)
276 .map(|file| async { Self::async_read(&file).await.map(|source| (file, source)) }),
277 )
278 .await
279 .into_iter()
280 .collect()
281 }
282}
283
284impl AsRef<str> for Source {
285 fn as_ref(&self) -> &str {
286 &self.content
287 }
288}
289
290impl AsRef<[u8]> for Source {
291 fn as_ref(&self) -> &[u8] {
292 self.content.as_bytes()
293 }
294}
295
296#[derive(Clone, Debug, Default, PartialEq, Eq)]
298pub enum SourceCompilationKind {
299 #[default]
301 Complete,
302 Optimized,
305}
306
307impl SourceCompilationKind {
308 pub const fn is_dirty(&self) -> bool {
310 matches!(self, Self::Complete)
311 }
312}