2021-10-26 11:28:10 +00:00
use crate ::{
2022-08-12 19:11:08 +00:00
artifacts ::{ output_selection ::ContractOutputSelection , Settings } ,
2021-10-30 17:59:44 +00:00
cache ::SOLIDITY_FILES_CACHE_FILENAME ,
2021-12-12 17:10:40 +00:00
error ::{ Result , SolcError , SolcIoError } ,
2021-11-13 19:31:55 +00:00
remappings ::Remapping ,
2022-05-04 05:33:25 +00:00
resolver ::{ Graph , SolImportAlias } ,
2022-02-04 16:20:24 +00:00
utils , Source , Sources ,
2021-10-30 17:59:44 +00:00
} ;
2022-02-04 16:20:24 +00:00
use serde ::{ Deserialize , Serialize } ;
2021-10-30 17:59:44 +00:00
use std ::{
2022-03-30 19:14:29 +00:00
collections ::{ BTreeSet , HashSet } ,
2022-01-17 12:27:40 +00:00
fmt ::{ self , Formatter } ,
2022-02-04 16:20:24 +00:00
fs ,
2022-08-12 19:11:08 +00:00
ops ::{ Deref , DerefMut } ,
2022-01-11 10:02:57 +00:00
path ::{ Component , Path , PathBuf } ,
2021-10-26 11:28:10 +00:00
} ;
/// Where to find all files or where to write them
2022-03-30 19:14:29 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize) ]
2021-10-26 11:28:10 +00:00
pub struct ProjectPathsConfig {
/// Project root
pub root : PathBuf ,
/// Path to the cache, if any
pub cache : PathBuf ,
/// Where to store build artifacts
pub artifacts : PathBuf ,
2022-06-30 16:21:46 +00:00
/// Where to store the build info files
pub build_infos : PathBuf ,
2021-10-26 11:28:10 +00:00
/// Where to find sources
pub sources : PathBuf ,
/// Where to find tests
pub tests : PathBuf ,
2022-06-08 12:13:03 +00:00
/// Where to find scripts
pub scripts : PathBuf ,
2021-10-30 17:59:44 +00:00
/// Where to look for libraries
pub libraries : Vec < PathBuf > ,
2021-11-13 19:31:55 +00:00
/// The compiler remappings
pub remappings : Vec < Remapping > ,
2021-10-26 11:28:10 +00:00
}
impl ProjectPathsConfig {
2021-10-30 17:59:44 +00:00
pub fn builder ( ) -> ProjectPathsConfigBuilder {
ProjectPathsConfigBuilder ::default ( )
}
/// Creates a new hardhat style config instance which points to the canonicalized root path
2021-11-13 19:31:55 +00:00
pub fn hardhat ( root : impl AsRef < Path > ) -> Result < Self > {
2021-10-30 17:59:44 +00:00
PathStyle ::HardHat . paths ( root )
}
/// Creates a new dapptools style config instance which points to the canonicalized root path
2021-11-13 19:31:55 +00:00
pub fn dapptools ( root : impl AsRef < Path > ) -> Result < Self > {
2021-10-30 17:59:44 +00:00
PathStyle ::Dapptools . paths ( root )
}
/// Creates a new config with the current directory as the root
2021-11-13 19:31:55 +00:00
pub fn current_hardhat ( ) -> Result < Self > {
2021-12-12 17:10:40 +00:00
Self ::hardhat ( std ::env ::current_dir ( ) . map_err ( | err | SolcError ::io ( err , " . " ) ) ? )
2021-10-30 17:59:44 +00:00
}
/// Creates a new config with the current directory as the root
2021-11-13 19:31:55 +00:00
pub fn current_dapptools ( ) -> Result < Self > {
2021-12-12 17:10:40 +00:00
Self ::dapptools ( std ::env ::current_dir ( ) . map_err ( | err | SolcError ::io ( err , " . " ) ) ? )
2021-10-30 17:59:44 +00:00
}
2021-12-12 23:39:28 +00:00
2022-03-30 19:14:29 +00:00
/// Returns a new [ProjectPaths] instance that contains all directories configured for this
/// project
pub fn paths ( & self ) -> ProjectPaths {
ProjectPaths {
artifacts : self . artifacts . clone ( ) ,
2022-06-30 16:21:46 +00:00
build_infos : self . build_infos . clone ( ) ,
2022-03-30 19:14:29 +00:00
sources : self . sources . clone ( ) ,
tests : self . tests . clone ( ) ,
2022-06-08 12:13:03 +00:00
scripts : self . scripts . clone ( ) ,
2022-03-30 19:14:29 +00:00
libraries : self . libraries . iter ( ) . cloned ( ) . collect ( ) ,
}
}
/// Same as [Self::paths()] but strips the `root` form all paths,
/// [ProjectPaths::strip_prefix_all()]
pub fn paths_relative ( & self ) -> ProjectPaths {
let mut paths = self . paths ( ) ;
paths . strip_prefix_all ( & self . root ) ;
paths
}
2021-12-12 23:39:28 +00:00
/// Creates all configured dirs and files
pub fn create_all ( & self ) -> std ::result ::Result < ( ) , SolcIoError > {
if let Some ( parent ) = self . cache . parent ( ) {
fs ::create_dir_all ( parent ) . map_err ( | err | SolcIoError ::new ( err , parent ) ) ? ;
}
fs ::create_dir_all ( & self . artifacts )
. map_err ( | err | SolcIoError ::new ( err , & self . artifacts ) ) ? ;
fs ::create_dir_all ( & self . sources ) . map_err ( | err | SolcIoError ::new ( err , & self . sources ) ) ? ;
fs ::create_dir_all ( & self . tests ) . map_err ( | err | SolcIoError ::new ( err , & self . tests ) ) ? ;
2022-06-08 12:13:03 +00:00
fs ::create_dir_all ( & self . scripts ) . map_err ( | err | SolcIoError ::new ( err , & self . scripts ) ) ? ;
2021-12-12 23:39:28 +00:00
for lib in & self . libraries {
fs ::create_dir_all ( lib ) . map_err ( | err | SolcIoError ::new ( err , lib ) ) ? ;
}
Ok ( ( ) )
}
2022-01-05 17:33:56 +00:00
2022-01-05 21:46:57 +00:00
/// Returns all sources found under the project's configured `sources` path
pub fn read_sources ( & self ) -> Result < Sources > {
tracing ::trace! ( " reading all sources from \" {} \" " , self . sources . display ( ) ) ;
Ok ( Source ::read_all_from ( & self . sources ) ? )
}
/// Returns all sources found under the project's configured `test` path
pub fn read_tests ( & self ) -> Result < Sources > {
tracing ::trace! ( " reading all tests from \" {} \" " , self . tests . display ( ) ) ;
Ok ( Source ::read_all_from ( & self . tests ) ? )
}
2022-06-08 12:13:03 +00:00
/// Returns all sources found under the project's configured `script` path
pub fn read_scripts ( & self ) -> Result < Sources > {
tracing ::trace! ( " reading all scripts from \" {} \" " , self . scripts . display ( ) ) ;
Ok ( Source ::read_all_from ( & self . scripts ) ? )
}
2022-07-14 17:57:10 +00:00
/// Returns true if the there is at least one solidity file in this config.
///
/// See also, `Self::input_files()`
pub fn has_input_files ( & self ) -> bool {
self . input_files_iter ( ) . next ( ) . is_some ( )
}
/// Returns an iterator that yields all solidity file paths for `Self::sources`, `Self::tests`
/// and `Self::scripts`
pub fn input_files_iter ( & self ) -> impl Iterator < Item = PathBuf > + '_ {
utils ::source_files_iter ( & self . sources )
. chain ( utils ::source_files_iter ( & self . tests ) )
. chain ( utils ::source_files_iter ( & self . scripts ) )
}
/// Returns the combined set solidity file paths for `Self::sources`, `Self::tests` and
/// `Self::scripts`
2022-01-05 21:46:57 +00:00
pub fn input_files ( & self ) -> Vec < PathBuf > {
2022-07-14 17:57:10 +00:00
self . input_files_iter ( ) . collect ( )
2022-01-05 21:46:57 +00:00
}
2022-06-08 12:13:03 +00:00
/// Returns the combined set of `Self::read_sources` + `Self::read_tests` + `Self::read_scripts`
2022-01-05 21:46:57 +00:00
pub fn read_input_files ( & self ) -> Result < Sources > {
Ok ( Source ::read_all_files ( self . input_files ( ) ) ? )
}
2022-08-01 16:47:41 +00:00
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths ( & mut self ) {
#[ cfg(windows) ]
{
use path_slash ::PathBufExt ;
let slashed = | p : & mut PathBuf | {
* p = p . to_slash_lossy ( ) . as_ref ( ) . into ( ) ;
} ;
slashed ( & mut self . root ) ;
slashed ( & mut self . cache ) ;
slashed ( & mut self . artifacts ) ;
slashed ( & mut self . build_infos ) ;
slashed ( & mut self . sources ) ;
slashed ( & mut self . tests ) ;
slashed ( & mut self . scripts ) ;
self . libraries . iter_mut ( ) . for_each ( slashed ) ;
self . remappings . iter_mut ( ) . for_each ( Remapping ::slash_path ) ;
}
}
2022-08-02 18:04:14 +00:00
/// Returns true if the `file` belongs to a `library`, See [`Self::find_library_ancestor()`]
pub fn has_library_ancestor ( & self , file : impl AsRef < Path > ) -> bool {
self . find_library_ancestor ( file ) . is_some ( )
}
/// Returns the library the file belongs to
///
/// Returns the first library that is an ancestor of the given `file`.
///
/// **Note:** this does not resolve remappings [`Self::resolve_import()`], instead this merely
/// checks if a `library` is a parent of `file`
///
/// # Example
///
/// ```
/// use std::path::Path;
/// use ethers_solc::ProjectPathsConfig;
/// let config = ProjectPathsConfig::builder().lib("lib").build().unwrap();
/// assert_eq!(config.find_library_ancestor("lib/src/Greeter.sol").unwrap(), Path::new("lib"));
/// ```
pub fn find_library_ancestor ( & self , file : impl AsRef < Path > ) -> Option < & PathBuf > {
let file = file . as_ref ( ) ;
for lib in & self . libraries {
if lib . is_relative ( ) & &
file . is_absolute ( ) & &
file . starts_with ( & self . root ) & &
file . starts_with ( self . root . join ( lib ) ) | |
file . is_relative ( ) & &
lib . is_absolute ( ) & &
lib . starts_with ( & self . root ) & &
self . root . join ( file ) . starts_with ( lib )
{
return Some ( lib )
}
if file . starts_with ( lib ) {
return Some ( lib )
}
}
None
}
2022-01-11 10:02:57 +00:00
/// Attempts to resolve an `import` from the given working directory.
///
/// The `cwd` path is the parent dir of the file that includes the `import`
2022-08-12 19:11:08 +00:00
///
/// This will also populate the `include_paths` with any nested library root paths that should
/// be provided to solc via `--include-path` because it uses absolute imports.
pub fn resolve_import_and_include_paths (
& self ,
cwd : & Path ,
import : & Path ,
include_paths : & mut IncludePaths ,
) -> Result < PathBuf > {
2022-01-11 10:02:57 +00:00
let component = import
. components ( )
. next ( )
. ok_or_else ( | | SolcError ::msg ( format! ( " Empty import path {} " , import . display ( ) ) ) ) ? ;
2022-08-12 19:11:08 +00:00
2022-01-11 10:02:57 +00:00
if component = = Component ::CurDir | | component = = Component ::ParentDir {
// if the import is relative we assume it's already part of the processed input
// file set
utils ::canonicalize ( cwd . join ( import ) ) . map_err ( | err | {
2022-11-07 23:43:11 +00:00
SolcError ::msg ( format! ( " failed to resolve relative import \" {err:?} \" " ) )
2022-01-11 10:02:57 +00:00
} )
} else {
// resolve library file
2022-08-12 19:11:08 +00:00
let resolved = self . resolve_library_import ( import . as_ref ( ) ) ;
if resolved . is_none ( ) {
// absolute paths in solidity are a thing for example `import
// "src/interfaces/IConfig.sol"` which could either point to `cwd +
// src/interfaces/IConfig.sol`, or make use of a remapping (`src/=....`)
if let Some ( lib ) = self . find_library_ancestor ( cwd ) {
if let Some ( ( include_path , import ) ) =
utils ::resolve_absolute_library ( lib , cwd , import )
{
// track the path for this absolute import inside a nested library
include_paths . insert ( include_path ) ;
return Ok ( import )
}
}
// also try to resolve absolute imports from the project paths
for path in [ & self . root , & self . sources , & self . tests , & self . scripts ] {
if cwd . starts_with ( path ) {
if let Ok ( import ) = utils ::canonicalize ( path . join ( import ) ) {
return Ok ( import )
}
}
}
}
resolved . ok_or_else ( | | {
2022-01-11 10:02:57 +00:00
SolcError ::msg ( format! (
" failed to resolve library import \" {:?} \" " ,
import . display ( )
) )
} )
}
}
2022-08-12 19:11:08 +00:00
/// Attempts to resolve an `import` from the given working directory.
///
/// The `cwd` path is the parent dir of the file that includes the `import`
pub fn resolve_import ( & self , cwd : & Path , import : & Path ) -> Result < PathBuf > {
self . resolve_import_and_include_paths ( cwd , import , & mut Default ::default ( ) )
}
2022-01-05 21:46:57 +00:00
/// Attempts to find the path to the real solidity file that's imported via the given `import`
/// path by applying the configured remappings and checking the library dirs
2022-01-27 10:04:14 +00:00
///
/// # Example
///
/// Following `@aave` dependency in the `lib` folder `node_modules`
///
/// ```text
/// <root>/node_modules/@aave
/// ├── aave-token
/// │ ├── contracts
/// │ │ ├── open-zeppelin
/// │ │ ├── token
/// ├── governance-v2
/// ├── contracts
/// ├── interfaces
/// ```
///
/// has this remapping: `@aave/=@aave/` (name:path) so contracts can be imported as
///
/// ```solidity
/// import "@aave/governance-v2/contracts/governance/Executor.sol";
/// ```
///
/// So that `Executor.sol` can be found by checking each `lib` folder (`node_modules`) with
/// applied remappings. Applying remapping works by checking if the import path of an import
/// statement starts with the name of a remapping and replacing it with the remapping's `path`.
///
/// There are some caveats though, dapptools style remappings usually include the `src` folder
/// `ds-test/=lib/ds-test/src/` so that imports look like `import "ds-test/test.sol";` (note the
/// missing `src` in the import path).
///
/// For hardhat/npm style that's not always the case, most notably for [openzeppelin-contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) if installed via npm.
/// The remapping is detected as `'@openzeppelin/=node_modules/@openzeppelin/contracts/'`, which
/// includes the source directory `contracts`, however it's common to see import paths like:
///
/// `import "@openzeppelin/contracts/token/ERC20/IERC20.sol";`
///
/// instead of
///
/// `import "@openzeppelin/token/ERC20/IERC20.sol";`
///
2022-03-19 17:05:39 +00:00
/// There is no strict rule behind this, but because [`crate::remappings::Remapping::find_many`]
/// returns `'@openzeppelin/=node_modules/@openzeppelin/contracts/'` we should handle the
/// case if the remapping path ends with `contracts` and the import path starts with
/// `<remapping name>/contracts`. Otherwise we can end up with a resolved path that has a
/// duplicate `contracts` segment:
/// `@openzeppelin/contracts/contracts/token/ERC20/IERC20.sol` we check for this edge case
/// here so that both styles work out of the box.
2022-01-05 21:46:57 +00:00
pub fn resolve_library_import ( & self , import : & Path ) -> Option < PathBuf > {
// if the import path starts with the name of the remapping then we get the resolved path by
// removing the name and adding the remainder to the path of the remapping
2022-01-27 10:04:14 +00:00
if let Some ( path ) = self . remappings . iter ( ) . find_map ( | r | {
import . strip_prefix ( & r . name ) . ok ( ) . map ( | stripped_import | {
let lib_path = Path ::new ( & r . path ) . join ( stripped_import ) ;
// we handle the edge case where the path of a remapping ends with "contracts"
// (`<name>/=.../contracts`) and the stripped import also starts with `contracts`
if let Ok ( adjusted_import ) = stripped_import . strip_prefix ( " contracts/ " ) {
if r . path . ends_with ( " contracts/ " ) & & ! lib_path . exists ( ) {
return Path ::new ( & r . path ) . join ( adjusted_import )
}
}
lib_path
} )
} ) {
2022-01-05 21:46:57 +00:00
Some ( self . root . join ( path ) )
} else {
utils ::resolve_library ( & self . libraries , import )
}
}
2022-01-05 17:33:56 +00:00
/// Attempts to autodetect the artifacts directory based on the given root path
///
/// Dapptools layout takes precedence over hardhat style.
/// This will return:
/// - `<root>/out` if it exists or `<root>/artifacts` does not exist,
/// - `<root>/artifacts` if it exists and `<root>/out` does not exist.
pub fn find_artifacts_dir ( root : impl AsRef < Path > ) -> PathBuf {
utils ::find_fave_or_alt_path ( root , " out " , " artifacts " )
}
/// Attempts to autodetect the source directory based on the given root path
///
/// Dapptools layout takes precedence over hardhat style.
/// This will return:
/// - `<root>/src` if it exists or `<root>/contracts` does not exist,
/// - `<root>/contracts` if it exists and `<root>/src` does not exist.
pub fn find_source_dir ( root : impl AsRef < Path > ) -> PathBuf {
utils ::find_fave_or_alt_path ( root , " src " , " contracts " )
}
/// Attempts to autodetect the lib directory based on the given root path
///
/// Dapptools layout takes precedence over hardhat style.
/// This will return:
/// - `<root>/lib` if it exists or `<root>/node_modules` does not exist,
/// - `<root>/node_modules` if it exists and `<root>/lib` does not exist.
pub fn find_libs ( root : impl AsRef < Path > ) -> Vec < PathBuf > {
vec! [ utils ::find_fave_or_alt_path ( root , " lib " , " node_modules " ) ]
}
2022-01-17 12:27:40 +00:00
/// Flattens all file imports into a single string
pub fn flatten ( & self , target : & Path ) -> Result < String > {
tracing ::trace! ( " flattening file " ) ;
let graph = Graph ::resolve ( self ) ? ;
2022-04-23 08:40:34 +00:00
self . flatten_node ( target , & graph , & mut Default ::default ( ) , false , false , false ) . map ( | x | {
format! ( " {} \n " , utils ::RE_THREE_OR_MORE_NEWLINES . replace_all ( & x , " \n \n " ) . trim ( ) )
} )
2022-01-17 12:27:40 +00:00
}
/// Flattens a single node from the dependency graph
fn flatten_node (
& self ,
target : & Path ,
graph : & Graph ,
2022-03-08 14:46:04 +00:00
imported : & mut HashSet < usize > ,
2022-01-17 12:27:40 +00:00
strip_version_pragma : bool ,
2022-04-07 10:12:25 +00:00
strip_experimental_pragma : bool ,
2022-01-17 12:27:40 +00:00
strip_license : bool ,
) -> Result < String > {
let target_dir = target . parent ( ) . ok_or_else ( | | {
SolcError ::msg ( format! ( " failed to get parent directory for \" {:?} \" " , target . display ( ) ) )
} ) ? ;
let target_index = graph . files ( ) . get ( target ) . ok_or_else ( | | {
SolcError ::msg ( format! ( " cannot resolve file at \" {:?} \" " , target . display ( ) ) )
} ) ? ;
2022-01-19 16:11:37 +00:00
2022-03-08 14:46:04 +00:00
if imported . contains ( target_index ) {
// short circuit nodes that were already imported, if both A.sol and B.sol import C.sol
2022-01-19 16:11:37 +00:00
return Ok ( String ::new ( ) )
}
2022-03-08 14:46:04 +00:00
imported . insert ( * target_index ) ;
2022-01-19 16:11:37 +00:00
2022-01-17 12:27:40 +00:00
let target_node = graph . node ( * target_index ) ;
let mut imports = target_node . imports ( ) . clone ( ) ;
2022-05-04 05:33:25 +00:00
imports . sort_by_key ( | x | x . loc ( ) . start ) ;
let mut content = target_node . content ( ) . to_owned ( ) ;
for alias in imports . iter ( ) . flat_map ( | i | i . data ( ) . aliases ( ) ) {
let ( alias , target ) = match alias {
SolImportAlias ::Contract ( alias , target ) = > ( alias . clone ( ) , target . clone ( ) ) ,
_ = > continue ,
} ;
let name_regex = utils ::create_contract_or_lib_name_regex ( & alias ) ;
let target_len = target . len ( ) as isize ;
let mut replace_offset = 0 ;
for cap in name_regex . captures_iter ( & content . clone ( ) ) {
if cap . name ( " ignore " ) . is_some ( ) {
continue
}
if let Some ( name_match ) =
vec! [ " n1 " , " n2 " , " n3 " ] . iter ( ) . find_map ( | name | cap . name ( name ) )
{
let name_match_range =
utils ::range_by_offset ( & name_match . range ( ) , replace_offset ) ;
replace_offset + = target_len - ( name_match_range . len ( ) as isize ) ;
content . replace_range ( name_match_range , & target ) ;
}
}
}
2022-01-17 12:27:40 +00:00
2022-05-04 05:33:25 +00:00
let mut content = content . as_bytes ( ) . to_vec ( ) ;
2022-01-17 12:27:40 +00:00
let mut offset = 0_ isize ;
2022-05-20 20:00:36 +00:00
let mut statements = [
( target_node . license ( ) , strip_license ) ,
( target_node . version ( ) , strip_version_pragma ) ,
( target_node . experimental ( ) , strip_experimental_pragma ) ,
]
. iter ( )
. filter_map ( | ( data , condition ) | if * condition { data . to_owned ( ) . as_ref ( ) } else { None } )
. collect ::< Vec < _ > > ( ) ;
statements . sort_by_key ( | x | x . loc ( ) . start ) ;
let ( mut imports , mut statements ) =
( imports . iter ( ) . peekable ( ) , statements . iter ( ) . peekable ( ) ) ;
while imports . peek ( ) . is_some ( ) | | statements . peek ( ) . is_some ( ) {
let ( next_import_start , next_statement_start ) = (
imports . peek ( ) . map_or ( usize ::max_value ( ) , | x | x . loc ( ) . start ) ,
statements . peek ( ) . map_or ( usize ::max_value ( ) , | x | x . loc ( ) . start ) ,
) ;
if next_statement_start < next_import_start {
let repl_range = statements . next ( ) . unwrap ( ) . loc_by_offset ( offset ) ;
offset - = repl_range . len ( ) as isize ;
content . splice ( repl_range , std ::iter ::empty ( ) ) ;
} else {
let import = imports . next ( ) . unwrap ( ) ;
let import_path = self . resolve_import ( target_dir , import . data ( ) . path ( ) ) ? ;
let s = self . flatten_node ( & import_path , graph , imported , true , true , true ) ? ;
let import_content = s . as_bytes ( ) ;
let import_content_len = import_content . len ( ) as isize ;
let import_range = import . loc_by_offset ( offset ) ;
offset + = import_content_len - ( import_range . len ( ) as isize ) ;
content . splice ( import_range , import_content . iter ( ) . copied ( ) ) ;
2022-01-17 12:27:40 +00:00
}
}
let result = String ::from_utf8 ( content ) . map_err ( | err | {
2022-11-07 23:43:11 +00:00
SolcError ::msg ( format! ( " failed to convert extended bytes to string: {err} " ) )
2022-01-17 12:27:40 +00:00
} ) ? ;
Ok ( result )
}
2021-10-30 17:59:44 +00:00
}
2021-12-19 12:35:11 +00:00
impl fmt ::Display for ProjectPathsConfig {
fn fmt ( & self , f : & mut Formatter < '_ > ) -> fmt ::Result {
writeln! ( f , " root: {} " , self . root . display ( ) ) ? ;
writeln! ( f , " contracts: {} " , self . sources . display ( ) ) ? ;
writeln! ( f , " artifacts: {} " , self . artifacts . display ( ) ) ? ;
writeln! ( f , " tests: {} " , self . tests . display ( ) ) ? ;
2022-06-08 12:13:03 +00:00
writeln! ( f , " scripts: {} " , self . scripts . display ( ) ) ? ;
2021-12-19 12:35:11 +00:00
writeln! ( f , " libs: " ) ? ;
for lib in & self . libraries {
writeln! ( f , " {} " , lib . display ( ) ) ? ;
}
writeln! ( f , " remappings: " ) ? ;
for remapping in & self . remappings {
2022-11-07 23:43:11 +00:00
writeln! ( f , " {remapping} " ) ? ;
2021-12-19 12:35:11 +00:00
}
Ok ( ( ) )
}
}
2022-03-30 19:14:29 +00:00
/// This is a subset of [ProjectPathsConfig] that contains all relevant folders in the project
#[ derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize) ]
pub struct ProjectPaths {
pub artifacts : PathBuf ,
2022-06-30 16:21:46 +00:00
pub build_infos : PathBuf ,
2022-03-30 19:14:29 +00:00
pub sources : PathBuf ,
pub tests : PathBuf ,
2022-06-08 12:13:03 +00:00
pub scripts : PathBuf ,
2022-03-30 19:14:29 +00:00
pub libraries : BTreeSet < PathBuf > ,
}
impl ProjectPaths {
/// Joins the folders' location with `root`
pub fn join_all ( & mut self , root : impl AsRef < Path > ) -> & mut Self {
let root = root . as_ref ( ) ;
self . artifacts = root . join ( & self . artifacts ) ;
2022-06-30 16:21:46 +00:00
self . build_infos = root . join ( & self . build_infos ) ;
2022-03-30 19:14:29 +00:00
self . sources = root . join ( & self . sources ) ;
self . tests = root . join ( & self . tests ) ;
2022-06-08 12:13:03 +00:00
self . scripts = root . join ( & self . scripts ) ;
2022-03-30 19:14:29 +00:00
let libraries = std ::mem ::take ( & mut self . libraries ) ;
self . libraries . extend ( libraries . into_iter ( ) . map ( | p | root . join ( p ) ) ) ;
self
}
/// Removes `base` from all folders
pub fn strip_prefix_all ( & mut self , base : impl AsRef < Path > ) -> & mut Self {
let base = base . as_ref ( ) ;
if let Ok ( prefix ) = self . artifacts . strip_prefix ( base ) {
self . artifacts = prefix . to_path_buf ( ) ;
}
2022-06-30 16:21:46 +00:00
if let Ok ( prefix ) = self . build_infos . strip_prefix ( base ) {
self . build_infos = prefix . to_path_buf ( ) ;
}
2022-03-30 19:14:29 +00:00
if let Ok ( prefix ) = self . sources . strip_prefix ( base ) {
self . sources = prefix . to_path_buf ( ) ;
}
if let Ok ( prefix ) = self . tests . strip_prefix ( base ) {
self . tests = prefix . to_path_buf ( ) ;
}
2022-06-08 12:13:03 +00:00
if let Ok ( prefix ) = self . scripts . strip_prefix ( base ) {
self . scripts = prefix . to_path_buf ( ) ;
}
2022-03-30 19:14:29 +00:00
let libraries = std ::mem ::take ( & mut self . libraries ) ;
self . libraries . extend (
libraries
. into_iter ( )
. map ( | p | p . strip_prefix ( base ) . map ( | p | p . to_path_buf ( ) ) . unwrap_or ( p ) ) ,
) ;
self
}
}
impl Default for ProjectPaths {
fn default ( ) -> Self {
Self {
artifacts : " out " . into ( ) ,
2022-06-30 16:21:46 +00:00
build_infos : [ " out " , " build-info " ] . iter ( ) . collect ::< PathBuf > ( ) ,
2022-03-30 19:14:29 +00:00
sources : " src " . into ( ) ,
2022-06-08 12:13:03 +00:00
tests : " test " . into ( ) ,
scripts : " script " . into ( ) ,
2022-03-30 19:14:29 +00:00
libraries : Default ::default ( ) ,
}
}
}
2021-10-30 17:59:44 +00:00
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub enum PathStyle {
HardHat ,
Dapptools ,
}
impl PathStyle {
2022-01-05 21:46:57 +00:00
/// Convert into a `ProjectPathsConfig` given the root path and based on the styled
2021-11-13 19:31:55 +00:00
pub fn paths ( & self , root : impl AsRef < Path > ) -> Result < ProjectPathsConfig > {
2021-12-12 17:10:40 +00:00
let root = root . as_ref ( ) ;
2022-01-05 21:46:57 +00:00
let root = utils ::canonicalize ( root ) ? ;
2021-10-30 17:59:44 +00:00
2021-11-13 19:31:55 +00:00
Ok ( match self {
2021-10-30 17:59:44 +00:00
PathStyle ::Dapptools = > ProjectPathsConfig ::builder ( )
. sources ( root . join ( " src " ) )
. artifacts ( root . join ( " out " ) )
2022-06-30 16:21:46 +00:00
. build_infos ( root . join ( " out " ) . join ( " build-info " ) )
2021-10-30 17:59:44 +00:00
. lib ( root . join ( " lib " ) )
2022-11-07 23:43:11 +00:00
. remappings ( Remapping ::find_many ( root . join ( " lib " ) ) )
2021-10-30 17:59:44 +00:00
. root ( root )
2021-11-13 19:31:55 +00:00
. build ( ) ? ,
2021-10-30 17:59:44 +00:00
PathStyle ::HardHat = > ProjectPathsConfig ::builder ( )
. sources ( root . join ( " contracts " ) )
. artifacts ( root . join ( " artifacts " ) )
2022-06-30 16:21:46 +00:00
. build_infos ( root . join ( " artifacts " ) . join ( " build-info " ) )
2021-10-30 17:59:44 +00:00
. lib ( root . join ( " node_modules " ) )
. root ( root )
2021-11-13 19:31:55 +00:00
. build ( ) ? ,
} )
2021-10-30 17:59:44 +00:00
}
}
#[ derive(Debug, Clone, Default) ]
pub struct ProjectPathsConfigBuilder {
root : Option < PathBuf > ,
cache : Option < PathBuf > ,
artifacts : Option < PathBuf > ,
2022-06-30 16:21:46 +00:00
build_infos : Option < PathBuf > ,
2021-10-30 17:59:44 +00:00
sources : Option < PathBuf > ,
tests : Option < PathBuf > ,
2022-06-08 12:13:03 +00:00
scripts : Option < PathBuf > ,
2021-10-30 17:59:44 +00:00
libraries : Option < Vec < PathBuf > > ,
2021-11-13 19:31:55 +00:00
remappings : Option < Vec < Remapping > > ,
2021-10-30 17:59:44 +00:00
}
impl ProjectPathsConfigBuilder {
pub fn root ( mut self , root : impl Into < PathBuf > ) -> Self {
2022-02-04 16:20:24 +00:00
self . root = Some ( utils ::canonicalized ( root ) ) ;
2021-10-30 17:59:44 +00:00
self
}
pub fn cache ( mut self , cache : impl Into < PathBuf > ) -> Self {
2022-02-04 16:20:24 +00:00
self . cache = Some ( utils ::canonicalized ( cache ) ) ;
2021-10-30 17:59:44 +00:00
self
}
pub fn artifacts ( mut self , artifacts : impl Into < PathBuf > ) -> Self {
2022-02-04 16:20:24 +00:00
self . artifacts = Some ( utils ::canonicalized ( artifacts ) ) ;
2021-10-30 17:59:44 +00:00
self
}
2022-06-30 16:21:46 +00:00
pub fn build_infos ( mut self , build_infos : impl Into < PathBuf > ) -> Self {
self . build_infos = Some ( utils ::canonicalized ( build_infos ) ) ;
self
}
2021-10-30 17:59:44 +00:00
pub fn sources ( mut self , sources : impl Into < PathBuf > ) -> Self {
2022-02-04 16:20:24 +00:00
self . sources = Some ( utils ::canonicalized ( sources ) ) ;
2021-10-30 17:59:44 +00:00
self
}
pub fn tests ( mut self , tests : impl Into < PathBuf > ) -> Self {
2022-02-04 16:20:24 +00:00
self . tests = Some ( utils ::canonicalized ( tests ) ) ;
2021-10-30 17:59:44 +00:00
self
}
2022-06-08 12:13:03 +00:00
pub fn scripts ( mut self , scripts : impl Into < PathBuf > ) -> Self {
self . scripts = Some ( utils ::canonicalized ( scripts ) ) ;
self
}
2021-10-30 17:59:44 +00:00
/// Specifically disallow additional libraries
pub fn no_libs ( mut self ) -> Self {
self . libraries = Some ( Vec ::new ( ) ) ;
self
}
pub fn lib ( mut self , lib : impl Into < PathBuf > ) -> Self {
2022-02-04 16:20:24 +00:00
self . libraries . get_or_insert_with ( Vec ::new ) . push ( utils ::canonicalized ( lib ) ) ;
2021-10-30 17:59:44 +00:00
self
}
pub fn libs ( mut self , libs : impl IntoIterator < Item = impl Into < PathBuf > > ) -> Self {
let libraries = self . libraries . get_or_insert_with ( Vec ::new ) ;
for lib in libs . into_iter ( ) {
2022-02-04 16:20:24 +00:00
libraries . push ( utils ::canonicalized ( lib ) ) ;
2021-10-30 17:59:44 +00:00
}
self
}
2021-11-13 19:31:55 +00:00
pub fn remapping ( mut self , remapping : Remapping ) -> Self {
self . remappings . get_or_insert_with ( Vec ::new ) . push ( remapping ) ;
self
}
pub fn remappings ( mut self , remappings : impl IntoIterator < Item = Remapping > ) -> Self {
let our_remappings = self . remappings . get_or_insert_with ( Vec ::new ) ;
for remapping in remappings . into_iter ( ) {
our_remappings . push ( remapping ) ;
}
self
}
2021-12-12 23:39:28 +00:00
pub fn build_with_root ( self , root : impl Into < PathBuf > ) -> ProjectPathsConfig {
2022-02-04 16:20:24 +00:00
let root = utils ::canonicalized ( root ) ;
let libraries = self . libraries . unwrap_or_else ( | | ProjectPathsConfig ::find_libs ( & root ) ) ;
2022-07-06 18:44:47 +00:00
let artifacts =
self . artifacts . unwrap_or_else ( | | ProjectPathsConfig ::find_artifacts_dir ( & root ) ) ;
2022-02-04 16:20:24 +00:00
2021-12-12 23:39:28 +00:00
ProjectPathsConfig {
2021-10-30 17:59:44 +00:00
cache : self
. cache
. unwrap_or_else ( | | root . join ( " cache " ) . join ( SOLIDITY_FILES_CACHE_FILENAME ) ) ,
2022-07-06 18:44:47 +00:00
build_infos : self . build_infos . unwrap_or_else ( | | artifacts . join ( " build-info " ) ) ,
artifacts ,
2022-01-05 17:33:56 +00:00
sources : self . sources . unwrap_or_else ( | | ProjectPathsConfig ::find_source_dir ( & root ) ) ,
2022-06-08 12:13:03 +00:00
tests : self . tests . unwrap_or_else ( | | root . join ( " test " ) ) ,
scripts : self . scripts . unwrap_or_else ( | | root . join ( " script " ) ) ,
2022-02-04 16:20:24 +00:00
remappings : self
. remappings
. unwrap_or_else ( | | libraries . iter ( ) . flat_map ( Remapping ::find_many ) . collect ( ) ) ,
libraries ,
2021-10-26 11:28:10 +00:00
root ,
2021-12-12 23:39:28 +00:00
}
}
pub fn build ( self ) -> std ::result ::Result < ProjectPathsConfig , SolcIoError > {
let root = self
. root
. clone ( )
. map ( Ok )
. unwrap_or_else ( std ::env ::current_dir )
. map_err ( | err | SolcIoError ::new ( err , " . " ) ) ? ;
Ok ( self . build_with_root ( root ) )
2021-10-26 11:28:10 +00:00
}
}
2021-10-30 17:59:44 +00:00
/// The config to use when compiling the contracts
#[ derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize) ]
pub struct SolcConfig {
/// How the file was compiled
pub settings : Settings ,
}
impl SolcConfig {
/// # Example
///
/// Autodetect solc version and default settings
///
/// ```rust
/// use ethers_solc::SolcConfig;
2022-01-20 19:41:19 +00:00
/// let config = SolcConfig::builder().build();
2021-10-30 17:59:44 +00:00
/// ```
pub fn builder ( ) -> SolcConfigBuilder {
SolcConfigBuilder ::default ( )
}
}
2022-02-17 15:31:35 +00:00
impl From < SolcConfig > for Settings {
fn from ( config : SolcConfig ) -> Self {
config . settings
}
}
2021-10-30 17:59:44 +00:00
#[ derive(Default) ]
pub struct SolcConfigBuilder {
settings : Option < Settings > ,
2022-02-17 15:31:35 +00:00
/// additionally selected outputs that should be included in the `Contract` that `solc´ creates
output_selection : Vec < ContractOutputSelection > ,
2021-10-30 17:59:44 +00:00
}
impl SolcConfigBuilder {
pub fn settings ( mut self , settings : Settings ) -> Self {
self . settings = Some ( settings ) ;
self
}
2022-02-17 15:31:35 +00:00
/// Adds another `ContractOutputSelection` to the set
#[ must_use ]
pub fn additional_output ( mut self , output : impl Into < ContractOutputSelection > ) -> Self {
self . output_selection . push ( output . into ( ) ) ;
self
}
/// Adds multiple `ContractOutputSelection` to the set
#[ must_use ]
pub fn additional_outputs < I , S > ( mut self , outputs : I ) -> Self
where
I : IntoIterator < Item = S > ,
S : Into < ContractOutputSelection > ,
{
for out in outputs {
self = self . additional_output ( out ) ;
}
self
}
2021-10-30 17:59:44 +00:00
/// Creates the solc config
///
/// If no solc version is configured then it will be determined by calling `solc --version`.
2022-01-20 19:41:19 +00:00
pub fn build ( self ) -> SolcConfig {
2022-02-17 15:31:35 +00:00
let Self { settings , output_selection } = self ;
let mut settings = settings . unwrap_or_default ( ) ;
settings . push_all ( output_selection ) ;
SolcConfig { settings }
2021-10-30 17:59:44 +00:00
}
}
2022-08-12 19:11:08 +00:00
/// Container for all `--include-path` arguments for Solc, se also [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping
///
/// The `--include--path` flag:
/// > Makes an additional source directory available to the default import callback. Use this option
/// > if you want to import contracts whose location is not fixed in relation to your main source
/// > tree, e.g. third-party libraries installed using a package manager. Can be used multiple
/// > times. Can only be used if base path has a non-empty value.
///
/// In contrast to `--allow-paths` [`AllowedLibPaths`], which takes multiple arguments,
/// `--include-path` only takes a single path argument.
#[ derive(Clone, Debug, Default) ]
pub struct IncludePaths ( pub ( crate ) BTreeSet < PathBuf > ) ;
// === impl IncludePaths ===
impl IncludePaths {
/// Returns the [Command](std::process::Command) arguments for this type
///
/// For each entry in the set, it will return `--include-path` + `<entry>`
pub fn args ( & self ) -> impl Iterator < Item = String > + '_ {
self . paths ( ) . flat_map ( | path | [ " --include-path " . to_string ( ) , format! ( " {} " , path . display ( ) ) ] )
}
/// Returns all paths that exist
pub fn paths ( & self ) -> impl Iterator < Item = & PathBuf > + '_ {
self . 0. iter ( ) . filter ( | path | path . exists ( ) )
}
}
impl Deref for IncludePaths {
type Target = BTreeSet < PathBuf > ;
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
impl DerefMut for IncludePaths {
fn deref_mut ( & mut self ) -> & mut Self ::Target {
& mut self . 0
}
}
2021-11-08 20:11:45 +00:00
/// Helper struct for serializing `--allow-paths` arguments to Solc
///
/// From the [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping):
/// For security reasons the compiler has restrictions on what directories it can access.
/// Directories of source files specified on the command line and target paths of
/// remappings are automatically allowed to be accessed by the file reader,
/// but everything else is rejected by default. Additional paths (and their subdirectories)
/// can be allowed via the --allow-paths /sample/path,/another/sample/path switch.
/// Everything inside the path specified via --base-path is always allowed.
#[ derive(Clone, Debug, Default) ]
2022-08-12 19:11:08 +00:00
pub struct AllowedLibPaths ( pub ( crate ) BTreeSet < PathBuf > ) ;
// === impl AllowedLibPaths ===
2021-11-08 20:11:45 +00:00
2022-01-08 22:20:28 +00:00
impl AllowedLibPaths {
2022-08-12 19:11:08 +00:00
/// Returns the [Command](std::process::Command) arguments for this type
///
/// `--allow-paths` takes a single value: all comma separated paths
pub fn args ( & self ) -> Option < [ String ; 2 ] > {
let args = self . to_string ( ) ;
if args . is_empty ( ) {
return None
}
Some ( [ " --allow-paths " . to_string ( ) , args ] )
}
/// Returns all paths that exist
pub fn paths ( & self ) -> impl Iterator < Item = & PathBuf > + '_ {
self . 0. iter ( ) . filter ( | path | path . exists ( ) )
}
}
impl Deref for AllowedLibPaths {
type Target = BTreeSet < PathBuf > ;
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
impl DerefMut for AllowedLibPaths {
fn deref_mut ( & mut self ) -> & mut Self ::Target {
& mut self . 0
2022-01-08 22:20:28 +00:00
}
}
2021-11-08 20:11:45 +00:00
impl fmt ::Display for AllowedLibPaths {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
2022-08-12 19:11:08 +00:00
let lib_paths =
self . paths ( ) . map ( | path | format! ( " {} " , path . display ( ) ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ;
2022-11-07 23:43:11 +00:00
write! ( f , " {lib_paths} " )
2021-11-08 20:11:45 +00:00
}
}
2022-02-04 16:20:24 +00:00
impl < T : Into < PathBuf > > From < Vec < T > > for AllowedLibPaths {
fn from ( libs : Vec < T > ) -> Self {
let libs = libs . into_iter ( ) . map ( utils ::canonicalized ) . collect ( ) ;
AllowedLibPaths ( libs )
2021-11-08 20:11:45 +00:00
}
}
2022-01-05 17:33:56 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn can_autodetect_dirs ( ) {
2022-08-02 18:04:14 +00:00
let root = utils ::tempdir ( " root " ) . unwrap ( ) ;
2022-01-05 17:33:56 +00:00
let out = root . path ( ) . join ( " out " ) ;
let artifacts = root . path ( ) . join ( " artifacts " ) ;
2022-06-30 16:21:46 +00:00
let build_infos = artifacts . join ( " build-info " ) ;
2022-01-05 17:33:56 +00:00
let contracts = root . path ( ) . join ( " contracts " ) ;
let src = root . path ( ) . join ( " src " ) ;
let lib = root . path ( ) . join ( " lib " ) ;
let node_modules = root . path ( ) . join ( " node_modules " ) ;
let root = root . path ( ) ;
assert_eq! ( ProjectPathsConfig ::find_source_dir ( root ) , src , ) ;
std ::fs ::File ::create ( & contracts ) . unwrap ( ) ;
assert_eq! ( ProjectPathsConfig ::find_source_dir ( root ) , contracts , ) ;
2022-01-05 21:46:57 +00:00
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . sources ,
2022-02-04 16:20:24 +00:00
utils ::canonicalized ( contracts ) ,
2022-01-05 21:46:57 +00:00
) ;
2022-01-05 17:33:56 +00:00
std ::fs ::File ::create ( & src ) . unwrap ( ) ;
assert_eq! ( ProjectPathsConfig ::find_source_dir ( root ) , src , ) ;
2022-01-05 21:46:57 +00:00
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . sources ,
2022-02-04 16:20:24 +00:00
utils ::canonicalized ( src ) ,
2022-01-05 21:46:57 +00:00
) ;
2022-01-05 17:33:56 +00:00
assert_eq! ( ProjectPathsConfig ::find_artifacts_dir ( root ) , out , ) ;
std ::fs ::File ::create ( & artifacts ) . unwrap ( ) ;
assert_eq! ( ProjectPathsConfig ::find_artifacts_dir ( root ) , artifacts , ) ;
2022-01-05 21:46:57 +00:00
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . artifacts ,
2022-02-04 16:20:24 +00:00
utils ::canonicalized ( artifacts ) ,
2022-01-05 21:46:57 +00:00
) ;
2022-06-30 16:21:46 +00:00
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . build_infos ,
2022-06-30 16:21:46 +00:00
utils ::canonicalized ( build_infos )
) ;
2022-01-05 17:33:56 +00:00
std ::fs ::File ::create ( & out ) . unwrap ( ) ;
assert_eq! ( ProjectPathsConfig ::find_artifacts_dir ( root ) , out , ) ;
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . artifacts ,
2022-02-04 16:20:24 +00:00
utils ::canonicalized ( out ) ,
2022-01-05 17:33:56 +00:00
) ;
2022-01-05 21:46:57 +00:00
assert_eq! ( ProjectPathsConfig ::find_libs ( root ) , vec! [ lib . clone ( ) ] , ) ;
2022-01-05 17:33:56 +00:00
std ::fs ::File ::create ( & node_modules ) . unwrap ( ) ;
assert_eq! ( ProjectPathsConfig ::find_libs ( root ) , vec! [ node_modules . clone ( ) ] , ) ;
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . libraries ,
2022-02-04 16:20:24 +00:00
vec! [ utils ::canonicalized ( node_modules ) ] ,
2022-01-05 17:33:56 +00:00
) ;
std ::fs ::File ::create ( & lib ) . unwrap ( ) ;
assert_eq! ( ProjectPathsConfig ::find_libs ( root ) , vec! [ lib . clone ( ) ] , ) ;
assert_eq! (
2022-09-04 17:57:52 +00:00
ProjectPathsConfig ::builder ( ) . build_with_root ( root ) . libraries ,
2022-02-04 16:20:24 +00:00
vec! [ utils ::canonicalized ( lib ) ] ,
2022-01-05 17:33:56 +00:00
) ;
}
2022-07-06 18:44:47 +00:00
#[ test ]
fn can_have_sane_build_info_default ( ) {
2022-08-02 18:04:14 +00:00
let root = utils ::tempdir ( " root " ) . unwrap ( ) ;
2022-07-06 18:44:47 +00:00
let root = root . path ( ) ;
let artifacts = root . join ( " forge-artifacts " ) ;
// Set the artifacts directory without setting the
// build info directory
2022-09-04 17:57:52 +00:00
let project = ProjectPathsConfig ::builder ( ) . artifacts ( & artifacts ) . build_with_root ( root ) ;
2022-07-06 18:44:47 +00:00
// The artifacts should be set correctly based on the configured value
assert_eq! ( project . artifacts , utils ::canonicalized ( artifacts ) ) ;
// The build infos should by default in the artifacts directory
assert_eq! ( project . build_infos , utils ::canonicalized ( project . artifacts . join ( " build-info " ) ) ) ;
}
2022-08-02 18:04:14 +00:00
#[ test ]
fn can_find_library_ancestor ( ) {
let mut config = ProjectPathsConfig ::builder ( ) . lib ( " lib " ) . build ( ) . unwrap ( ) ;
config . root = " /root/ " . into ( ) ;
assert_eq! ( config . find_library_ancestor ( " lib/src/Greeter.sol " ) . unwrap ( ) , Path ::new ( " lib " ) ) ;
assert_eq! (
config . find_library_ancestor ( " /root/lib/src/Greeter.sol " ) . unwrap ( ) ,
Path ::new ( " lib " )
) ;
config . libraries . push ( " /root/test/ " . into ( ) ) ;
assert_eq! (
config . find_library_ancestor ( " test/src/Greeter.sol " ) . unwrap ( ) ,
Path ::new ( " /root/test/ " )
) ;
assert_eq! (
config . find_library_ancestor ( " /root/test/src/Greeter.sol " ) . unwrap ( ) ,
Path ::new ( " /root/test/ " )
) ;
}
2022-01-05 17:33:56 +00:00
}