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 FnOnce(&mut OutputSelection) + Copy);
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
171/// Parser of the source files which is used to identify imports and version requirements of the
172/// given source.
173///
174/// Used by path resolver to resolve imports or determine compiler versions needed to compiler given
175/// sources.
176pub trait ParsedSource: Clone + Debug + Sized + Send {
177    type Language: Language;
178
179    /// Parses the content of the source file.
180    fn parse(content: &str, file: &Path) -> Result<Self>;
181
182    /// Returns the version requirement of the source.
183    fn version_req(&self) -> Option<&VersionReq>;
184
185    /// Returns a list of contract names defined in the source.
186    fn contract_names(&self) -> &[String];
187
188    /// Returns the language of the source.
189    fn language(&self) -> Self::Language;
190
191    /// Invoked during import resolution. Should resolve imports for the given source, and populate
192    /// include_paths for compilers which support this config.
193    fn resolve_imports<C>(
194        &self,
195        paths: &ProjectPathsConfig<C>,
196        include_paths: &mut BTreeSet<PathBuf>,
197    ) -> Result<Vec<PathBuf>>;
198
199    /// Used to configure [OutputSelection] for sparse builds. In certain cases, we might want to
200    /// include some of the file dependencies into the compiler output even if we might not be
201    /// directly interested in them.
202    ///
203    /// Example of such case is when we are compiling Solidity file containing link references and
204    /// need them to be included in the output to deploy needed libraries.
205    ///
206    /// Receives iterator over imports of the current source.
207    ///
208    /// Returns iterator over paths to the files that should be compiled with full output selection.
209    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
220/// Error returned by compiler. Might also represent a warning or informational message.
221pub 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/// Output of the compiler, including contracts, sources, errors and metadata. might be
232/// extended to be more generic in the future.
233#[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    /// Retains only those files the given iterator yields
247    ///
248    /// In other words, removes all contracts for files not included in the iterator
249    pub fn retain_files<F, I>(&mut self, files: I)
250    where
251        F: AsRef<Path>,
252        I: IntoIterator<Item = F>,
253    {
254        // Note: use `to_lowercase` here because solc not necessarily emits the exact file name,
255        // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol`
256        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
300/// Keeps a set of languages recognized by the compiler.
301pub trait Language:
302    Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
303{
304    /// Extensions of source files recognized by the language set.
305    const FILE_EXTENSIONS: &'static [&'static str];
306}
307
308/// Represents a compiled contract
309pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
310    /// Reference to contract ABI
311    fn abi_ref(&self) -> Option<&JsonAbi>;
312
313    //// Reference to contract bytecode
314    fn bin_ref(&self) -> Option<&BytecodeObject>;
315
316    //// Reference to contract runtime bytecode
317    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/// The main compiler abstraction trait.
351///
352/// Currently mostly represents a wrapper around compiler binary aware of the version and able to
353/// compile given input into [`CompilerOutput`] including artifacts and errors.
354#[auto_impl::auto_impl(&, Box, Arc)]
355pub trait Compiler: Send + Sync + Clone {
356    /// Input type for the compiler. Contains settings and sources to be compiled.
357    type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
358    /// Error type returned by the compiler.
359    type CompilationError: CompilationError;
360    /// Output data for each contract
361    type CompilerContract: CompilerContract;
362    /// Source parser used for resolving imports and version requirements.
363    type Parser: SourceParser<ParsedSource: ParsedSource<Language = Self::Language>>;
364    /// Compiler settings.
365    type Settings: CompilerSettings;
366    /// Enum of languages supported by the compiler.
367    type Language: Language;
368
369    /// Main entrypoint for the compiler. Compiles given input into [CompilerOutput]. Takes
370    /// ownership over the input and returns back version with potential modifications made to it.
371    /// Returned input is always the one which was seen by the binary.
372    fn compile(
373        &self,
374        input: &Self::Input,
375    ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
376
377    /// Returns all versions available locally and remotely. Should return versions with stripped
378    /// metadata.
379    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}