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 FnMut(&mut OutputSelection));
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 fn finalize_imports(
171 &mut self,
172 _nodes: &mut Vec<Node<Self::ParsedSource>>,
173 _include_paths: &BTreeSet<PathBuf>,
174 ) -> Result<()> {
175 Ok(())
176 }
177}
178
179pub trait ParsedSource: Clone + Debug + Sized + Send {
185 type Language: Language;
186
187 fn parse(content: &str, file: &Path) -> Result<Self>;
189
190 fn version_req(&self) -> Option<&VersionReq>;
192
193 fn contract_names(&self) -> &[String];
195
196 fn language(&self) -> Self::Language;
198
199 fn resolve_imports<C>(
202 &self,
203 paths: &ProjectPathsConfig<C>,
204 include_paths: &mut BTreeSet<PathBuf>,
205 ) -> Result<Vec<PathBuf>>;
206
207 fn compilation_dependencies<'a>(
218 &self,
219 _imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
220 ) -> impl Iterator<Item = &'a Path>
221 where
222 Self: 'a,
223 {
224 vec![].into_iter()
225 }
226}
227
228pub trait CompilationError:
230 Serialize + Send + Sync + Display + Debug + Clone + PartialEq + Eq + 'static
231{
232 fn is_warning(&self) -> bool;
233 fn is_error(&self) -> bool;
234 fn source_location(&self) -> Option<SourceLocation>;
235 fn severity(&self) -> Severity;
236 fn error_code(&self) -> Option<u64>;
237}
238
239#[derive(Debug, Serialize, Deserialize)]
242pub struct CompilerOutput<E, C> {
243 #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
244 pub errors: Vec<E>,
245 #[serde(default = "BTreeMap::new")]
246 pub contracts: FileToContractsMap<C>,
247 #[serde(default)]
248 pub sources: BTreeMap<PathBuf, SourceFile>,
249 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
250 pub metadata: BTreeMap<String, serde_json::Value>,
251}
252
253impl<E, C> CompilerOutput<E, C> {
254 pub fn retain_files<F, I>(&mut self, files: I)
258 where
259 F: AsRef<Path>,
260 I: IntoIterator<Item = F>,
261 {
262 let files: HashSet<_> =
265 files.into_iter().map(|s| s.as_ref().to_string_lossy().to_lowercase()).collect();
266 self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
267 self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
268 }
269
270 pub fn merge(&mut self, other: Self) {
271 self.errors.extend(other.errors);
272 self.contracts.extend(other.contracts);
273 self.sources.extend(other.sources);
274 }
275
276 pub fn join_all(&mut self, root: &Path) {
277 self.contracts = std::mem::take(&mut self.contracts)
278 .into_iter()
279 .map(|(path, contracts)| (root.join(path), contracts))
280 .collect();
281 self.sources = std::mem::take(&mut self.sources)
282 .into_iter()
283 .map(|(path, source)| (root.join(path), source))
284 .collect();
285 }
286
287 pub fn map_err<F, O: FnMut(E) -> F>(self, op: O) -> CompilerOutput<F, C> {
288 CompilerOutput {
289 errors: self.errors.into_iter().map(op).collect(),
290 contracts: self.contracts,
291 sources: self.sources,
292 metadata: self.metadata,
293 }
294 }
295}
296
297impl<E, C> Default for CompilerOutput<E, C> {
298 fn default() -> Self {
299 Self {
300 errors: Vec::new(),
301 contracts: BTreeMap::new(),
302 sources: BTreeMap::new(),
303 metadata: BTreeMap::new(),
304 }
305 }
306}
307
308pub trait Language:
310 Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
311{
312 const FILE_EXTENSIONS: &'static [&'static str];
314}
315
316pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
318 fn abi_ref(&self) -> Option<&JsonAbi>;
320
321 fn bin_ref(&self) -> Option<&BytecodeObject>;
323
324 fn bin_runtime_ref(&self) -> Option<&BytecodeObject>;
326
327 fn as_compact_contract_ref(&self) -> CompactContractRef<'_> {
328 CompactContractRef {
329 abi: self.abi_ref(),
330 bin: self.bin_ref(),
331 bin_runtime: self.bin_runtime_ref(),
332 }
333 }
334}
335
336impl CompilerContract for Contract {
337 fn abi_ref(&self) -> Option<&JsonAbi> {
338 self.abi.as_ref()
339 }
340 fn bin_ref(&self) -> Option<&BytecodeObject> {
341 if let Some(ref evm) = self.evm {
342 evm.bytecode.as_ref().map(|c| &c.object)
343 } else {
344 None
345 }
346 }
347 fn bin_runtime_ref(&self) -> Option<&BytecodeObject> {
348 if let Some(ref evm) = self.evm {
349 evm.deployed_bytecode
350 .as_ref()
351 .and_then(|deployed| deployed.bytecode.as_ref().map(|evm| &evm.object))
352 } else {
353 None
354 }
355 }
356}
357
358#[auto_impl::auto_impl(&, Box, Arc)]
363pub trait Compiler: Send + Sync + Clone {
364 type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
366 type CompilationError: CompilationError;
368 type CompilerContract: CompilerContract;
370 type Parser: SourceParser<ParsedSource: ParsedSource<Language = Self::Language>>;
372 type Settings: CompilerSettings;
374 type Language: Language;
376
377 fn compile(
381 &self,
382 input: &Self::Input,
383 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
384
385 fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion>;
388}
389
390pub(crate) fn cache_version(
391 path: PathBuf,
392 args: &[String],
393 f: impl FnOnce(&Path) -> Result<Version>,
394) -> Result<Version> {
395 #[allow(clippy::complexity)]
396 static VERSION_CACHE: OnceLock<Mutex<HashMap<(PathBuf, Vec<String>), Version>>> =
397 OnceLock::new();
398
399 let mut cache = VERSION_CACHE
400 .get_or_init(Default::default)
401 .lock()
402 .unwrap_or_else(std::sync::PoisonError::into_inner);
403
404 match cache.entry((path, args.to_vec())) {
405 Entry::Occupied(entry) => Ok(entry.get().clone()),
406 Entry::Vacant(entry) => {
407 let path = &entry.key().0;
408 let _guard =
409 debug_span!("get_version", path = %path.file_name().map(|n| n.to_string_lossy()).unwrap_or_else(|| path.to_string_lossy()))
410 .entered();
411 let version = f(path)?;
412 entry.insert(version.clone());
413 Ok(version)
414 }
415 }
416}