foundry_compilers/compilers/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
use crate::ProjectPathsConfig;
use core::fmt;
use foundry_compilers_artifacts::{
    error::SourceLocation,
    output_selection::OutputSelection,
    remappings::Remapping,
    sources::{Source, Sources},
    Contract, FileToContractsMap, Severity, SourceFile,
};
use foundry_compilers_core::error::Result;
use semver::{Version, VersionReq};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
    borrow::Cow,
    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
    fmt::{Debug, Display},
    hash::Hash,
    path::{Path, PathBuf},
    sync::{Mutex, OnceLock},
};

pub mod multi;
pub mod solc;
pub mod vyper;
pub use vyper::*;

/// A compiler version is either installed (available locally) or can be downloaded, from the remote
/// endpoint
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CompilerVersion {
    Installed(Version),
    Remote(Version),
}

impl CompilerVersion {
    pub fn is_installed(&self) -> bool {
        matches!(self, Self::Installed(_))
    }
}

impl AsRef<Version> for CompilerVersion {
    fn as_ref(&self) -> &Version {
        match self {
            Self::Installed(v) | Self::Remote(v) => v,
        }
    }
}

impl From<CompilerVersion> for Version {
    fn from(s: CompilerVersion) -> Self {
        match s {
            CompilerVersion::Installed(v) | CompilerVersion::Remote(v) => v,
        }
    }
}

impl fmt::Display for CompilerVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

/// Compilation settings including evm_version, output_selection, etc.
pub trait CompilerSettings:
    Default + Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static
{
    /// Executes given fn with mutable reference to configured [OutputSelection].
    fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy);

    /// Returns true if artifacts compiled with given `other` config are compatible with this
    /// config and if compilation can be skipped.
    ///
    /// Ensures that all settings fields are equal except for `output_selection` which is required
    /// to be a subset of `cached.output_selection`.
    fn can_use_cached(&self, other: &Self) -> bool;

    /// Method which might be invoked to add remappings to the input.
    fn with_remappings(self, _remappings: &[Remapping]) -> Self {
        self
    }

    /// Builder method to set the base path for the compiler. Primarily used by solc implementation
    /// to se --base-path.
    fn with_base_path(self, _base_path: &Path) -> Self {
        self
    }

    /// Builder method to set the allowed paths for the compiler. Primarily used by solc
    /// implementation to set --allow-paths.
    fn with_allow_paths(self, _allowed_paths: &BTreeSet<PathBuf>) -> Self {
        self
    }

    /// Builder method to set the include paths for the compiler. Primarily used by solc
    /// implementation to set --include-paths.
    fn with_include_paths(self, _include_paths: &BTreeSet<PathBuf>) -> Self {
        self
    }
}

/// Input of a compiler, including sources and settings used for their compilation.
pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
    type Settings: CompilerSettings;
    type Language: Language;

    /// Constructs one or multiple inputs from given sources set. Might return multiple inputs in
    /// cases when sources need to be divided into sets per language (Yul + Solidity for example).
    fn build(
        sources: Sources,
        settings: Self::Settings,
        language: Self::Language,
        version: Version,
    ) -> Self;

    /// Returns language of the sources included into this input.
    fn language(&self) -> Self::Language;

    /// Returns compiler version for which this input is intended.
    fn version(&self) -> &Version;

    fn sources(&self) -> impl Iterator<Item = (&Path, &Source)>;

    /// Returns compiler name used by reporters to display output during compilation.
    fn compiler_name(&self) -> Cow<'static, str>;

    /// Strips given prefix from all paths.
    fn strip_prefix(&mut self, base: &Path);
}

/// Parser of the source files which is used to identify imports and version requirements of the
/// given source.
///
/// Used by path resolver to resolve imports or determine compiler versions needed to compiler given
/// sources.
pub trait ParsedSource: Debug + Sized + Send + Clone {
    type Language: Language;

    fn parse(content: &str, file: &Path) -> Result<Self>;
    fn version_req(&self) -> Option<&VersionReq>;

    /// Invoked during import resolution. Should resolve imports for the given source, and populate
    /// include_paths for compilers which support this config.
    fn resolve_imports<C>(
        &self,
        paths: &ProjectPathsConfig<C>,
        include_paths: &mut BTreeSet<PathBuf>,
    ) -> Result<Vec<PathBuf>>;
    fn language(&self) -> Self::Language;

    /// Used to configure [OutputSelection] for sparse builds. In certain cases, we might want to
    /// include some of the file dependencies into the compiler output even if we might not be
    /// directly interested in them.
    ///
    /// Example of such case is when we are compiling Solidity file containing link references and
    /// need them to be included in the output to deploy needed libraries.
    ///
    /// Receives iterator over imports of the current source.
    ///
    /// Returns iterator over paths to the files that should be compiled with full output selection.
    fn compilation_dependencies<'a>(
        &self,
        _imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
    ) -> impl Iterator<Item = &'a Path>
    where
        Self: 'a,
    {
        vec![].into_iter()
    }
}

/// Error returned by compiler. Might also represent a warning or informational message.
pub trait CompilationError:
    Serialize + Send + Sync + Display + Debug + Clone + PartialEq + Eq + 'static
{
    fn is_warning(&self) -> bool;
    fn is_error(&self) -> bool;
    fn source_location(&self) -> Option<SourceLocation>;
    fn severity(&self) -> Severity;
    fn error_code(&self) -> Option<u64>;
}

/// Output of the compiler, including contracts, sources and errors. Currently only generic over the
/// error but might be extended in the future.
#[derive(Debug, Serialize, Deserialize)]
pub struct CompilerOutput<E> {
    #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
    pub errors: Vec<E>,
    #[serde(default)]
    pub contracts: FileToContractsMap<Contract>,
    #[serde(default)]
    pub sources: BTreeMap<PathBuf, SourceFile>,
}

impl<E> CompilerOutput<E> {
    /// Retains only those files the given iterator yields
    ///
    /// In other words, removes all contracts for files not included in the iterator
    pub fn retain_files<F, I>(&mut self, files: I)
    where
        F: AsRef<Path>,
        I: IntoIterator<Item = F>,
    {
        // Note: use `to_lowercase` here because solc not necessarily emits the exact file name,
        // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol`
        let files: HashSet<_> =
            files.into_iter().map(|s| s.as_ref().to_string_lossy().to_lowercase()).collect();
        self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
        self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
    }

    pub fn merge(&mut self, other: Self) {
        self.errors.extend(other.errors);
        self.contracts.extend(other.contracts);
        self.sources.extend(other.sources);
    }

    pub fn join_all(&mut self, root: &Path) {
        self.contracts = std::mem::take(&mut self.contracts)
            .into_iter()
            .map(|(path, contracts)| (root.join(path), contracts))
            .collect();
        self.sources = std::mem::take(&mut self.sources)
            .into_iter()
            .map(|(path, source)| (root.join(path), source))
            .collect();
    }

    pub fn map_err<F, O: FnMut(E) -> F>(self, op: O) -> CompilerOutput<F> {
        CompilerOutput {
            errors: self.errors.into_iter().map(op).collect(),
            contracts: self.contracts,
            sources: self.sources,
        }
    }
}

impl<E> Default for CompilerOutput<E> {
    fn default() -> Self {
        Self { errors: Vec::new(), contracts: BTreeMap::new(), sources: BTreeMap::new() }
    }
}

/// Keeps a set of languages recognized by the compiler.
pub trait Language:
    Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
{
    /// Extensions of source files recognized by the language set.
    const FILE_EXTENSIONS: &'static [&'static str];
}

/// The main compiler abstraction trait.
///
/// Currently mostly represents a wrapper around compiler binary aware of the version and able to
/// compile given input into [`CompilerOutput`] including artifacts and errors.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait Compiler: Send + Sync + Clone {
    /// Input type for the compiler. Contains settings and sources to be compiled.
    type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
    /// Error type returned by the compiler.
    type CompilationError: CompilationError;
    /// Source parser used for resolving imports and version requirements.
    type ParsedSource: ParsedSource<Language = Self::Language>;
    /// Compiler settings.
    type Settings: CompilerSettings;
    /// Enum of languages supported by the compiler.
    type Language: Language;

    /// Main entrypoint for the compiler. Compiles given input into [CompilerOutput]. Takes
    /// ownership over the input and returns back version with potential modifications made to it.
    /// Returned input is always the one which was seen by the binary.
    fn compile(&self, input: &Self::Input) -> Result<CompilerOutput<Self::CompilationError>>;

    /// Returns all versions available locally and remotely. Should return versions with stripped
    /// metadata.
    fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion>;
}

pub(crate) fn cache_version(
    path: PathBuf,
    args: &[String],
    f: impl FnOnce(&Path) -> Result<Version>,
) -> Result<Version> {
    #[allow(clippy::complexity)]
    static VERSION_CACHE: OnceLock<Mutex<HashMap<PathBuf, HashMap<Vec<String>, Version>>>> =
        OnceLock::new();
    let mut lock = VERSION_CACHE
        .get_or_init(|| Mutex::new(HashMap::new()))
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner);

    if let Some(version) = lock.get(&path).and_then(|versions| versions.get(args)) {
        return Ok(version.clone());
    }

    let version = f(&path)?;

    lock.entry(path).or_default().insert(args.to_vec(), version.clone());

    Ok(version)
}