foundry_compilers/compilers/
mod.rs

1use 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/// A compiler version is either installed (available locally) or can be downloaded, from the remote
33/// endpoint
34#[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
69/// Compilation settings including evm_version, output_selection, etc.
70pub trait CompilerSettings:
71    Default + Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static
72{
73    /// We allow configuring settings restrictions which might optionally contain specific
74    /// requiremets for compiler configuration. e.g. min/max evm_version, optimizer runs
75    type Restrictions: CompilerSettingsRestrictions;
76
77    /// Executes given fn with mutable reference to configured [OutputSelection].
78    fn update_output_selection(&mut self, f: impl FnMut(&mut OutputSelection));
79
80    /// Returns true if artifacts compiled with given `other` config are compatible with this
81    /// config and if compilation can be skipped.
82    ///
83    /// Ensures that all settings fields are equal except for `output_selection` which is required
84    /// to be a subset of `cached.output_selection`.
85    fn can_use_cached(&self, other: &Self) -> bool;
86
87    /// Method which might be invoked to add remappings to the input.
88    fn with_remappings(self, _remappings: &[Remapping]) -> Self {
89        self
90    }
91
92    /// Builder method to set the base path for the compiler. Primarily used by solc implementation
93    /// to se --base-path.
94    fn with_base_path(self, _base_path: &Path) -> Self {
95        self
96    }
97
98    /// Builder method to set the allowed paths for the compiler. Primarily used by solc
99    /// implementation to set --allow-paths.
100    fn with_allow_paths(self, _allowed_paths: &BTreeSet<PathBuf>) -> Self {
101        self
102    }
103
104    /// Builder method to set the include paths for the compiler. Primarily used by solc
105    /// implementation to set --include-paths.
106    fn with_include_paths(self, _include_paths: &BTreeSet<PathBuf>) -> Self {
107        self
108    }
109
110    /// Returns whether current settings satisfy given restrictions.
111    fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool;
112}
113
114/// Input of a compiler, including sources and settings used for their compilation.
115pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
116    type Settings: CompilerSettings;
117    type Language: Language;
118
119    /// Constructs one or multiple inputs from given sources set. Might return multiple inputs in
120    /// cases when sources need to be divided into sets per language (Yul + Solidity for example).
121    fn build(
122        sources: Sources,
123        settings: Self::Settings,
124        language: Self::Language,
125        version: Version,
126    ) -> Self;
127
128    /// Returns language of the sources included into this input.
129    fn language(&self) -> Self::Language;
130
131    /// Returns compiler version for which this input is intended.
132    fn version(&self) -> &Version;
133
134    fn sources(&self) -> impl Iterator<Item = (&Path, &Source)>;
135
136    /// Returns compiler name used by reporters to display output during compilation.
137    fn compiler_name(&self) -> Cow<'static, str>;
138
139    /// Strips given prefix from all paths.
140    fn strip_prefix(&mut self, base: &Path);
141}
142
143/// [`ParsedSource`] parser.
144pub trait SourceParser: Clone + Debug + Send + Sync {
145    type ParsedSource: ParsedSource;
146
147    /// Creates a new parser for the given config.
148    fn new(config: &ProjectPathsConfig) -> Self;
149
150    /// Reads and parses the source file at the given path.
151    fn read(&mut self, path: &Path) -> Result<Node<Self::ParsedSource>> {
152        Node::read(path)
153    }
154
155    /// Parses the sources in the given sources map.
156    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
179/// Parser of the source files which is used to identify imports and version requirements of the
180/// given source.
181///
182/// Used by path resolver to resolve imports or determine compiler versions needed to compiler given
183/// sources.
184pub trait ParsedSource: Clone + Debug + Sized + Send {
185    type Language: Language;
186
187    /// Parses the content of the source file.
188    fn parse(content: &str, file: &Path) -> Result<Self>;
189
190    /// Returns the version requirement of the source.
191    fn version_req(&self) -> Option<&VersionReq>;
192
193    /// Returns a list of contract names defined in the source.
194    fn contract_names(&self) -> &[String];
195
196    /// Returns the language of the source.
197    fn language(&self) -> Self::Language;
198
199    /// Invoked during import resolution. Should resolve imports for the given source, and populate
200    /// include_paths for compilers which support this config.
201    fn resolve_imports<C>(
202        &self,
203        paths: &ProjectPathsConfig<C>,
204        include_paths: &mut BTreeSet<PathBuf>,
205    ) -> Result<Vec<PathBuf>>;
206
207    /// Used to configure [OutputSelection] for sparse builds. In certain cases, we might want to
208    /// include some of the file dependencies into the compiler output even if we might not be
209    /// directly interested in them.
210    ///
211    /// Example of such case is when we are compiling Solidity file containing link references and
212    /// need them to be included in the output to deploy needed libraries.
213    ///
214    /// Receives iterator over imports of the current source.
215    ///
216    /// Returns iterator over paths to the files that should be compiled with full output selection.
217    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
228/// Error returned by compiler. Might also represent a warning or informational message.
229pub 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/// Output of the compiler, including contracts, sources, errors and metadata. might be
240/// extended to be more generic in the future.
241#[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    /// Retains only those files the given iterator yields
255    ///
256    /// In other words, removes all contracts for files not included in the iterator
257    pub fn retain_files<F, I>(&mut self, files: I)
258    where
259        F: AsRef<Path>,
260        I: IntoIterator<Item = F>,
261    {
262        // Note: use `to_lowercase` here because solc not necessarily emits the exact file name,
263        // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol`
264        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
308/// Keeps a set of languages recognized by the compiler.
309pub trait Language:
310    Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
311{
312    /// Extensions of source files recognized by the language set.
313    const FILE_EXTENSIONS: &'static [&'static str];
314}
315
316/// Represents a compiled contract
317pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
318    /// Reference to contract ABI
319    fn abi_ref(&self) -> Option<&JsonAbi>;
320
321    //// Reference to contract bytecode
322    fn bin_ref(&self) -> Option<&BytecodeObject>;
323
324    //// Reference to contract runtime bytecode
325    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/// The main compiler abstraction trait.
359///
360/// Currently mostly represents a wrapper around compiler binary aware of the version and able to
361/// compile given input into [`CompilerOutput`] including artifacts and errors.
362#[auto_impl::auto_impl(&, Box, Arc)]
363pub trait Compiler: Send + Sync + Clone {
364    /// Input type for the compiler. Contains settings and sources to be compiled.
365    type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
366    /// Error type returned by the compiler.
367    type CompilationError: CompilationError;
368    /// Output data for each contract
369    type CompilerContract: CompilerContract;
370    /// Source parser used for resolving imports and version requirements.
371    type Parser: SourceParser<ParsedSource: ParsedSource<Language = Self::Language>>;
372    /// Compiler settings.
373    type Settings: CompilerSettings;
374    /// Enum of languages supported by the compiler.
375    type Language: Language;
376
377    /// Main entrypoint for the compiler. Compiles given input into [CompilerOutput]. Takes
378    /// ownership over the input and returns back version with potential modifications made to it.
379    /// Returned input is always the one which was seen by the binary.
380    fn compile(
381        &self,
382        input: &Self::Input,
383    ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
384
385    /// Returns all versions available locally and remotely. Should return versions with stripped
386    /// metadata.
387    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}