fix: use CARGO_MANIFEST_DIR as root for relative paths in abigen! (#631)
* feat: resolve env vars in abigen paths * docs: add env interpolation note * chore: docs
This commit is contained in:
parent
d06bfdc15c
commit
f6e803b4eb
|
@ -2,14 +2,10 @@
|
|||
use super::util;
|
||||
use ethers_core::types::Address;
|
||||
|
||||
use crate::util::resolve_path;
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{env, fs, path::Path, str::FromStr};
|
||||
use url::Url;
|
||||
|
||||
/// A source of a Truffle artifact JSON.
|
||||
|
@ -19,7 +15,7 @@ pub enum Source {
|
|||
String(String),
|
||||
|
||||
/// An ABI located on the local file system.
|
||||
Local(PathBuf),
|
||||
Local(String),
|
||||
|
||||
/// An ABI to be retrieved over HTTP(S).
|
||||
Http(Url),
|
||||
|
@ -94,7 +90,7 @@ impl Source {
|
|||
let url = base.join(source.as_ref())?;
|
||||
|
||||
match url.scheme() {
|
||||
"file" => Ok(Source::local(url.path())),
|
||||
"file" => Ok(Source::local(url.path().to_string())),
|
||||
"http" | "https" => match url.host_str() {
|
||||
Some("etherscan.io") => Source::etherscan(
|
||||
url.path()
|
||||
|
@ -111,11 +107,8 @@ impl Source {
|
|||
}
|
||||
|
||||
/// Creates a local filesystem source from a path string.
|
||||
pub fn local<P>(path: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Source::Local(path.as_ref().into())
|
||||
pub fn local(path: impl Into<String>) -> Self {
|
||||
Source::Local(path.into())
|
||||
}
|
||||
|
||||
/// Creates an HTTP source from a URL.
|
||||
|
@ -178,21 +171,27 @@ impl FromStr for Source {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reads a Truffle artifact JSON file from the local filesystem.
|
||||
fn get_local_contract(path: &Path) -> Result<String> {
|
||||
/// Reads an artifact JSON file from the local filesystem.
|
||||
///
|
||||
/// The given path can be relative or absolute and can contain env vars like
|
||||
/// `"$CARGO_MANIFEST_DIR/contracts/a.json"`
|
||||
/// If the path is relative after all env vars have been resolved then we assume the root is either
|
||||
/// `CARGO_MANIFEST_DIR` or the current working directory.
|
||||
fn get_local_contract(path: impl AsRef<str>) -> Result<String> {
|
||||
let path = resolve_path(path.as_ref())?;
|
||||
let path = if path.is_relative() {
|
||||
let absolute_path = path.canonicalize().with_context(|| {
|
||||
format!(
|
||||
"unable to canonicalize file from working dir {} with path {}",
|
||||
env::current_dir()
|
||||
.map(|cwd| cwd.display().to_string())
|
||||
.unwrap_or_else(|err| format!("??? ({})", err)),
|
||||
path.display(),
|
||||
)
|
||||
})?;
|
||||
Cow::Owned(absolute_path)
|
||||
let manifest_path = env::var("CARGO_MANIFEST_DIR")?;
|
||||
let root = Path::new(&manifest_path);
|
||||
let mut contract_path = root.join(&path);
|
||||
if !contract_path.exists() {
|
||||
contract_path = path.canonicalize()?;
|
||||
}
|
||||
if !contract_path.exists() {
|
||||
anyhow::bail!("Unable to find local contract \"{}\"", path.display())
|
||||
}
|
||||
contract_path
|
||||
} else {
|
||||
Cow::Borrowed(path)
|
||||
path
|
||||
};
|
||||
|
||||
let json = fs::read_to_string(&path)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use ethers_core::types::Address;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use cfg_if::cfg_if;
|
||||
|
@ -81,10 +82,75 @@ pub fn http_get(_url: &str) -> Result<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Replaces any occurrences of env vars in the `raw` str with their value
|
||||
pub fn resolve_path(raw: &str) -> Result<PathBuf> {
|
||||
let mut unprocessed = raw;
|
||||
let mut resolved = String::new();
|
||||
|
||||
while let Some(dollar_sign) = unprocessed.find('$') {
|
||||
let (head, tail) = unprocessed.split_at(dollar_sign);
|
||||
resolved.push_str(head);
|
||||
|
||||
match parse_identifier(&tail[1..]) {
|
||||
Some((variable, rest)) => {
|
||||
let value = std::env::var(variable)?;
|
||||
resolved.push_str(&value);
|
||||
unprocessed = rest;
|
||||
}
|
||||
None => {
|
||||
anyhow::bail!("Unable to parse a variable from \"{}\"", tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
resolved.push_str(unprocessed);
|
||||
|
||||
Ok(PathBuf::from(resolved))
|
||||
}
|
||||
|
||||
fn parse_identifier(text: &str) -> Option<(&str, &str)> {
|
||||
let mut calls = 0;
|
||||
|
||||
let (head, tail) = take_while(text, |c| {
|
||||
calls += 1;
|
||||
match c {
|
||||
'_' => true,
|
||||
letter if letter.is_ascii_alphabetic() => true,
|
||||
digit if digit.is_ascii_digit() && calls > 1 => true,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if head.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((head, tail))
|
||||
}
|
||||
}
|
||||
|
||||
fn take_while(s: &str, mut predicate: impl FnMut(char) -> bool) -> (&str, &str) {
|
||||
let mut index = 0;
|
||||
for c in s.chars() {
|
||||
if predicate(c) {
|
||||
index += c.len_utf8();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.split_at(index)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_resolve_path() {
|
||||
let raw = "./$ENV_VAR";
|
||||
std::env::set_var("ENV_VAR", "file.txt");
|
||||
let resolved = resolve_path(raw).unwrap();
|
||||
assert_eq!(resolved.to_str().unwrap(), "./file.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_name_to_ident_empty() {
|
||||
assert_quote!(expand_input_name(0, ""), { p0 });
|
||||
|
|
|
@ -16,8 +16,10 @@ mod spanned;
|
|||
pub(crate) mod utils;
|
||||
|
||||
/// Proc macro to generate type-safe bindings to a contract(s). This macro
|
||||
/// accepts one or more Ethereum contract ABI or a path. Note that this path is
|
||||
/// accepts one or more Ethereum contract ABI or a path. Note that relative paths are
|
||||
/// rooted in the crate's root `CARGO_MANIFEST_DIR`.
|
||||
/// Environment variable interpolation is supported via `$` prefix, like
|
||||
/// `"$CARGO_MANIFEST_DIR/contracts/c.json"`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue