foundry_compilers/
buildinfo.rs

1//! Represents an entire build
2
3use crate::compilers::{
4    CompilationError, CompilerContract, CompilerInput, CompilerOutput, Language,
5};
6use foundry_compilers_core::{error::Result, utils};
7use semver::Version;
8use serde::{de::DeserializeOwned, Deserialize, Serialize};
9use std::{
10    collections::{BTreeMap, HashSet},
11    path::{Path, PathBuf},
12};
13
14pub const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-build-info-1";
15
16// A hardhat compatible build info representation
17#[derive(Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct BuildInfo<I, O> {
20    pub id: String,
21    #[serde(rename = "_format")]
22    pub format: String,
23    pub solc_version: Version,
24    pub solc_long_version: Version,
25    pub input: I,
26    pub output: O,
27}
28
29impl<I: DeserializeOwned, O: DeserializeOwned> BuildInfo<I, O> {
30    /// Deserializes the `BuildInfo` object from the given file
31    pub fn read(path: &Path) -> Result<Self> {
32        utils::read_json_file(path)
33    }
34}
35
36/// Additional context we cache for each compiler run.
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38pub struct BuildContext<L> {
39    /// Mapping from internal compiler source id to path of the source file.
40    pub source_id_to_path: BTreeMap<u32, PathBuf>,
41    /// Language of the compiler.
42    pub language: L,
43}
44
45impl<L: Language> BuildContext<L> {
46    pub fn new<I, E, C>(input: &I, output: &CompilerOutput<E, C>) -> Result<Self>
47    where
48        I: CompilerInput<Language = L>,
49    {
50        let mut source_id_to_path = BTreeMap::new();
51
52        let input_sources = input.sources().map(|(path, _)| path).collect::<HashSet<_>>();
53        for (path, source) in output.sources.iter() {
54            if input_sources.contains(path.as_path()) {
55                source_id_to_path.insert(source.id, path.to_path_buf());
56            }
57        }
58
59        Ok(Self { source_id_to_path, language: input.language() })
60    }
61
62    pub fn join_all(&mut self, root: &Path) {
63        self.source_id_to_path.values_mut().for_each(|path| {
64            *path = root.join(path.as_path());
65        });
66    }
67
68    pub fn with_joined_paths(mut self, root: &Path) -> Self {
69        self.join_all(root);
70        self
71    }
72}
73
74/// Represents `BuildInfo` object
75#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
76pub struct RawBuildInfo<L> {
77    /// The hash that identifies the BuildInfo
78    pub id: String,
79    #[serde(flatten)]
80    pub build_context: BuildContext<L>,
81    /// serialized `BuildInfo` json
82    #[serde(flatten)]
83    pub build_info: BTreeMap<String, serde_json::Value>,
84}
85
86// === impl RawBuildInfo ===
87
88impl<L: Language> RawBuildInfo<L> {
89    /// Serializes a `BuildInfo` object
90    pub fn new<I: CompilerInput<Language = L>, E: CompilationError, C: CompilerContract>(
91        input: &I,
92        output: &CompilerOutput<E, C>,
93        full_build_info: bool,
94    ) -> Result<Self> {
95        let version = input.version().clone();
96        let build_context = BuildContext::new(input, output)?;
97
98        let solc_short = format!("{}.{}.{}", version.major, version.minor, version.patch);
99        let input = serde_json::to_value(input)?;
100        let id = utils::unique_hash_many([
101            ETHERS_FORMAT_VERSION,
102            &version.to_string(),
103            &serde_json::to_string(&input)?,
104        ]);
105
106        let mut build_info = BTreeMap::new();
107
108        if full_build_info {
109            build_info.insert("_format".to_string(), serde_json::to_value(ETHERS_FORMAT_VERSION)?);
110            build_info.insert("solcVersion".to_string(), serde_json::to_value(&solc_short)?);
111            build_info.insert("solcLongVersion".to_string(), serde_json::to_value(&version)?);
112            build_info.insert("input".to_string(), input);
113            build_info.insert("output".to_string(), serde_json::to_value(output)?);
114        }
115
116        Ok(Self { id, build_info, build_context })
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::compilers::solc::SolcVersionedInput;
124    use foundry_compilers_artifacts::{sources::Source, Contract, Error, SolcLanguage, Sources};
125    use std::path::PathBuf;
126
127    #[test]
128    fn build_info_serde() {
129        let v: Version = "0.8.4+commit.c7e474f2".parse().unwrap();
130        let input = SolcVersionedInput::build(
131            Sources::from([(PathBuf::from("input.sol"), Source::new(""))]),
132            Default::default(),
133            SolcLanguage::Solidity,
134            v,
135        );
136        let output = CompilerOutput::<Error, Contract>::default();
137        let raw_info = RawBuildInfo::new(&input, &output, true).unwrap();
138        let _info: BuildInfo<SolcVersionedInput, CompilerOutput<Error, Contract>> =
139            serde_json::from_str(&serde_json::to_string(&raw_info).unwrap()).unwrap();
140    }
141}