foundry_compilers/
buildinfo.rs1use 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#[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 pub fn read(path: &Path) -> Result<Self> {
32 utils::read_json_file(path)
33 }
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38pub struct BuildContext<L> {
39 pub source_id_to_path: BTreeMap<u32, PathBuf>,
41 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
76pub struct RawBuildInfo<L> {
77 pub id: String,
79 #[serde(flatten)]
80 pub build_context: BuildContext<L>,
81 #[serde(flatten)]
83 pub build_info: BTreeMap<String, serde_json::Value>,
84}
85
86impl<L: Language> RawBuildInfo<L> {
89 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}