1use alloy_json_abi::JsonAbi;
4use alloy_primitives::Bytes;
5use foundry_compilers_artifacts::{
6 hh::HardhatArtifact,
7 sourcemap::{SourceMap, SyntaxError},
8 BytecodeObject, CompactBytecode, CompactContract, CompactContractBytecode,
9 CompactContractBytecodeCow, CompactDeployedBytecode, Contract, FileToContractsMap, SourceFile,
10};
11use foundry_compilers_core::{
12 error::{Result, SolcError, SolcIoError},
13 utils::{self, strip_prefix_owned},
14};
15use path_slash::PathBufExt;
16use semver::Version;
17use serde::{de::DeserializeOwned, Deserialize, Serialize};
18use std::{
19 borrow::Cow,
20 collections::{btree_map::BTreeMap, HashMap, HashSet},
21 ffi::OsString,
22 fmt, fs,
23 hash::Hash,
24 ops::Deref,
25 path::{Path, PathBuf},
26};
27
28mod configurable;
29pub use configurable::*;
30
31mod hh;
32pub use hh::*;
33
34use crate::{
35 cache::{CachedArtifacts, CompilerCache},
36 output::{
37 contracts::VersionedContracts,
38 sources::{VersionedSourceFile, VersionedSourceFiles},
39 },
40 CompilerContract, ProjectPathsConfig,
41};
42
43#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
45pub struct ArtifactId {
46 pub path: PathBuf,
48 pub name: String,
49 pub source: PathBuf,
51 pub version: Version,
53 pub build_id: String,
55 pub profile: String,
56}
57
58impl ArtifactId {
59 pub fn slash_paths(&mut self) {
61 #[cfg(windows)]
62 {
63 self.path = self.path.to_slash_lossy().as_ref().into();
64 self.source = self.source.to_slash_lossy().as_ref().into();
65 }
66 }
67
68 pub fn with_slashed_paths(mut self) -> Self {
70 self.slash_paths();
71 self
72 }
73
74 pub fn strip_file_prefixes(&mut self, base: &Path) {
76 if let Ok(stripped) = self.source.strip_prefix(base) {
77 self.source = stripped.to_path_buf();
78 }
79 }
80
81 pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
83 self.strip_file_prefixes(base);
84 self
85 }
86
87 pub fn slug(&self) -> String {
92 format!("{}.json:{}", self.path.file_stem().unwrap().to_string_lossy(), self.name)
93 }
94
95 pub fn identifier(&self) -> String {
97 format!("{}:{}", self.source.display(), self.name)
98 }
99
100 pub fn slug_versioned(&self) -> String {
102 format!(
103 "{}.{}.{}.{}.json:{}",
104 self.path.file_stem().unwrap().to_string_lossy(),
105 self.version.major,
106 self.version.minor,
107 self.version.patch,
108 self.name
109 )
110 }
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct ArtifactFile<T> {
116 pub artifact: T,
118 pub file: PathBuf,
120 pub version: Version,
122 pub build_id: String,
123 pub profile: String,
124}
125
126impl<T: Serialize> ArtifactFile<T> {
127 pub fn write(&self) -> Result<()> {
129 trace!("writing artifact file {:?} {}", self.file, self.version);
130 utils::create_parent_dir_all(&self.file)?;
131 utils::write_json_file(&self.artifact, &self.file, 64 * 1024)
132 }
133}
134
135impl<T> ArtifactFile<T> {
136 pub fn join(&mut self, root: &Path) {
138 self.file = root.join(&self.file);
139 }
140
141 pub fn strip_prefix(&mut self, base: &Path) {
143 if let Ok(stripped) = self.file.strip_prefix(base) {
144 self.file = stripped.to_path_buf();
145 }
146 }
147}
148
149pub(crate) type ArtifactsMap<T> = FileToContractsMap<Vec<ArtifactFile<T>>>;
151
152#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct Artifacts<T>(pub ArtifactsMap<T>);
155
156impl<T> From<ArtifactsMap<T>> for Artifacts<T> {
157 fn from(m: ArtifactsMap<T>) -> Self {
158 Self(m)
159 }
160}
161
162impl<'a, T> IntoIterator for &'a Artifacts<T> {
163 type Item = (&'a PathBuf, &'a BTreeMap<String, Vec<ArtifactFile<T>>>);
164 type IntoIter =
165 std::collections::btree_map::Iter<'a, PathBuf, BTreeMap<String, Vec<ArtifactFile<T>>>>;
166
167 fn into_iter(self) -> Self::IntoIter {
168 self.0.iter()
169 }
170}
171
172impl<T> IntoIterator for Artifacts<T> {
173 type Item = (PathBuf, BTreeMap<String, Vec<ArtifactFile<T>>>);
174 type IntoIter =
175 std::collections::btree_map::IntoIter<PathBuf, BTreeMap<String, Vec<ArtifactFile<T>>>>;
176
177 fn into_iter(self) -> Self::IntoIter {
178 self.0.into_iter()
179 }
180}
181
182impl<T> Default for Artifacts<T> {
183 fn default() -> Self {
184 Self(Default::default())
185 }
186}
187
188impl<T> AsRef<ArtifactsMap<T>> for Artifacts<T> {
189 fn as_ref(&self) -> &ArtifactsMap<T> {
190 &self.0
191 }
192}
193
194impl<T> AsMut<ArtifactsMap<T>> for Artifacts<T> {
195 fn as_mut(&mut self) -> &mut ArtifactsMap<T> {
196 &mut self.0
197 }
198}
199
200impl<T> Deref for Artifacts<T> {
201 type Target = ArtifactsMap<T>;
202
203 fn deref(&self) -> &Self::Target {
204 &self.0
205 }
206}
207
208impl<T: Serialize> Artifacts<T> {
209 pub fn write_all(&self) -> Result<()> {
211 for artifact in self.artifact_files() {
212 artifact.write()?;
213 }
214 Ok(())
215 }
216}
217
218impl<T> Artifacts<T> {
219 pub fn slash_paths(&mut self) {
221 #[cfg(windows)]
222 {
223 self.0 = std::mem::take(&mut self.0)
224 .into_iter()
225 .map(|(path, files)| (PathBuf::from(path.to_slash_lossy().as_ref()), files))
226 .collect()
227 }
228 }
229
230 pub fn into_inner(self) -> ArtifactsMap<T> {
231 self.0
232 }
233
234 pub fn join_all(&mut self, root: &Path) -> &mut Self {
236 self.artifact_files_mut().for_each(|artifact| artifact.join(root));
237 self
238 }
239
240 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
242 self.artifact_files_mut().for_each(|artifact| artifact.strip_prefix(base));
243 self
244 }
245
246 fn get_contract_artifact_files(&self, contract_name: &str) -> Option<&Vec<ArtifactFile<T>>> {
248 self.0.values().find_map(|all| all.get(contract_name))
249 }
250
251 pub fn find_artifact(
253 &self,
254 file: &Path,
255 contract_name: &str,
256 version: &Version,
257 ) -> Option<&ArtifactFile<T>> {
258 self.0
259 .get(file)
260 .and_then(|contracts| contracts.get(contract_name))
261 .and_then(|artifacts| artifacts.iter().find(|artifact| artifact.version == *version))
262 }
263
264 pub fn has_contract_artifact(&self, contract_name: &str, artifact_path: &Path) -> bool {
266 self.get_contract_artifact_files(contract_name)
267 .map(|artifacts| artifacts.iter().any(|artifact| artifact.file == artifact_path))
268 .unwrap_or_default()
269 }
270
271 pub fn has_artifact(&self, artifact_path: &Path) -> bool {
273 self.artifact_files().any(|artifact| artifact.file == artifact_path)
274 }
275
276 pub fn artifact_files(&self) -> impl Iterator<Item = &ArtifactFile<T>> {
278 self.0.values().flat_map(BTreeMap::values).flatten()
279 }
280
281 pub fn artifact_files_mut(&mut self) -> impl Iterator<Item = &mut ArtifactFile<T>> {
283 self.0.values_mut().flat_map(BTreeMap::values_mut).flatten()
284 }
285
286 pub fn artifacts<O: ArtifactOutput<Artifact = T>>(
290 &self,
291 ) -> impl Iterator<Item = (ArtifactId, &T)> + '_ {
292 self.0.iter().flat_map(|(source, contract_artifacts)| {
293 contract_artifacts.iter().flat_map(move |(_contract_name, artifacts)| {
294 artifacts.iter().filter_map(move |artifact| {
295 O::contract_name(&artifact.file).map(|name| {
296 (
297 ArtifactId {
298 path: PathBuf::from(&artifact.file),
299 name,
300 source: source.clone(),
301 version: artifact.version.clone(),
302 build_id: artifact.build_id.clone(),
303 profile: artifact.profile.clone(),
304 }
305 .with_slashed_paths(),
306 &artifact.artifact,
307 )
308 })
309 })
310 })
311 })
312 }
313
314 pub fn into_artifacts<O: ArtifactOutput<Artifact = T>>(
316 self,
317 ) -> impl Iterator<Item = (ArtifactId, T)> {
318 self.0.into_iter().flat_map(|(source, contract_artifacts)| {
319 contract_artifacts.into_iter().flat_map(move |(_contract_name, artifacts)| {
320 let source = source.clone();
321 artifacts.into_iter().filter_map(move |artifact| {
322 O::contract_name(&artifact.file).map(|name| {
323 (
324 ArtifactId {
325 path: PathBuf::from(&artifact.file),
326 name,
327 source: source.clone(),
328 version: artifact.version,
329 build_id: artifact.build_id.clone(),
330 profile: artifact.profile.clone(),
331 }
332 .with_slashed_paths(),
333 artifact.artifact,
334 )
335 })
336 })
337 })
338 })
339 }
340
341 pub fn artifacts_with_files(&self) -> impl Iterator<Item = (&PathBuf, &String, &T)> + '_ {
347 self.0.iter().flat_map(|(f, contract_artifacts)| {
348 contract_artifacts.iter().flat_map(move |(name, artifacts)| {
349 artifacts.iter().map(move |artifact| (f, name, &artifact.artifact))
350 })
351 })
352 }
353
354 pub fn into_artifacts_with_files(self) -> impl Iterator<Item = (PathBuf, String, T)> {
358 self.0.into_iter().flat_map(|(f, contract_artifacts)| {
359 contract_artifacts.into_iter().flat_map(move |(name, artifacts)| {
360 let contract_name = name;
361 let file = f.clone();
362 artifacts
363 .into_iter()
364 .map(move |artifact| (file.clone(), contract_name.clone(), artifact.artifact))
365 })
366 })
367 }
368
369 pub fn into_stripped_file_prefixes(self, base: &Path) -> Self {
372 let artifacts =
373 self.0.into_iter().map(|(path, c)| (strip_prefix_owned(path, base), c)).collect();
374 Self(artifacts)
375 }
376
377 pub fn find_first(&self, contract_name: &str) -> Option<&T> {
379 self.0.iter().find_map(|(_file, contracts)| {
380 contracts.get(contract_name).and_then(|c| c.first().map(|a| &a.artifact))
381 })
382 }
383
384 pub fn find(&self, contract_path: &Path, contract_name: &str) -> Option<&T> {
386 self.0.iter().filter(|(path, _)| path.as_path() == contract_path).find_map(
387 |(_file, contracts)| {
388 contracts.get(contract_name).and_then(|c| c.first().map(|a| &a.artifact))
389 },
390 )
391 }
392
393 pub fn remove(&mut self, contract_path: &Path, contract_name: &str) -> Option<T> {
395 self.0.iter_mut().filter(|(path, _)| path.as_path() == contract_path).find_map(
396 |(_file, contracts)| {
397 let mut artifact = None;
398 if let Some((c, mut artifacts)) = contracts.remove_entry(contract_name) {
399 if !artifacts.is_empty() {
400 artifact = Some(artifacts.remove(0).artifact);
401 }
402 if !artifacts.is_empty() {
403 contracts.insert(c, artifacts);
404 }
405 }
406 artifact
407 },
408 )
409 }
410
411 pub fn remove_first(&mut self, contract_name: &str) -> Option<T> {
416 self.0.iter_mut().find_map(|(_file, contracts)| {
417 let mut artifact = None;
418 if let Some((c, mut artifacts)) = contracts.remove_entry(contract_name) {
419 if !artifacts.is_empty() {
420 artifact = Some(artifacts.remove(0).artifact);
421 }
422 if !artifacts.is_empty() {
423 contracts.insert(c, artifacts);
424 }
425 }
426 artifact
427 })
428 }
429}
430
431pub trait Artifact {
433 fn into_inner(self) -> (Option<JsonAbi>, Option<Bytes>);
435
436 fn into_compact_contract(self) -> CompactContract;
438
439 fn into_contract_bytecode(self) -> CompactContractBytecode;
441
442 fn into_parts(self) -> (Option<JsonAbi>, Option<Bytes>, Option<Bytes>);
444
445 fn into_abi(self) -> Option<JsonAbi>
447 where
448 Self: Sized,
449 {
450 self.into_parts().0
451 }
452
453 fn into_bytecode_bytes(self) -> Option<Bytes>
455 where
456 Self: Sized,
457 {
458 self.into_parts().1
459 }
460 fn into_deployed_bytecode_bytes(self) -> Option<Bytes>
462 where
463 Self: Sized,
464 {
465 self.into_parts().2
466 }
467
468 fn try_into_parts(self) -> Result<(JsonAbi, Bytes, Bytes)>
470 where
471 Self: Sized,
472 {
473 let (abi, bytecode, deployed_bytecode) = self.into_parts();
474
475 Ok((
476 abi.ok_or_else(|| SolcError::msg("abi missing"))?,
477 bytecode.ok_or_else(|| SolcError::msg("bytecode missing"))?,
478 deployed_bytecode.ok_or_else(|| SolcError::msg("deployed bytecode missing"))?,
479 ))
480 }
481
482 fn get_contract_bytecode(&self) -> CompactContractBytecodeCow<'_>;
485
486 fn get_bytecode(&self) -> Option<Cow<'_, CompactBytecode>> {
488 self.get_contract_bytecode().bytecode
489 }
490
491 fn get_bytecode_object(&self) -> Option<Cow<'_, BytecodeObject>> {
493 let val = match self.get_bytecode()? {
494 Cow::Borrowed(b) => Cow::Borrowed(&b.object),
495 Cow::Owned(b) => Cow::Owned(b.object),
496 };
497 Some(val)
498 }
499
500 fn get_bytecode_bytes(&self) -> Option<Cow<'_, Bytes>> {
502 let val = match self.get_bytecode_object()? {
503 Cow::Borrowed(b) => Cow::Borrowed(b.as_bytes()?),
504 Cow::Owned(b) => Cow::Owned(b.into_bytes()?),
505 };
506 Some(val)
507 }
508
509 fn get_deployed_bytecode(&self) -> Option<Cow<'_, CompactDeployedBytecode>> {
511 self.get_contract_bytecode().deployed_bytecode
512 }
513
514 fn get_deployed_bytecode_object(&self) -> Option<Cow<'_, BytecodeObject>> {
516 let val = match self.get_deployed_bytecode()? {
517 Cow::Borrowed(b) => Cow::Borrowed(&b.bytecode.as_ref()?.object),
518 Cow::Owned(b) => Cow::Owned(b.bytecode?.object),
519 };
520 Some(val)
521 }
522
523 fn get_deployed_bytecode_bytes(&self) -> Option<Cow<'_, Bytes>> {
525 let val = match self.get_deployed_bytecode_object()? {
526 Cow::Borrowed(b) => Cow::Borrowed(b.as_bytes()?),
527 Cow::Owned(b) => Cow::Owned(b.into_bytes()?),
528 };
529 Some(val)
530 }
531
532 fn get_abi(&self) -> Option<Cow<'_, JsonAbi>> {
534 self.get_contract_bytecode().abi
535 }
536
537 fn get_source_map(&self) -> Option<std::result::Result<SourceMap, SyntaxError>> {
542 self.get_bytecode()?.source_map()
543 }
544
545 fn get_source_map_str(&self) -> Option<Cow<'_, str>> {
547 match self.get_bytecode()? {
548 Cow::Borrowed(code) => code.source_map.as_deref().map(Cow::Borrowed),
549 Cow::Owned(code) => code.source_map.map(Cow::Owned),
550 }
551 }
552
553 fn get_source_map_deployed(&self) -> Option<std::result::Result<SourceMap, SyntaxError>> {
558 self.get_deployed_bytecode()?.source_map()
559 }
560
561 fn get_source_map_deployed_str(&self) -> Option<Cow<'_, str>> {
563 match self.get_bytecode()? {
564 Cow::Borrowed(code) => code.source_map.as_deref().map(Cow::Borrowed),
565 Cow::Owned(code) => code.source_map.map(Cow::Owned),
566 }
567 }
568}
569
570impl<T> Artifact for T
571where
572 T: Into<CompactContractBytecode> + Into<CompactContract>,
573 for<'a> &'a T: Into<CompactContractBytecodeCow<'a>>,
574{
575 fn into_inner(self) -> (Option<JsonAbi>, Option<Bytes>) {
576 let artifact = self.into_compact_contract();
577 (artifact.abi, artifact.bin.and_then(|bin| bin.into_bytes()))
578 }
579
580 fn into_compact_contract(self) -> CompactContract {
581 self.into()
582 }
583
584 fn into_contract_bytecode(self) -> CompactContractBytecode {
585 self.into()
586 }
587
588 fn into_parts(self) -> (Option<JsonAbi>, Option<Bytes>, Option<Bytes>) {
589 self.into_compact_contract().into_parts()
590 }
591
592 fn get_contract_bytecode(&self) -> CompactContractBytecodeCow<'_> {
593 self.into()
594 }
595}
596
597pub trait ArtifactOutput {
610 type Artifact: Artifact + DeserializeOwned + Serialize + fmt::Debug + Send + Sync;
612 type CompilerContract: CompilerContract;
613
614 fn on_output<L>(
619 &self,
620 contracts: &VersionedContracts<Self::CompilerContract>,
621 sources: &VersionedSourceFiles,
622 layout: &ProjectPathsConfig<L>,
623 ctx: OutputContext<'_>,
624 primary_profiles: &HashMap<PathBuf, &str>,
625 ) -> Result<Artifacts<Self::Artifact>> {
626 let mut artifacts =
627 self.output_to_artifacts(contracts, sources, ctx, layout, primary_profiles);
628 fs::create_dir_all(&layout.artifacts)
629 .map_err(|err| SolcIoError::new(err, &layout.artifacts))?;
630
631 artifacts.join_all(&layout.artifacts);
632 artifacts.write_all()?;
633
634 self.handle_artifacts(contracts, &artifacts)?;
635
636 Ok(artifacts)
637 }
638
639 fn handle_artifacts(
641 &self,
642 _contracts: &VersionedContracts<Self::CompilerContract>,
643 _artifacts: &Artifacts<Self::Artifact>,
644 ) -> Result<()> {
645 Ok(())
646 }
647
648 fn output_file_name(
651 name: &str,
652 version: &Version,
653 profile: &str,
654 with_version: bool,
655 with_profile: bool,
656 ) -> PathBuf {
657 let mut name = name.to_string();
658 if with_version {
659 name.push_str(&format!(".{}.{}.{}", version.major, version.minor, version.patch));
660 }
661 if with_profile {
662 name.push_str(&format!(".{profile}"));
663 }
664 name.push_str(".json");
665 name.into()
666 }
667
668 fn conflict_free_output_file(
681 already_taken: &HashSet<String>,
682 conflict: PathBuf,
683 contract_file: &Path,
684 artifacts_folder: &Path,
685 ) -> PathBuf {
686 let mut rel_candidate = conflict;
687 if let Ok(stripped) = rel_candidate.strip_prefix(artifacts_folder) {
688 rel_candidate = stripped.to_path_buf();
689 }
690 #[allow(clippy::redundant_clone)] let mut candidate = rel_candidate.clone();
692 let mut current_parent = contract_file.parent();
693
694 while let Some(parent_name) = current_parent.and_then(|f| f.file_name()) {
695 candidate = Path::new(parent_name).join(&candidate);
697 let out_path = artifacts_folder.join(&candidate);
698 if !already_taken.contains(&out_path.to_slash_lossy().to_lowercase()) {
699 trace!("found alternative output file={:?} for {:?}", out_path, contract_file);
700 return out_path;
701 }
702 current_parent = current_parent.and_then(|f| f.parent());
703 }
704
705 trace!("no conflict free output file found after traversing the file");
710
711 let mut num = 1;
712
713 loop {
714 let mut components = rel_candidate.components();
717 let first = components.next().expect("path not empty");
718 let name = first.as_os_str();
719 let mut numerated = OsString::with_capacity(name.len() + 2);
720 numerated.push(name);
721 numerated.push("_");
722 numerated.push(num.to_string());
723
724 let candidate: PathBuf = Some(numerated.as_os_str())
725 .into_iter()
726 .chain(components.map(|c| c.as_os_str()))
727 .collect();
728 if !already_taken.contains(&candidate.to_slash_lossy().to_lowercase()) {
729 trace!("found alternative output file={:?} for {:?}", candidate, contract_file);
730 return candidate;
731 }
732
733 num += 1;
734 }
735 }
736
737 fn output_file(
741 contract_file: &Path,
742 name: &str,
743 version: &Version,
744 profile: &str,
745 with_version: bool,
746 with_profile: bool,
747 ) -> PathBuf {
748 contract_file
749 .file_name()
750 .map(Path::new)
751 .map(|p| {
752 p.join(Self::output_file_name(name, version, profile, with_version, with_profile))
753 })
754 .unwrap_or_else(|| {
755 Self::output_file_name(name, version, profile, with_version, with_profile)
756 })
757 }
758
759 fn contract_name(file: &Path) -> Option<String> {
764 file.file_stem().and_then(|s| s.to_str().map(|s| s.to_string()))
765 }
766
767 fn read_cached_artifact(path: &Path) -> Result<Self::Artifact> {
775 utils::read_json_file(path)
776 }
777
778 fn read_cached_artifacts<T, I>(files: I) -> Result<BTreeMap<PathBuf, Self::Artifact>>
782 where
783 I: IntoIterator<Item = T>,
784 T: Into<PathBuf>,
785 {
786 let mut artifacts = BTreeMap::default();
787 for path in files.into_iter() {
788 let path = path.into();
789 let artifact = Self::read_cached_artifact(&path)?;
790 artifacts.insert(path, artifact);
791 }
792 Ok(artifacts)
793 }
794
795 fn contract_to_artifact(
801 &self,
802 _file: &Path,
803 _name: &str,
804 contract: Self::CompilerContract,
805 source_file: Option<&SourceFile>,
806 ) -> Self::Artifact;
807
808 #[allow(clippy::too_many_arguments)]
811 fn get_artifact_path(
812 ctx: &OutputContext<'_>,
813 already_taken: &HashSet<String>,
814 file: &Path,
815 name: &str,
816 artifacts_folder: &Path,
817 version: &Version,
818 profile: &str,
819 with_version: bool,
820 with_profile: bool,
821 ) -> PathBuf {
822 if let Some(existing_artifact) = ctx.existing_artifact(file, name, version, profile) {
827 trace!("use existing artifact file {:?}", existing_artifact,);
828 existing_artifact.to_path_buf()
829 } else {
830 let path = Self::output_file(file, name, version, profile, with_version, with_profile);
831
832 let path = artifacts_folder.join(path);
833
834 if already_taken.contains(&path.to_slash_lossy().to_lowercase()) {
835 Self::conflict_free_output_file(already_taken, path, file, artifacts_folder)
837 } else {
838 path
839 }
840 }
841 }
842
843 fn output_to_artifacts<C>(
848 &self,
849 contracts: &VersionedContracts<Self::CompilerContract>,
850 sources: &VersionedSourceFiles,
851 ctx: OutputContext<'_>,
852 layout: &ProjectPathsConfig<C>,
853 primary_profiles: &HashMap<PathBuf, &str>,
854 ) -> Artifacts<Self::Artifact> {
855 let mut artifacts = ArtifactsMap::new();
856
857 let mut non_standalone_sources = HashSet::new();
859
860 let mut taken_paths_lowercase = ctx
862 .existing_artifacts
863 .values()
864 .flat_map(|artifacts| artifacts.values())
865 .flat_map(|artifacts| artifacts.values())
866 .flat_map(|artifacts| artifacts.values())
867 .map(|a| a.path.to_slash_lossy().to_lowercase())
868 .collect::<HashSet<_>>();
869
870 let mut files = contracts.keys().collect::<Vec<_>>();
871 files.sort_by(|&file1, &file2| {
873 (file1.components().count(), file1).cmp(&(file2.components().count(), file2))
874 });
875 for file in files {
876 for (name, versioned_contracts) in &contracts[file] {
877 let unique_versions =
878 versioned_contracts.iter().map(|c| &c.version).collect::<HashSet<_>>();
879 let unique_profiles =
880 versioned_contracts.iter().map(|c| &c.profile).collect::<HashSet<_>>();
881 let primary_profile = primary_profiles.get(file);
882
883 for contract in versioned_contracts {
884 non_standalone_sources.insert(file);
885
886 let source_file = sources.find_file_and_version(file, &contract.version);
888
889 let artifact_path = Self::get_artifact_path(
890 &ctx,
891 &taken_paths_lowercase,
892 file,
893 name,
894 layout.artifacts.as_path(),
895 &contract.version,
896 &contract.profile,
897 unique_versions.len() > 1,
898 unique_profiles.len() > 1
899 && primary_profile.is_none_or(|p| p != &contract.profile),
900 );
901
902 taken_paths_lowercase.insert(artifact_path.to_slash_lossy().to_lowercase());
903
904 trace!(
905 "use artifact file {:?} for contract file {} {}",
906 artifact_path,
907 file.display(),
908 contract.version
909 );
910
911 let artifact = self.contract_to_artifact(
912 file,
913 name,
914 contract.contract.clone(),
915 source_file,
916 );
917
918 let artifact = ArtifactFile {
919 artifact,
920 file: artifact_path,
921 version: contract.version.clone(),
922 build_id: contract.build_id.clone(),
923 profile: contract.profile.clone(),
924 };
925
926 artifacts
927 .entry(file.to_path_buf())
928 .or_default()
929 .entry(name.to_string())
930 .or_default()
931 .push(artifact);
932 }
933 }
934 }
935
936 for (file, sources) in sources.as_ref().iter() {
941 let unique_versions = sources.iter().map(|s| &s.version).collect::<HashSet<_>>();
942 let unique_profiles = sources.iter().map(|s| &s.profile).collect::<HashSet<_>>();
943 for source in sources {
944 if !non_standalone_sources.contains(file) {
945 if source.source_file.ast.is_none()
950 || source.source_file.contains_contract_definition()
951 {
952 continue;
953 }
954
955 if let Some(name) = Path::new(file).file_stem().and_then(|stem| stem.to_str()) {
957 if let Some(artifact) =
958 self.standalone_source_file_to_artifact(file, source)
959 {
960 let artifact_path = Self::get_artifact_path(
961 &ctx,
962 &taken_paths_lowercase,
963 file,
964 name,
965 &layout.artifacts,
966 &source.version,
967 &source.profile,
968 unique_versions.len() > 1,
969 unique_profiles.len() > 1,
970 );
971
972 taken_paths_lowercase
973 .insert(artifact_path.to_slash_lossy().to_lowercase());
974
975 artifacts
976 .entry(file.clone())
977 .or_default()
978 .entry(name.to_string())
979 .or_default()
980 .push(ArtifactFile {
981 artifact,
982 file: artifact_path,
983 version: source.version.clone(),
984 build_id: source.build_id.clone(),
985 profile: source.profile.clone(),
986 });
987 }
988 }
989 }
990 }
991 }
992
993 Artifacts(artifacts)
994 }
995
996 fn standalone_source_file_to_artifact(
1006 &self,
1007 _path: &Path,
1008 _file: &VersionedSourceFile,
1009 ) -> Option<Self::Artifact>;
1010
1011 fn is_dirty(&self, _artifact_file: &ArtifactFile<Self::Artifact>) -> Result<bool> {
1013 Ok(false)
1014 }
1015
1016 fn handle_cached_artifacts(&self, _artifacts: &Artifacts<Self::Artifact>) -> Result<()> {
1018 Ok(())
1019 }
1020}
1021
1022#[derive(Clone, Debug, Default)]
1024#[non_exhaustive]
1025pub struct OutputContext<'a> {
1026 pub existing_artifacts: BTreeMap<&'a Path, &'a CachedArtifacts>,
1038}
1039
1040impl<'a> OutputContext<'a> {
1043 pub fn new<S>(cache: &'a CompilerCache<S>) -> Self {
1045 let existing_artifacts = cache
1046 .files
1047 .iter()
1048 .map(|(file, entry)| (file.as_path(), &entry.artifacts))
1049 .collect::<BTreeMap<_, _>>();
1050
1051 Self { existing_artifacts }
1052 }
1053
1054 pub fn existing_artifact(
1059 &self,
1060 file: &Path,
1061 contract: &str,
1062 version: &Version,
1063 profile: &str,
1064 ) -> Option<&Path> {
1065 self.existing_artifacts
1066 .get(file)
1067 .and_then(|contracts| contracts.get(contract))
1068 .and_then(|versions| versions.get(version))
1069 .and_then(|profiles| profiles.get(profile))
1070 .map(|a| a.path.as_path())
1071 }
1072}
1073
1074#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1085pub struct MinimalCombinedArtifacts {
1086 _priv: (),
1087}
1088
1089impl ArtifactOutput for MinimalCombinedArtifacts {
1090 type Artifact = CompactContractBytecode;
1091 type CompilerContract = Contract;
1092
1093 fn contract_to_artifact(
1094 &self,
1095 _file: &Path,
1096 _name: &str,
1097 contract: Contract,
1098 _source_file: Option<&SourceFile>,
1099 ) -> Self::Artifact {
1100 Self::Artifact::from(contract)
1101 }
1102
1103 fn standalone_source_file_to_artifact(
1104 &self,
1105 _path: &Path,
1106 _file: &VersionedSourceFile,
1107 ) -> Option<Self::Artifact> {
1108 None
1109 }
1110}
1111
1112#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1115pub struct MinimalCombinedArtifactsHardhatFallback {
1116 _priv: (),
1117}
1118
1119impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
1120 type Artifact = CompactContractBytecode;
1121 type CompilerContract = Contract;
1122
1123 fn on_output<C>(
1124 &self,
1125 output: &VersionedContracts<Contract>,
1126 sources: &VersionedSourceFiles,
1127 layout: &ProjectPathsConfig<C>,
1128 ctx: OutputContext<'_>,
1129 primary_profiles: &HashMap<PathBuf, &str>,
1130 ) -> Result<Artifacts<Self::Artifact>> {
1131 MinimalCombinedArtifacts::default().on_output(
1132 output,
1133 sources,
1134 layout,
1135 ctx,
1136 primary_profiles,
1137 )
1138 }
1139
1140 fn read_cached_artifact(path: &Path) -> Result<Self::Artifact> {
1141 #[derive(Deserialize)]
1142 #[serde(untagged)]
1143 enum Artifact {
1144 Compact(CompactContractBytecode),
1145 Hardhat(HardhatArtifact),
1146 }
1147
1148 Ok(match utils::read_json_file::<Artifact>(path)? {
1149 Artifact::Compact(c) => c,
1150 Artifact::Hardhat(h) => h.into_contract_bytecode(),
1151 })
1152 }
1153
1154 fn contract_to_artifact(
1155 &self,
1156 file: &Path,
1157 name: &str,
1158 contract: Contract,
1159 source_file: Option<&SourceFile>,
1160 ) -> Self::Artifact {
1161 MinimalCombinedArtifacts::default().contract_to_artifact(file, name, contract, source_file)
1162 }
1163
1164 fn standalone_source_file_to_artifact(
1165 &self,
1166 path: &Path,
1167 file: &VersionedSourceFile,
1168 ) -> Option<Self::Artifact> {
1169 MinimalCombinedArtifacts::default().standalone_source_file_to_artifact(path, file)
1170 }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175 use super::*;
1176
1177 #[test]
1178 fn is_artifact() {
1179 fn assert_artifact<T: Artifact>() {}
1180
1181 assert_artifact::<CompactContractBytecode>();
1182 assert_artifact::<serde_json::Value>();
1183 }
1184
1185 #[test]
1186 fn can_find_alternate_paths() {
1187 let mut already_taken = HashSet::new();
1188
1189 let file = Path::new("v1/tokens/Greeter.sol");
1190 let conflict = PathBuf::from("out/Greeter.sol/Greeter.json");
1191 let artifacts_folder = Path::new("out");
1192
1193 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1194 &already_taken,
1195 conflict.clone(),
1196 file,
1197 artifacts_folder,
1198 );
1199 assert_eq!(alternative.to_slash_lossy(), "out/tokens/Greeter.sol/Greeter.json");
1200
1201 already_taken.insert("out/tokens/Greeter.sol/Greeter.json".to_lowercase());
1202 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1203 &already_taken,
1204 conflict.clone(),
1205 file,
1206 artifacts_folder,
1207 );
1208 assert_eq!(alternative.to_slash_lossy(), "out/v1/tokens/Greeter.sol/Greeter.json");
1209
1210 already_taken.insert("out/v1/tokens/Greeter.sol/Greeter.json".to_lowercase());
1211 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1212 &already_taken,
1213 conflict,
1214 file,
1215 artifacts_folder,
1216 );
1217 assert_eq!(alternative, PathBuf::from("Greeter.sol_1/Greeter.json"));
1218 }
1219
1220 #[test]
1221 fn can_find_alternate_path_conflict() {
1222 let mut already_taken = HashSet::new();
1223
1224 let file = "/Users/carter/dev/goldfinch/mono/packages/protocol/test/forge/mainnet/utils/BaseMainnetForkingTest.t.sol";
1225 let conflict = PathBuf::from("/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts/BaseMainnetForkingTest.t.sol/BaseMainnetForkingTest.json");
1226 already_taken.insert("/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts/BaseMainnetForkingTest.t.sol/BaseMainnetForkingTest.json".into());
1227
1228 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1229 &already_taken,
1230 conflict,
1231 file.as_ref(),
1232 "/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts".as_ref(),
1233 );
1234
1235 assert_eq!(alternative.to_slash_lossy(), "/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts/utils/BaseMainnetForkingTest.t.sol/BaseMainnetForkingTest.json");
1236 }
1237
1238 fn assert_artifact<T: crate::Artifact>() {}
1239
1240 #[test]
1241 fn test() {
1242 assert_artifact::<CompactContractBytecode>();
1243 assert_artifact::<CompactContractBytecodeCow<'static>>();
1244 }
1245}