foundry_compilers/compilers/
mod.rs1use crate::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 semver::{Version, VersionReq};
13use serde::{de::DeserializeOwned, Deserialize, Serialize};
14use std::{
15 borrow::Cow,
16 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
17 fmt::{Debug, Display},
18 hash::Hash,
19 path::{Path, PathBuf},
20 sync::{Mutex, OnceLock},
21};
22
23pub mod multi;
24pub mod solc;
25pub mod vyper;
26pub use vyper::*;
27
28mod restrictions;
29pub use restrictions::{CompilerSettingsRestrictions, RestrictionsWithVersion};
30
31#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum CompilerVersion {
36 Installed(Version),
37 Remote(Version),
38}
39
40impl CompilerVersion {
41 pub fn is_installed(&self) -> bool {
42 matches!(self, Self::Installed(_))
43 }
44}
45
46impl AsRef<Version> for CompilerVersion {
47 fn as_ref(&self) -> &Version {
48 match self {
49 Self::Installed(v) | Self::Remote(v) => v,
50 }
51 }
52}
53
54impl From<CompilerVersion> for Version {
55 fn from(s: CompilerVersion) -> Self {
56 match s {
57 CompilerVersion::Installed(v) | CompilerVersion::Remote(v) => v,
58 }
59 }
60}
61
62impl fmt::Display for CompilerVersion {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(f, "{}", self.as_ref())
65 }
66}
67
68pub trait CompilerSettings:
70 Default + Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static
71{
72 type Restrictions: CompilerSettingsRestrictions;
75
76 fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy);
78
79 fn can_use_cached(&self, other: &Self) -> bool;
85
86 fn with_remappings(self, _remappings: &[Remapping]) -> Self {
88 self
89 }
90
91 fn with_base_path(self, _base_path: &Path) -> Self {
94 self
95 }
96
97 fn with_allow_paths(self, _allowed_paths: &BTreeSet<PathBuf>) -> Self {
100 self
101 }
102
103 fn with_include_paths(self, _include_paths: &BTreeSet<PathBuf>) -> Self {
106 self
107 }
108
109 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool;
111}
112
113pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
115 type Settings: CompilerSettings;
116 type Language: Language;
117
118 fn build(
121 sources: Sources,
122 settings: Self::Settings,
123 language: Self::Language,
124 version: Version,
125 ) -> Self;
126
127 fn language(&self) -> Self::Language;
129
130 fn version(&self) -> &Version;
132
133 fn sources(&self) -> impl Iterator<Item = (&Path, &Source)>;
134
135 fn compiler_name(&self) -> Cow<'static, str>;
137
138 fn strip_prefix(&mut self, base: &Path);
140}
141
142pub trait ParsedSource: Debug + Sized + Send + Clone {
148 type Language: Language;
149
150 fn parse(content: &str, file: &Path) -> Result<Self>;
152
153 fn version_req(&self) -> Option<&VersionReq>;
155
156 fn contract_names(&self) -> &[String];
158
159 fn language(&self) -> Self::Language;
161
162 fn resolve_imports<C>(
165 &self,
166 paths: &ProjectPathsConfig<C>,
167 include_paths: &mut BTreeSet<PathBuf>,
168 ) -> Result<Vec<PathBuf>>;
169
170 fn compilation_dependencies<'a>(
181 &self,
182 _imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
183 ) -> impl Iterator<Item = &'a Path>
184 where
185 Self: 'a,
186 {
187 vec![].into_iter()
188 }
189}
190
191pub trait CompilationError:
193 Serialize + Send + Sync + Display + Debug + Clone + PartialEq + Eq + 'static
194{
195 fn is_warning(&self) -> bool;
196 fn is_error(&self) -> bool;
197 fn source_location(&self) -> Option<SourceLocation>;
198 fn severity(&self) -> Severity;
199 fn error_code(&self) -> Option<u64>;
200}
201
202#[derive(Debug, Serialize, Deserialize)]
205pub struct CompilerOutput<E, C> {
206 #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
207 pub errors: Vec<E>,
208 #[serde(default = "BTreeMap::new")]
209 pub contracts: FileToContractsMap<C>,
210 #[serde(default)]
211 pub sources: BTreeMap<PathBuf, SourceFile>,
212 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
213 pub metadata: BTreeMap<String, serde_json::Value>,
214}
215
216impl<E, C> CompilerOutput<E, C> {
217 pub fn retain_files<F, I>(&mut self, files: I)
221 where
222 F: AsRef<Path>,
223 I: IntoIterator<Item = F>,
224 {
225 let files: HashSet<_> =
228 files.into_iter().map(|s| s.as_ref().to_string_lossy().to_lowercase()).collect();
229 self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
230 self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
231 }
232
233 pub fn merge(&mut self, other: Self) {
234 self.errors.extend(other.errors);
235 self.contracts.extend(other.contracts);
236 self.sources.extend(other.sources);
237 }
238
239 pub fn join_all(&mut self, root: &Path) {
240 self.contracts = std::mem::take(&mut self.contracts)
241 .into_iter()
242 .map(|(path, contracts)| (root.join(path), contracts))
243 .collect();
244 self.sources = std::mem::take(&mut self.sources)
245 .into_iter()
246 .map(|(path, source)| (root.join(path), source))
247 .collect();
248 }
249
250 pub fn map_err<F, O: FnMut(E) -> F>(self, op: O) -> CompilerOutput<F, C> {
251 CompilerOutput {
252 errors: self.errors.into_iter().map(op).collect(),
253 contracts: self.contracts,
254 sources: self.sources,
255 metadata: self.metadata,
256 }
257 }
258}
259
260impl<E, C> Default for CompilerOutput<E, C> {
261 fn default() -> Self {
262 Self {
263 errors: Vec::new(),
264 contracts: BTreeMap::new(),
265 sources: BTreeMap::new(),
266 metadata: BTreeMap::new(),
267 }
268 }
269}
270
271pub trait Language:
273 Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
274{
275 const FILE_EXTENSIONS: &'static [&'static str];
277}
278
279pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
281 fn abi_ref(&self) -> Option<&JsonAbi>;
283
284 fn bin_ref(&self) -> Option<&BytecodeObject>;
286
287 fn bin_runtime_ref(&self) -> Option<&BytecodeObject>;
289
290 fn as_compact_contract_ref(&self) -> CompactContractRef<'_> {
291 CompactContractRef {
292 abi: self.abi_ref(),
293 bin: self.bin_ref(),
294 bin_runtime: self.bin_runtime_ref(),
295 }
296 }
297}
298
299impl CompilerContract for Contract {
300 fn abi_ref(&self) -> Option<&JsonAbi> {
301 self.abi.as_ref()
302 }
303 fn bin_ref(&self) -> Option<&BytecodeObject> {
304 if let Some(ref evm) = self.evm {
305 evm.bytecode.as_ref().map(|c| &c.object)
306 } else {
307 None
308 }
309 }
310 fn bin_runtime_ref(&self) -> Option<&BytecodeObject> {
311 if let Some(ref evm) = self.evm {
312 evm.deployed_bytecode
313 .as_ref()
314 .and_then(|deployed| deployed.bytecode.as_ref().map(|evm| &evm.object))
315 } else {
316 None
317 }
318 }
319}
320
321#[auto_impl::auto_impl(&, Box, Arc)]
326pub trait Compiler: Send + Sync + Clone {
327 type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
329 type CompilationError: CompilationError;
331 type CompilerContract: CompilerContract;
333 type ParsedSource: ParsedSource<Language = Self::Language>;
335 type Settings: CompilerSettings;
337 type Language: Language;
339
340 fn compile(
344 &self,
345 input: &Self::Input,
346 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
347
348 fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion>;
351}
352
353pub(crate) fn cache_version(
354 path: PathBuf,
355 args: &[String],
356 f: impl FnOnce(&Path) -> Result<Version>,
357) -> Result<Version> {
358 #[allow(clippy::complexity)]
359 static VERSION_CACHE: OnceLock<Mutex<HashMap<PathBuf, HashMap<Vec<String>, Version>>>> =
360 OnceLock::new();
361 let mut lock = VERSION_CACHE
362 .get_or_init(|| Mutex::new(HashMap::new()))
363 .lock()
364 .unwrap_or_else(std::sync::PoisonError::into_inner);
365
366 if let Some(version) = lock.get(&path).and_then(|versions| versions.get(args)) {
367 return Ok(version.clone());
368 }
369
370 let version = f(&path)?;
371
372 lock.entry(path).or_default().insert(args.to_vec(), version.clone());
373
374 Ok(version)
375}