ethers-rs/ethers-solc/src/project_util.rs

253 lines
7.6 KiB
Rust
Raw Normal View History

//! Utilities for mocking project workspaces
use crate::{
config::ProjectPathsConfigBuilder,
error::{Result, SolcError},
hh::HardhatArtifacts,
utils::tempdir,
ArtifactOutput, MinimalCombinedArtifacts, PathStyle, Project, ProjectCompileOutput,
ProjectPathsConfig, SolcIoError,
};
use fs_extra::{dir, file};
use std::{
fmt,
path::{Path, PathBuf},
};
use tempfile::TempDir;
/// A [`Project`] wrapper that lives in a new temporary directory
///
/// Once `TempProject` is dropped, the temp dir is automatically removed, see [`TempDir::drop()`]
pub struct TempProject<T: ArtifactOutput = MinimalCombinedArtifacts> {
/// temporary workspace root
_root: TempDir,
/// actual project workspace with the `root` tempdir as its root
inner: Project<T>,
}
impl<T: ArtifactOutput> TempProject<T> {
/// Makes sure all resources are created
pub fn create_new(root: TempDir, inner: Project<T>) -> std::result::Result<Self, SolcIoError> {
let project = Self { _root: root, inner };
project.paths().create_all()?;
Ok(project)
}
/// Creates a new temp project inside a tempdir with a prefixed directory
pub fn prefixed(prefix: &str, paths: ProjectPathsConfigBuilder) -> Result<Self> {
let tmp_dir = tempdir(prefix)?;
let paths = paths.build_with_root(tmp_dir.path());
let inner = Project::builder().artifacts().paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
/// Creates a new temp project for the given `PathStyle`
pub fn with_style(prefix: &str, style: PathStyle) -> Result<Self> {
let tmp_dir = tempdir(prefix)?;
let paths = style.paths(tmp_dir.path())?;
let inner = Project::builder().artifacts().paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
/// Creates a new temp project using the provided paths and setting the project root to a temp
/// dir
pub fn new(paths: ProjectPathsConfigBuilder) -> Result<Self> {
Self::prefixed("temp-project", paths)
}
pub fn project(&self) -> &Project<T> {
&self.inner
}
pub fn compile(&self) -> Result<ProjectCompileOutput<T>> {
self.project().compile()
}
pub fn flatten(&self, target: &Path) -> Result<String> {
self.project().flatten(target)
}
pub fn project_mut(&mut self) -> &mut Project<T> {
&mut self.inner
}
/// The configured paths of the project
pub fn paths(&self) -> &ProjectPathsConfig {
&self.project().paths
}
/// The configured paths of the project
pub fn paths_mut(&mut self) -> &mut ProjectPathsConfig {
&mut self.project_mut().paths
}
refactor(solc): rewrite compiler passes and cache change detection (#802) * chore: clippy * refactor: rewrite compiler passes and cache * feat: more work on compile pipeline * feat: add cache constructor * add artifact filtering * fine tune api * feat: prepare version integration * docs: more docs * feat: add cacheentry2 * replace cacheentry types * integrate new api * docs: more docs * feat: implement new output handler * feat: integrate cached files in new compile pipeline * refactor: more cache refactor * docs: more docs * feat: add source name mapping * feat: implement new parallel solc * refactor: do a little cleanup * refactor: even more cleanup * even more cleanup * chore: make it compile * chore: make it compile with all features * chore: clippy fix * feat: integrate new compiler pipeline * docs: more docs * refactor: move stuff around * refactor: start deprecating output type * chore: make it compile again * chore(deps): bump solc version 0.2.0 * feat: unify output types * cargo fix * refactor: add contracts wrapper * chore: replace ProjectCompileOutput * docs: add more docs * feat: add offline mode * feat: more artifact helpers * chore: cleanup cache * chore: streamline types * fix: better artifacts mapping * chore: some cleanup * chore: change artifact * chore: add configure solc fn * feat: add artifact reading * feat: implement retain and extend * feat: add cache extending * feat: write to disk * chore: make clippy happy * feat: implement path mapping * chore: nits * feat: introduce states * feat: add compiler state machine * chore: move cache types to cache mod * chore: make clippy happy * feat: add debug derives * fix: use resolved import source unit names * fix: failing tests * test: test multiple libs properly * chore: make clippy happy * chore: update CHANGELOG * fix: doc tests * fix: set offline mode correctly * chore: make it compile again * Update ethers-solc/src/artifacts.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * feat: find remappings by default * typos * add eth_syncing RPC (#848) * add eth_syncing RPC * Changelo updated * small comments * Intermediate SyncingStatus * fix(core): adjust Ganache for new cli output (#851) * fix: review comments * fix: cache relative path bug * chore: add cache example * chore: use absolute paths * fix: remove overwritten files from cache * fix: rustfmt * chore: more helper functions * chore: export AggregatedOutput * feat: implement helper functions * feat: even more helpers * fix: failing doc tests * refactor: remove source name map tracking * fix: determine artifacts in ephemeral mode * refactor: allowed paths should not fail Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> Co-authored-by: rakita <rakita@users.noreply.github.com> Co-authored-by: wolflo <33909953+wolflo@users.noreply.github.com>
2022-02-04 16:20:24 +00:00
/// Returns the path to the artifacts directory
pub fn artifacts_path(&self) -> &PathBuf {
&self.paths().artifacts
}
/// Returns the path to the sources directory
pub fn sources_path(&self) -> &PathBuf {
&self.paths().sources
}
/// Returns the path to the cache file
pub fn cache_path(&self) -> &PathBuf {
&self.paths().cache
}
/// The root path of the temporary workspace
pub fn root(&self) -> &Path {
self.project().paths.root.as_path()
}
/// Copies a single file into the projects source
pub fn copy_source(&self, source: impl AsRef<Path>) -> Result<()> {
copy_file(source, &self.paths().sources)
}
pub fn copy_sources<I, S>(&self, sources: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<Path>,
{
for path in sources {
self.copy_source(path)?;
}
Ok(())
}
fn get_lib(&self) -> Result<PathBuf> {
self.paths()
.libraries
.get(0)
.cloned()
.ok_or_else(|| SolcError::msg("No libraries folders configured"))
}
/// Copies a single file into the project's main library directory
pub fn copy_lib(&self, lib: impl AsRef<Path>) -> Result<()> {
let lib_dir = self.get_lib()?;
copy_file(lib, lib_dir)
}
/// Copy a series of files into the main library dir
pub fn copy_libs<I, S>(&self, libs: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<Path>,
{
for path in libs {
self.copy_lib(path)?;
}
Ok(())
}
/// Adds a new library file
pub fn add_lib(&self, name: impl AsRef<str>, content: impl AsRef<str>) -> Result<PathBuf> {
let name = contract_file_name(name);
let lib_dir = self.get_lib()?;
let lib = lib_dir.join(name);
create_contract_file(lib, content)
}
/// Adds a new source file
pub fn add_source(&self, name: impl AsRef<str>, content: impl AsRef<str>) -> Result<PathBuf> {
let name = contract_file_name(name);
let source = self.paths().sources.join(name);
create_contract_file(source, content)
}
}
impl<T: ArtifactOutput> fmt::Debug for TempProject<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TempProject").field("paths", self.paths()).finish()
}
}
fn create_contract_file(path: PathBuf, content: impl AsRef<str>) -> Result<PathBuf> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|err| SolcIoError::new(err, parent.to_path_buf()))?;
}
std::fs::write(&path, content.as_ref()).map_err(|err| SolcIoError::new(err, path.clone()))?;
Ok(path)
}
fn contract_file_name(name: impl AsRef<str>) -> String {
let name = name.as_ref();
if name.ends_with(".sol") {
name.to_string()
} else {
format!("{}.sol", name)
}
}
impl TempProject<HardhatArtifacts> {
/// Creates an empty new hardhat style workspace in a new temporary dir
pub fn hardhat() -> Result<Self> {
let tmp_dir = tempdir("tmp_hh")?;
let paths = ProjectPathsConfig::hardhat(tmp_dir.path())?;
let inner = Project::builder().artifacts().paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
}
impl TempProject<MinimalCombinedArtifacts> {
/// Creates an empty new dapptools style workspace in a new temporary dir
pub fn dapptools() -> Result<Self> {
let tmp_dir = tempdir("tmp_dapp")?;
let paths = ProjectPathsConfig::dapptools(tmp_dir.path())?;
let inner = Project::builder().artifacts().paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
}
impl<T: ArtifactOutput> AsRef<Project<T>> for TempProject<T> {
fn as_ref(&self) -> &Project<T> {
self.project()
}
}
/// commonly used options for copying entire folders
fn dir_copy_options() -> dir::CopyOptions {
dir::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000, //64kb
copy_inside: true,
content_only: true,
depth: 0,
}
}
/// commonly used options for copying files
fn file_copy_options() -> file::CopyOptions {
file::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000, //64kb
}
}
/// Copies a single file into the given dir
pub fn copy_file(source: impl AsRef<Path>, target_dir: impl AsRef<Path>) -> Result<()> {
let source = source.as_ref();
let target = target_dir.as_ref().join(
source
.file_name()
.ok_or_else(|| SolcError::msg(format!("No file name for {}", source.display())))?,
);
fs_extra::file::copy(source, target, &file_copy_options())?;
Ok(())
}
/// Copies all content of the source dir into the target dir
pub fn copy_dir(source: impl AsRef<Path>, target_dir: impl AsRef<Path>) -> Result<()> {
fs_extra::dir::copy(source, target_dir, &dir_copy_options())?;
Ok(())
}