Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: extra files logic #88

Merged
merged 7 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 198 additions & 41 deletions src/artifact_output/configurable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ use crate::{
BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection,
EvmOutputSelection, EwasmOutputSelection,
},
Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
GeneratedSource, LosslessMetadata, Metadata, Offsets, Settings, StorageLayout, UserDoc,
Ast, BytecodeObject, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData,
GasEstimates, GeneratedSource, LosslessMetadata, Metadata, Offsets, Settings,
StorageLayout, UserDoc,
},
sources::VersionedSourceFile,
Artifact, ArtifactOutput, SolcConfig, SolcError, SourceFile,
utils, Artifact, ArtifactFile, ArtifactOutput, SolcConfig, SolcError, SourceFile,
};
use alloy_json_abi::JsonAbi;
use alloy_primitives::hex;
Expand Down Expand Up @@ -233,8 +234,24 @@ impl ConfigurableArtifacts {
impl ArtifactOutput for ConfigurableArtifacts {
type Artifact = ConfigurableContractArtifact;

fn write_contract_extras(&self, contract: &Contract, file: &Path) -> Result<(), SolcError> {
self.additional_files.write_extras(contract, file)
/// Writes extra files for compiled artifact based on [Self::additional_files]
fn handle_artifacts(
&self,
contracts: &crate::contracts::VersionedContracts,
artifacts: &crate::Artifacts<Self::Artifact>,
) -> Result<(), SolcError> {
for (file, contracts) in contracts.as_ref().iter() {
for (name, versioned_contracts) in contracts {
for contract in versioned_contracts {
if let Some(artifact) = artifacts.find_artifact(file, name, &contract.version) {
let file = &artifact.file;
utils::create_parent_dir_all(file)?;
self.additional_files.write_extras(&contract.contract, file)?;
}
}
}
}
Ok(())
}

fn contract_to_artifact(
Expand Down Expand Up @@ -370,6 +387,87 @@ impl ArtifactOutput for ConfigurableArtifacts {
..Default::default()
})
}

/// We want to enforce recompilation if artifact is missing data we need for writing extra
/// files.
fn is_dirty(&self, artifact_file: &ArtifactFile<Self::Artifact>) -> Result<bool, SolcError> {
let artifact = &artifact_file.artifact;
let ExtraOutputFiles {
abi: _,
metadata,
ir,
ir_optimized,
ewasm,
assembly,
source_map,
generated_sources,
bytecode: _,
deployed_bytecode: _,
__non_exhaustive: _,
} = self.additional_files;

if metadata && artifact.metadata.is_none() {
return Ok(true);
}
if ir && artifact.ir.is_none() {
return Ok(true);
}
if ir_optimized && artifact.ir_optimized.is_none() {
return Ok(true);
}
if ewasm && artifact.ewasm.is_none() {
return Ok(true);
}
if assembly && artifact.assembly.is_none() {
return Ok(true);
}
if source_map && artifact.get_source_map_str().is_none() {
return Ok(true);
}
if generated_sources {
// We can't check if generated sources are missing or just empty.
return Ok(true);
}
Ok(false)
}

/// Writes extra files for cached artifacts based on [Self::additional_files].
fn handle_cached_artifacts(
&self,
artifacts: &crate::Artifacts<Self::Artifact>,
) -> Result<(), SolcError> {
for artifacts in artifacts.values() {
for artifacts in artifacts.values() {
for artifact_file in artifacts {
let file = &artifact_file.file;
let artifact = &artifact_file.artifact;
self.additional_files.process_abi(artifact.abi.as_ref(), file)?;
self.additional_files.process_assembly(artifact.assembly.as_deref(), file)?;
self.additional_files
.process_bytecode(artifact.bytecode.as_ref().map(|b| &b.object), file)?;
self.additional_files.process_deployed_bytecode(
artifact
.deployed_bytecode
.as_ref()
.and_then(|d| d.bytecode.as_ref())
.map(|b| &b.object),
file,
)?;
self.additional_files
.process_generated_sources(Some(&artifact.generated_sources), file)?;
self.additional_files.process_ir(artifact.ir.as_deref(), file)?;
self.additional_files
.process_ir_optimized(artifact.ir_optimized.as_deref(), file)?;
self.additional_files.process_ewasm(artifact.ewasm.as_ref(), file)?;
self.additional_files.process_metadata(artifact.metadata.as_ref(), file)?;
self.additional_files
.process_source_map(artifact.get_source_map_str().as_deref(), file)?;
}
}
}

Ok(())
}
}

/// Determines the additional values to include in the contract's artifact file
Expand Down Expand Up @@ -596,90 +694,149 @@ impl ExtraOutputFiles {
config
}

/// Write the set values as separate files
pub fn write_extras(&self, contract: &Contract, file: &Path) -> Result<(), SolcError> {
fn process_abi(&self, abi: Option<&JsonAbi>, file: &Path) -> Result<(), SolcError> {
if self.abi {
if let Some(ref abi) = contract.abi {
if let Some(abi) = abi {
let file = file.with_extension("abi.json");
fs::write(&file, serde_json::to_string_pretty(abi)?)
.map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

fn process_metadata(&self, metadata: Option<&Metadata>, file: &Path) -> Result<(), SolcError> {
if self.metadata {
if let Some(ref metadata) = contract.metadata {
if let Some(metadata) = metadata {
let file = file.with_extension("metadata.json");
fs::write(&file, serde_json::to_string_pretty(&metadata.raw_json()?)?)
fs::write(&file, serde_json::to_string_pretty(metadata)?)
.map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

if self.ir_optimized {
if let Some(ref iropt) = contract.ir_optimized {
let file = file.with_extension("iropt");
fs::write(&file, iropt).map_err(|err| SolcError::io(err, file))?
}
}

fn process_ir(&self, ir: Option<&str>, file: &Path) -> Result<(), SolcError> {
if self.ir {
if let Some(ref ir) = contract.ir {
if let Some(ir) = ir {
let file = file.with_extension("ir");
fs::write(&file, ir).map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

fn process_ir_optimized(
&self,
ir_optimized: Option<&str>,
file: &Path,
) -> Result<(), SolcError> {
if self.ir_optimized {
if let Some(ir_optimized) = ir_optimized {
let file = file.with_extension("iropt");
fs::write(&file, ir_optimized).map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

fn process_ewasm(&self, ewasm: Option<&Ewasm>, file: &Path) -> Result<(), SolcError> {
if self.ewasm {
if let Some(ref ewasm) = contract.ewasm {
if let Some(ewasm) = ewasm {
let file = file.with_extension("ewasm");
fs::write(&file, serde_json::to_vec_pretty(ewasm)?)
.map_err(|err| SolcError::io(err, file))?;
}
}
Ok(())
}

fn process_assembly(&self, asm: Option<&str>, file: &Path) -> Result<(), SolcError> {
if self.assembly {
if let Some(ref evm) = contract.evm {
if let Some(ref asm) = evm.assembly {
let file = file.with_extension("asm");
fs::write(&file, asm).map_err(|err| SolcError::io(err, file))?
}
if let Some(asm) = asm {
let file = file.with_extension("asm");
fs::write(&file, asm).map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

fn process_generated_sources(
&self,
generated_sources: Option<&Vec<GeneratedSource>>,
file: &Path,
) -> Result<(), SolcError> {
if self.generated_sources {
if let Some(ref evm) = contract.evm {
if let Some(ref bytecode) = evm.bytecode {
let file = file.with_extension("gensources");
fs::write(&file, serde_json::to_vec_pretty(&bytecode.generated_sources)?)
.map_err(|err| SolcError::io(err, file))?;
}
if let Some(generated_sources) = generated_sources {
let file = file.with_extension("gensources");
fs::write(&file, serde_json::to_vec_pretty(generated_sources)?)
.map_err(|err| SolcError::io(err, file))?;
}
}
Ok(())
}

fn process_source_map(&self, source_map: Option<&str>, file: &Path) -> Result<(), SolcError> {
if self.source_map {
if let Some(ref evm) = contract.evm {
if let Some(ref bytecode) = evm.bytecode {
if let Some(ref sourcemap) = bytecode.source_map {
let file = file.with_extension("sourcemap");
fs::write(&file, sourcemap).map_err(|err| SolcError::io(err, file))?
}
}
if let Some(source_map) = source_map {
let file = file.with_extension("sourcemap");
fs::write(&file, source_map).map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

fn process_bytecode(
&self,
bytecode: Option<&BytecodeObject>,
file: &Path,
) -> Result<(), SolcError> {
if self.bytecode {
if let Some(ref code) = contract.get_bytecode_bytes() {
let code = hex::encode(code.as_ref());
if let Some(bytecode) = bytecode {
let code = hex::encode(bytecode.as_ref());
let file = file.with_extension("bin");
fs::write(&file, code).map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

fn process_deployed_bytecode(
&self,
deployed: Option<&BytecodeObject>,
file: &Path,
) -> Result<(), SolcError> {
if self.deployed_bytecode {
if let Some(ref code) = contract.get_deployed_bytecode_bytes() {
let code = hex::encode(code.as_ref());
if let Some(deployed) = deployed {
let code = hex::encode(deployed.as_ref());
let file = file.with_extension("deployed-bin");
fs::write(&file, code).map_err(|err| SolcError::io(err, file))?
}
}
Ok(())
}

/// Write the set values as separate files
pub fn write_extras(&self, contract: &Contract, file: &Path) -> Result<(), SolcError> {
self.process_abi(contract.abi.as_ref(), file)?;
self.process_metadata(contract.metadata.as_ref().map(|m| &m.metadata), file)?;
self.process_ir(contract.ir.as_deref(), file)?;
self.process_ir_optimized(contract.ir_optimized.as_deref(), file)?;
self.process_ewasm(contract.ewasm.as_ref(), file)?;

let evm = contract.evm.as_ref();
self.process_assembly(evm.and_then(|evm| evm.assembly.as_deref()), file)?;

let bytecode = evm.and_then(|evm| evm.bytecode.as_ref());
self.process_generated_sources(bytecode.map(|b| &b.generated_sources), file)?;

let deployed_bytecode = evm.and_then(|evm| evm.deployed_bytecode.as_ref());
self.process_source_map(bytecode.and_then(|b| b.source_map.as_deref()), file)?;
self.process_bytecode(bytecode.map(|b| &b.object), file)?;
self.process_deployed_bytecode(
deployed_bytecode.and_then(|d| d.bytecode.as_ref()).map(|b| &b.object),
file,
)?;

Ok(())
}
Expand Down
44 changes: 15 additions & 29 deletions src/artifact_output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,41 +623,17 @@ pub trait ArtifactOutput {
artifacts.join_all(&layout.artifacts);
artifacts.write_all()?;

self.write_extras(contracts, &artifacts)?;
self.handle_artifacts(contracts, &artifacts)?;

Ok(artifacts)
}

/// Write additional files for the contract
fn write_contract_extras(&self, contract: &Contract, file: &Path) -> Result<()> {
ExtraOutputFiles::all().write_extras(contract, file)
}

/// Writes additional files for the contracts if the included in the `Contract`, such as `ir`,
/// `ewasm`, `iropt`.
///
/// By default, these fields are _not_ enabled in the [`crate::artifacts::Settings`], see
/// [`crate::artifacts::output_selection::OutputSelection::default_output_selection()`], and the
/// respective fields of the [`Contract`] will `None`. If they'll be manually added to the
/// `output_selection`, then we're also creating individual files for this output, such as
/// `Greeter.iropt`, `Gretter.ewasm`
fn write_extras(
/// Invoked after artifacts has been written to disk for additional processing.
fn handle_artifacts(
&self,
contracts: &VersionedContracts,
artifacts: &Artifacts<Self::Artifact>,
_contracts: &VersionedContracts,
_artifacts: &Artifacts<Self::Artifact>,
) -> Result<()> {
for (file, contracts) in contracts.as_ref().iter() {
for (name, versioned_contracts) in contracts {
for c in versioned_contracts {
if let Some(artifact) = artifacts.find_artifact(file, name, &c.version) {
let file = &artifact.file;
utils::create_parent_dir_all(file)?;
self.write_contract_extras(&c.contract, file)?;
}
}
}
}

Ok(())
}

Expand Down Expand Up @@ -985,6 +961,16 @@ pub trait ArtifactOutput {
_path: &str,
_file: &VersionedSourceFile,
) -> Option<Self::Artifact>;

/// Handler allowing artifacts handler to enforce artifact recompilation.
fn is_dirty(&self, _artifact_file: &ArtifactFile<Self::Artifact>) -> Result<bool> {
Ok(false)
}

/// Invoked with all artifacts that were not recompiled.
fn handle_cached_artifacts(&self, _artifacts: &Artifacts<Self::Artifact>) -> Result<()> {
Ok(())
}
}

/// Additional context to use during [`ArtifactOutput::on_output()`]
Expand Down
12 changes: 12 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,18 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
return true;
}

// If any requested extra files are missing for any artifact, mark source as dirty to
// generate them
for artifacts in self.cached_artifacts.values() {
for artifacts in artifacts.values() {
for artifact_file in artifacts {
if self.project.artifacts_handler().is_dirty(artifact_file).unwrap_or(true) {
return true;
}
}
}
}

// all things match, can be reused
false
}
Expand Down
Loading