foundry_compilers/
buildinfo.rs1use crate::compilers::{
4 CompilationError, CompilerContract, CompilerInput, CompilerOutput, Language,
5};
6use alloy_primitives::hex;
7use foundry_compilers_core::{error::Result, utils};
8use md5::Digest;
9use semver::Version;
10use serde::{de::DeserializeOwned, Deserialize, Serialize};
11use std::{
12 collections::{BTreeMap, HashSet},
13 path::{Path, PathBuf},
14};
15
16pub const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-build-info-1";
17
18#[derive(Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct BuildInfo<I, O> {
22 pub id: String,
23 #[serde(rename = "_format")]
24 pub format: String,
25 pub solc_version: Version,
26 pub solc_long_version: Version,
27 pub input: I,
28 pub output: O,
29}
30
31impl<I: DeserializeOwned, O: DeserializeOwned> BuildInfo<I, O> {
32 pub fn read(path: &Path) -> Result<Self> {
34 utils::read_json_file(path)
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
40pub struct BuildContext<L> {
41 pub source_id_to_path: BTreeMap<u32, PathBuf>,
43 pub language: L,
45}
46
47impl<L: Language> BuildContext<L> {
48 pub fn new<I, E, C>(input: &I, output: &CompilerOutput<E, C>) -> Result<Self>
49 where
50 I: CompilerInput<Language = L>,
51 {
52 let mut source_id_to_path = BTreeMap::new();
53
54 let input_sources = input.sources().map(|(path, _)| path).collect::<HashSet<_>>();
55 for (path, source) in output.sources.iter() {
56 if input_sources.contains(path.as_path()) {
57 source_id_to_path.insert(source.id, path.to_path_buf());
58 }
59 }
60
61 Ok(Self { source_id_to_path, language: input.language() })
62 }
63
64 pub fn join_all(&mut self, root: &Path) {
65 self.source_id_to_path.values_mut().for_each(|path| {
66 *path = root.join(path.as_path());
67 });
68 }
69
70 pub fn with_joined_paths(mut self, root: &Path) -> Self {
71 self.join_all(root);
72 self
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78pub struct RawBuildInfo<L> {
79 pub id: String,
81 #[serde(flatten)]
82 pub build_context: BuildContext<L>,
83 #[serde(flatten)]
85 pub build_info: BTreeMap<String, serde_json::Value>,
86}
87
88impl<L: Language> RawBuildInfo<L> {
91 pub fn new<I: CompilerInput<Language = L>, E: CompilationError, C: CompilerContract>(
93 input: &I,
94 output: &CompilerOutput<E, C>,
95 full_build_info: bool,
96 ) -> Result<Self> {
97 let version = input.version().clone();
98 let build_context = BuildContext::new(input, output)?;
99
100 let mut hasher = md5::Md5::new();
101
102 hasher.update(ETHERS_FORMAT_VERSION);
103
104 let solc_short = format!("{}.{}.{}", version.major, version.minor, version.patch);
105 hasher.update(&solc_short);
106 hasher.update(version.to_string());
107
108 let input = serde_json::to_value(input)?;
109 hasher.update(&serde_json::to_string(&input)?);
110
111 let result = hasher.finalize();
115 let id = hex::encode(result);
116
117 let mut build_info = BTreeMap::new();
118
119 if full_build_info {
120 build_info.insert("_format".to_string(), serde_json::to_value(ETHERS_FORMAT_VERSION)?);
121 build_info.insert("solcVersion".to_string(), serde_json::to_value(&solc_short)?);
122 build_info.insert("solcLongVersion".to_string(), serde_json::to_value(&version)?);
123 build_info.insert("input".to_string(), input);
124 build_info.insert("output".to_string(), serde_json::to_value(output)?);
125 }
126
127 Ok(Self { id, build_info, build_context })
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::compilers::solc::SolcVersionedInput;
135 use foundry_compilers_artifacts::{sources::Source, Contract, Error, SolcLanguage, Sources};
136 use std::path::PathBuf;
137
138 #[test]
139 fn build_info_serde() {
140 let v: Version = "0.8.4+commit.c7e474f2".parse().unwrap();
141 let input = SolcVersionedInput::build(
142 Sources::from([(PathBuf::from("input.sol"), Source::new(""))]),
143 Default::default(),
144 SolcLanguage::Solidity,
145 v,
146 );
147 let output = CompilerOutput::<Error, Contract>::default();
148 let raw_info = RawBuildInfo::new(&input, &output, true).unwrap();
149 let _info: BuildInfo<SolcVersionedInput, CompilerOutput<Error, Contract>> =
150 serde_json::from_str(&serde_json::to_string(&raw_info).unwrap()).unwrap();
151 }
152}