foundry_compilers/compilers/
mod.rs1use crate::{resolver::Node, ProjectPathsConfig};
2use alloy_json_abi::JsonAbi;
3use core::fmt;
4use foundry_compilers_artifacts::{
5 error::SourceLocation,
6 output_selection::OutputSelection,
7 remappings::Remapping,
8 sources::{Source, Sources},
9 BytecodeObject, CompactContractRef, Contract, FileToContractsMap, Severity, SourceFile,
10};
11use foundry_compilers_core::error::Result;
12use rayon::prelude::*;
13use semver::{Version, VersionReq};
14use serde::{de::DeserializeOwned, Deserialize, Serialize};
15use std::{
16 borrow::Cow,
17 collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
18 fmt::{Debug, Display},
19 hash::Hash,
20 path::{Path, PathBuf},
21 sync::{Mutex, OnceLock},
22};
23
24pub mod multi;
25pub mod solc;
26pub mod vyper;
27pub use vyper::*;
28
29mod restrictions;
30pub use restrictions::{CompilerSettingsRestrictions, RestrictionsWithVersion};
31
32#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
35#[serde(untagged)]
36pub enum CompilerVersion {
37 Installed(Version),
38 Remote(Version),
39}
40
41impl CompilerVersion {
42 pub fn is_installed(&self) -> bool {
43 matches!(self, Self::Installed(_))
44 }
45}
46
47impl AsRef<Version> for CompilerVersion {
48 fn as_ref(&self) -> &Version {
49 match self {
50 Self::Installed(v) | Self::Remote(v) => v,
51 }
52 }
53}
54
55impl From<CompilerVersion> for Version {
56 fn from(s: CompilerVersion) -> Self {
57 match s {
58 CompilerVersion::Installed(v) | CompilerVersion::Remote(v) => v,
59 }
60 }
61}
62
63impl fmt::Display for CompilerVersion {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.as_ref())
66 }
67}
68
69pub trait CompilerSettings:
71 Default + Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static
72{
73 type Restrictions: CompilerSettingsRestrictions;
76
77 fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy);
79
80 fn can_use_cached(&self, other: &Self) -> bool;
86
87 fn with_remappings(self, _remappings: &[Remapping]) -> Self {
89 self
90 }
91
92 fn with_base_path(self, _base_path: &Path) -> Self {
95 self
96 }
97
98 fn with_allow_paths(self, _allowed_paths: &BTreeSet<PathBuf>) -> Self {
101 self
102 }
103
104 fn with_include_paths(self, _include_paths: &BTreeSet<PathBuf>) -> Self {
107 self
108 }
109
110 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool;
112}
113
114pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
116 type Settings: CompilerSettings;
117 type Language: Language;
118
119 fn build(
122 sources: Sources,
123 settings: Self::Settings,
124 language: Self::Language,
125 version: Version,
126 ) -> Self;
127
128 fn language(&self) -> Self::Language;
130
131 fn version(&self) -> &Version;
133
134 fn sources(&self) -> impl Iterator<Item = (&Path, &Source)>;
135
136 fn compiler_name(&self) -> Cow<'static, str>;
138
139 fn strip_prefix(&mut self, base: &Path);
141}
142
143pub trait SourceParser: Clone + Debug + Send + Sync {
145 type ParsedSource: ParsedSource;
146
147 fn new(config: &ProjectPathsConfig) -> Self;
149
150 fn read(&mut self, path: &Path) -> Result<Node<Self::ParsedSource>> {
152 Node::read(path)
153 }
154
155 fn parse_sources(
157 &mut self,
158 sources: &mut Sources,
159 ) -> Result<Vec<(PathBuf, Node<Self::ParsedSource>)>> {
160 sources
161 .0
162 .par_iter()
163 .map(|(path, source)| {
164 let data = Self::ParsedSource::parse(source.as_ref(), path)?;
165 Ok((path.clone(), Node::new(path.clone(), source.clone(), data)))
166 })
167 .collect::<Result<_>>()
168 }
169}
170
171pub trait ParsedSource: Clone + Debug + Sized + Send {
177 type Language: Language;
178
179 fn parse(content: &str, file: &Path) -> Result<Self>;
181
182 fn version_req(&self) -> Option<&VersionReq>;
184
185 fn contract_names(&self) -> &[String];
187
188 fn language(&self) -> Self::Language;
190
191 fn resolve_imports<C>(
194 &self,
195 paths: &ProjectPathsConfig<C>,
196 include_paths: &mut BTreeSet<PathBuf>,
197 ) -> Result<Vec<PathBuf>>;
198
199 fn compilation_dependencies<'a>(
210 &self,
211 _imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
212 ) -> impl Iterator<Item = &'a Path>
213 where
214 Self: 'a,
215 {
216 vec![].into_iter()
217 }
218}
219
220pub trait CompilationError:
222 Serialize + Send + Sync + Display + Debug + Clone + PartialEq + Eq + 'static
223{
224 fn is_warning(&self) -> bool;
225 fn is_error(&self) -> bool;
226 fn source_location(&self) -> Option<SourceLocation>;
227 fn severity(&self) -> Severity;
228 fn error_code(&self) -> Option<u64>;
229}
230
231#[derive(Debug, Serialize, Deserialize)]
234pub struct CompilerOutput<E, C> {
235 #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
236 pub errors: Vec<E>,
237 #[serde(default = "BTreeMap::new")]
238 pub contracts: FileToContractsMap<C>,
239 #[serde(default)]
240 pub sources: BTreeMap<PathBuf, SourceFile>,
241 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
242 pub metadata: BTreeMap<String, serde_json::Value>,
243}
244
245impl<E, C> CompilerOutput<E, C> {
246 pub fn retain_files<F, I>(&mut self, files: I)
250 where
251 F: AsRef<Path>,
252 I: IntoIterator<Item = F>,
253 {
254 let files: HashSet<_> =
257 files.into_iter().map(|s| s.as_ref().to_string_lossy().to_lowercase()).collect();
258 self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
259 self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
260 }
261
262 pub fn merge(&mut self, other: Self) {
263 self.errors.extend(other.errors);
264 self.contracts.extend(other.contracts);
265 self.sources.extend(other.sources);
266 }
267
268 pub fn join_all(&mut self, root: &Path) {
269 self.contracts = std::mem::take(&mut self.contracts)
270 .into_iter()
271 .map(|(path, contracts)| (root.join(path), contracts))
272 .collect();
273 self.sources = std::mem::take(&mut self.sources)
274 .into_iter()
275 .map(|(path, source)| (root.join(path), source))
276 .collect();
277 }
278
279 pub fn map_err<F, O: FnMut(E) -> F>(self, op: O) -> CompilerOutput<F, C> {
280 CompilerOutput {
281 errors: self.errors.into_iter().map(op).collect(),
282 contracts: self.contracts,
283 sources: self.sources,
284 metadata: self.metadata,
285 }
286 }
287}
288
289impl<E, C> Default for CompilerOutput<E, C> {
290 fn default() -> Self {
291 Self {
292 errors: Vec::new(),
293 contracts: BTreeMap::new(),
294 sources: BTreeMap::new(),
295 metadata: BTreeMap::new(),
296 }
297 }
298}
299
300pub trait Language:
302 Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
303{
304 const FILE_EXTENSIONS: &'static [&'static str];
306}
307
308pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
310 fn abi_ref(&self) -> Option<&JsonAbi>;
312
313 fn bin_ref(&self) -> Option<&BytecodeObject>;
315
316 fn bin_runtime_ref(&self) -> Option<&BytecodeObject>;
318
319 fn as_compact_contract_ref(&self) -> CompactContractRef<'_> {
320 CompactContractRef {
321 abi: self.abi_ref(),
322 bin: self.bin_ref(),
323 bin_runtime: self.bin_runtime_ref(),
324 }
325 }
326}
327
328impl CompilerContract for Contract {
329 fn abi_ref(&self) -> Option<&JsonAbi> {
330 self.abi.as_ref()
331 }
332 fn bin_ref(&self) -> Option<&BytecodeObject> {
333 if let Some(ref evm) = self.evm {
334 evm.bytecode.as_ref().map(|c| &c.object)
335 } else {
336 None
337 }
338 }
339 fn bin_runtime_ref(&self) -> Option<&BytecodeObject> {
340 if let Some(ref evm) = self.evm {
341 evm.deployed_bytecode
342 .as_ref()
343 .and_then(|deployed| deployed.bytecode.as_ref().map(|evm| &evm.object))
344 } else {
345 None
346 }
347 }
348}
349
350#[auto_impl::auto_impl(&, Box, Arc)]
355pub trait Compiler: Send + Sync + Clone {
356 type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
358 type CompilationError: CompilationError;
360 type CompilerContract: CompilerContract;
362 type Parser: SourceParser<ParsedSource: ParsedSource<Language = Self::Language>>;
364 type Settings: CompilerSettings;
366 type Language: Language;
368
369 fn compile(
373 &self,
374 input: &Self::Input,
375 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
376
377 fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion>;
380}
381
382pub(crate) fn cache_version(
383 path: PathBuf,
384 args: &[String],
385 f: impl FnOnce(&Path) -> Result<Version>,
386) -> Result<Version> {
387 #[allow(clippy::complexity)]
388 static VERSION_CACHE: OnceLock<Mutex<HashMap<(PathBuf, Vec<String>), Version>>> =
389 OnceLock::new();
390
391 let mut cache = VERSION_CACHE
392 .get_or_init(Default::default)
393 .lock()
394 .unwrap_or_else(std::sync::PoisonError::into_inner);
395
396 match cache.entry((path, args.to_vec())) {
397 Entry::Occupied(entry) => Ok(entry.get().clone()),
398 Entry::Vacant(entry) => {
399 let path = &entry.key().0;
400 let _guard =
401 debug_span!("get_version", path = %path.file_name().map(|n| n.to_string_lossy()).unwrap_or_else(|| path.to_string_lossy()))
402 .entered();
403 let version = f(path)?;
404 entry.insert(version.clone());
405 Ok(version)
406 }
407 }
408}