foundry_compilers/compilers/
mod.rs

1use 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/// A compiler version is either installed (available locally) or can be downloaded, from the remote
32/// endpoint
33#[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
68/// Compilation settings including evm_version, output_selection, etc.
69pub trait CompilerSettings:
70    Default + Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static
71{
72    /// We allow configuring settings restrictions which might optionally contain specific
73    /// requiremets for compiler configuration. e.g. min/max evm_version, optimizer runs
74    type Restrictions: CompilerSettingsRestrictions;
75
76    /// Executes given fn with mutable reference to configured [OutputSelection].
77    fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy);
78
79    /// Returns true if artifacts compiled with given `other` config are compatible with this
80    /// config and if compilation can be skipped.
81    ///
82    /// Ensures that all settings fields are equal except for `output_selection` which is required
83    /// to be a subset of `cached.output_selection`.
84    fn can_use_cached(&self, other: &Self) -> bool;
85
86    /// Method which might be invoked to add remappings to the input.
87    fn with_remappings(self, _remappings: &[Remapping]) -> Self {
88        self
89    }
90
91    /// Builder method to set the base path for the compiler. Primarily used by solc implementation
92    /// to se --base-path.
93    fn with_base_path(self, _base_path: &Path) -> Self {
94        self
95    }
96
97    /// Builder method to set the allowed paths for the compiler. Primarily used by solc
98    /// implementation to set --allow-paths.
99    fn with_allow_paths(self, _allowed_paths: &BTreeSet<PathBuf>) -> Self {
100        self
101    }
102
103    /// Builder method to set the include paths for the compiler. Primarily used by solc
104    /// implementation to set --include-paths.
105    fn with_include_paths(self, _include_paths: &BTreeSet<PathBuf>) -> Self {
106        self
107    }
108
109    /// Returns whether current settings satisfy given restrictions.
110    fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool;
111}
112
113/// Input of a compiler, including sources and settings used for their compilation.
114pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
115    type Settings: CompilerSettings;
116    type Language: Language;
117
118    /// Constructs one or multiple inputs from given sources set. Might return multiple inputs in
119    /// cases when sources need to be divided into sets per language (Yul + Solidity for example).
120    fn build(
121        sources: Sources,
122        settings: Self::Settings,
123        language: Self::Language,
124        version: Version,
125    ) -> Self;
126
127    /// Returns language of the sources included into this input.
128    fn language(&self) -> Self::Language;
129
130    /// Returns compiler version for which this input is intended.
131    fn version(&self) -> &Version;
132
133    fn sources(&self) -> impl Iterator<Item = (&Path, &Source)>;
134
135    /// Returns compiler name used by reporters to display output during compilation.
136    fn compiler_name(&self) -> Cow<'static, str>;
137
138    /// Strips given prefix from all paths.
139    fn strip_prefix(&mut self, base: &Path);
140}
141
142/// Parser of the source files which is used to identify imports and version requirements of the
143/// given source.
144///
145/// Used by path resolver to resolve imports or determine compiler versions needed to compiler given
146/// sources.
147pub trait ParsedSource: Debug + Sized + Send + Clone {
148    type Language: Language;
149
150    /// Parses the content of the source file.
151    fn parse(content: &str, file: &Path) -> Result<Self>;
152
153    /// Returns the version requirement of the source.
154    fn version_req(&self) -> Option<&VersionReq>;
155
156    /// Returns a list of contract names defined in the source.
157    fn contract_names(&self) -> &[String];
158
159    /// Returns the language of the source.
160    fn language(&self) -> Self::Language;
161
162    /// Invoked during import resolution. Should resolve imports for the given source, and populate
163    /// include_paths for compilers which support this config.
164    fn resolve_imports<C>(
165        &self,
166        paths: &ProjectPathsConfig<C>,
167        include_paths: &mut BTreeSet<PathBuf>,
168    ) -> Result<Vec<PathBuf>>;
169
170    /// Used to configure [OutputSelection] for sparse builds. In certain cases, we might want to
171    /// include some of the file dependencies into the compiler output even if we might not be
172    /// directly interested in them.
173    ///
174    /// Example of such case is when we are compiling Solidity file containing link references and
175    /// need them to be included in the output to deploy needed libraries.
176    ///
177    /// Receives iterator over imports of the current source.
178    ///
179    /// Returns iterator over paths to the files that should be compiled with full output selection.
180    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
191/// Error returned by compiler. Might also represent a warning or informational message.
192pub 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/// Output of the compiler, including contracts, sources, errors and metadata. might be
203/// extended to be more generic in the future.
204#[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    /// Retains only those files the given iterator yields
218    ///
219    /// In other words, removes all contracts for files not included in the iterator
220    pub fn retain_files<F, I>(&mut self, files: I)
221    where
222        F: AsRef<Path>,
223        I: IntoIterator<Item = F>,
224    {
225        // Note: use `to_lowercase` here because solc not necessarily emits the exact file name,
226        // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol`
227        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
271/// Keeps a set of languages recognized by the compiler.
272pub trait Language:
273    Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
274{
275    /// Extensions of source files recognized by the language set.
276    const FILE_EXTENSIONS: &'static [&'static str];
277}
278
279/// Represents a compiled contract
280pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
281    /// Reference to contract ABI
282    fn abi_ref(&self) -> Option<&JsonAbi>;
283
284    //// Reference to contract bytecode
285    fn bin_ref(&self) -> Option<&BytecodeObject>;
286
287    //// Reference to contract runtime bytecode
288    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/// The main compiler abstraction trait.
322///
323/// Currently mostly represents a wrapper around compiler binary aware of the version and able to
324/// compile given input into [`CompilerOutput`] including artifacts and errors.
325#[auto_impl::auto_impl(&, Box, Arc)]
326pub trait Compiler: Send + Sync + Clone {
327    /// Input type for the compiler. Contains settings and sources to be compiled.
328    type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
329    /// Error type returned by the compiler.
330    type CompilationError: CompilationError;
331    /// Output data for each contract
332    type CompilerContract: CompilerContract;
333    /// Source parser used for resolving imports and version requirements.
334    type ParsedSource: ParsedSource<Language = Self::Language>;
335    /// Compiler settings.
336    type Settings: CompilerSettings;
337    /// Enum of languages supported by the compiler.
338    type Language: Language;
339
340    /// Main entrypoint for the compiler. Compiles given input into [CompilerOutput]. Takes
341    /// ownership over the input and returns back version with potential modifications made to it.
342    /// Returned input is always the one which was seen by the binary.
343    fn compile(
344        &self,
345        input: &Self::Input,
346    ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
347
348    /// Returns all versions available locally and remotely. Should return versions with stripped
349    /// metadata.
350    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}