diff --git a/ethers-contract/ethers-contract-abigen/src/multi.rs b/ethers-contract/ethers-contract-abigen/src/multi.rs index c37a14a9..9729e847 100644 --- a/ethers-contract/ethers-contract-abigen/src/multi.rs +++ b/ethers-contract/ethers-contract-abigen/src/multi.rs @@ -8,10 +8,13 @@ use std::{ collections::{BTreeMap, BTreeSet, HashMap, HashSet}, fs, io::Write, - path::Path, + path::{Path, PathBuf}, }; use toml::Value; +/// The default ethers dependency to generate. +const DEFAULT_ETHERS_DEP: &str = "ethers = { git = \"https://github.com/gakonst/ethers-rs\", default-features = false, features = [\"abigen\"] }"; + /// Collects Abigen structs for a series of contracts, pending generation of /// the contract bindings. #[derive(Debug, Clone)] @@ -211,12 +214,17 @@ impl MultiExpansion { } } - MultiExpansionResult { contracts: expansions, dirty_contracts, shared_types } + MultiExpansionResult { root: None, contracts: expansions, dirty_contracts, shared_types } } } /// Represents an intermediary result of [`MultiExpansion::expand()`] pub struct MultiExpansionResult { + /// The root dir at which this should be executed. + /// + /// This is used to check if there's an existing `Cargo.toml`, from which we can derive the + /// proper `ethers` dependencies. + root: Option, contracts: Vec<(ExpandedContract, Context)>, /// contains the indices of contracts with structs that need to be updated dirty_contracts: HashSet, @@ -251,6 +259,14 @@ impl MultiExpansionResult { tokens } + /// Sets the directory from which this type should expand from. + /// + /// This is used to try to find the proper `ethers` dependency if the `root` is an existing + /// workspace. By default, the cwd is assumed to be the `root`. + pub fn set_root(&mut self, root: impl Into) { + self.root = Some(root.into()); + } + /// Sets the path to the shared types module according to the value of `single_file` /// /// If `single_file` then it's expected that types will be written to `shared_types.rs` @@ -278,7 +294,7 @@ impl MultiExpansionResult { /// Converts this result into [`MultiBindingsInner`] fn into_bindings(mut self, single_file: bool, rustfmt: bool) -> MultiBindingsInner { self.set_shared_import_path(single_file); - let Self { contracts, shared_types, .. } = self; + let Self { contracts, shared_types, root, .. } = self; let bindings = contracts .into_iter() .map(|(expanded, ctx)| ContractBindings { @@ -310,7 +326,7 @@ impl MultiExpansionResult { None }; - MultiBindingsInner { bindings, shared_types } + MultiBindingsInner { root, bindings, shared_types } } } @@ -532,6 +548,11 @@ impl MultiBindings { } struct MultiBindingsInner { + /// The root dir at which this should be executed. + /// + /// This is used to check if there's an existing `Cargo.toml`, from which we can derive the + /// proper `ethers` dependencies. + root: Option, /// Abigen objects to be written bindings: BTreeMap, /// contains the content of the shared types if any @@ -569,16 +590,24 @@ impl MultiBindingsInner { Ok(toml) } - /// parses the active Cargo.toml to get what version of ethers we are using - fn find_crate_version(&self) -> Result { - let cargo_toml = std::env::current_dir()?.join("Cargo.toml"); + /// Returns the ethers crate version to use. + /// + /// If we fail to detect a matching `ethers` dependency, this returns the [`DEFAULT_ETHERS_DEP`] + /// version. + fn crate_version(&self) -> String { + self.try_find_crate_version().unwrap_or_else(|_| DEFAULT_ETHERS_DEP.to_string()) + } - let default_dep = || { - "ethers = { git = \"https://github.com/gakonst/ethers-rs\", default-features = false, features = [\"abigen\"] }".to_string() - }; + /// parses the active Cargo.toml to get what version of ethers we are using. + /// + /// Fails if the existing `Cargo.toml` does not contain a valid ethers dependency + fn try_find_crate_version(&self) -> Result { + let cargo_toml = + if let Some(root) = self.root.clone() { root } else { std::env::current_dir()? } + .join("Cargo.toml"); if !cargo_toml.exists() { - return Ok(default_dep()) + return Ok(DEFAULT_ETHERS_DEP.to_string()) } let data = fs::read_to_string(cargo_toml)?; @@ -597,7 +626,7 @@ impl MultiBindingsInner { version )) } else { - Ok(default_dep()) + Ok(DEFAULT_ETHERS_DEP.to_string()) } } @@ -608,7 +637,7 @@ impl MultiBindingsInner { name: impl AsRef, version: impl AsRef, ) -> Result<()> { - let crate_version = self.find_crate_version()?; + let crate_version = self.crate_version(); let contents = self.generate_cargo_toml(name, version, crate_version)?; let mut file = fs::OpenOptions::new() @@ -746,7 +775,7 @@ impl MultiBindingsInner { if check_cargo_toml { // additionally check the contents of the cargo - let crate_version = self.find_crate_version()?; + let crate_version = self.crate_version(); let cargo_contents = self.generate_cargo_toml(name, version, crate_version)?; check_file_in_dir(crate_path, "Cargo.toml", &cargo_contents)?; } @@ -870,6 +899,18 @@ mod tests { }) } + #[test] + fn can_find_ethers_dep() { + run_test(|context| { + let Context { multi_gen, mod_root } = context; + + let single_file = true; + let mut inner = multi_gen.clone().build().unwrap().into_inner(single_file); + inner.root = Some(PathBuf::from("this does not exist")); + inner.write_to_module(mod_root, single_file).unwrap(); + }) + } + #[test] fn can_generate_single_file_module() { run_test(|context| {