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 super::util;
|
||||||
use ethers_core::types::Address;
|
use ethers_core::types::Address;
|
||||||
|
|
||||||
|
use crate::util::resolve_path;
|
||||||
use anyhow::{anyhow, Context, Error, Result};
|
use anyhow::{anyhow, Context, Error, Result};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use std::{
|
use std::{env, fs, path::Path, str::FromStr};
|
||||||
borrow::Cow,
|
|
||||||
env, fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// A source of a Truffle artifact JSON.
|
/// A source of a Truffle artifact JSON.
|
||||||
|
@ -19,7 +15,7 @@ pub enum Source {
|
||||||
String(String),
|
String(String),
|
||||||
|
|
||||||
/// An ABI located on the local file system.
|
/// An ABI located on the local file system.
|
||||||
Local(PathBuf),
|
Local(String),
|
||||||
|
|
||||||
/// An ABI to be retrieved over HTTP(S).
|
/// An ABI to be retrieved over HTTP(S).
|
||||||
Http(Url),
|
Http(Url),
|
||||||
|
@ -94,7 +90,7 @@ impl Source {
|
||||||
let url = base.join(source.as_ref())?;
|
let url = base.join(source.as_ref())?;
|
||||||
|
|
||||||
match url.scheme() {
|
match url.scheme() {
|
||||||
"file" => Ok(Source::local(url.path())),
|
"file" => Ok(Source::local(url.path().to_string())),
|
||||||
"http" | "https" => match url.host_str() {
|
"http" | "https" => match url.host_str() {
|
||||||
Some("etherscan.io") => Source::etherscan(
|
Some("etherscan.io") => Source::etherscan(
|
||||||
url.path()
|
url.path()
|
||||||
|
@ -111,11 +107,8 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a local filesystem source from a path string.
|
/// Creates a local filesystem source from a path string.
|
||||||
pub fn local<P>(path: P) -> Self
|
pub fn local(path: impl Into<String>) -> Self {
|
||||||
where
|
Source::Local(path.into())
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
Source::Local(path.as_ref().into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an HTTP source from a URL.
|
/// 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.
|
/// Reads an artifact JSON file from the local filesystem.
|
||||||
fn get_local_contract(path: &Path) -> Result<String> {
|
///
|
||||||
|
/// 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 path = if path.is_relative() {
|
||||||
let absolute_path = path.canonicalize().with_context(|| {
|
let manifest_path = env::var("CARGO_MANIFEST_DIR")?;
|
||||||
format!(
|
let root = Path::new(&manifest_path);
|
||||||
"unable to canonicalize file from working dir {} with path {}",
|
let mut contract_path = root.join(&path);
|
||||||
env::current_dir()
|
if !contract_path.exists() {
|
||||||
.map(|cwd| cwd.display().to_string())
|
contract_path = path.canonicalize()?;
|
||||||
.unwrap_or_else(|err| format!("??? ({})", err)),
|
}
|
||||||
path.display(),
|
if !contract_path.exists() {
|
||||||
)
|
anyhow::bail!("Unable to find local contract \"{}\"", path.display())
|
||||||
})?;
|
}
|
||||||
Cow::Owned(absolute_path)
|
contract_path
|
||||||
} else {
|
} else {
|
||||||
Cow::Borrowed(path)
|
path
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = fs::read_to_string(&path)
|
let json = fs::read_to_string(&path)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ethers_core::types::Address;
|
use ethers_core::types::Address;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cfg_if::cfg_if;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn input_name_to_ident_empty() {
|
fn input_name_to_ident_empty() {
|
||||||
assert_quote!(expand_input_name(0, ""), { p0 });
|
assert_quote!(expand_input_name(0, ""), { p0 });
|
||||||
|
|
|
@ -16,8 +16,10 @@ mod spanned;
|
||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
/// Proc macro to generate type-safe bindings to a contract(s). This macro
|
/// 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`.
|
/// rooted in the crate's root `CARGO_MANIFEST_DIR`.
|
||||||
|
/// Environment variable interpolation is supported via `$` prefix, like
|
||||||
|
/// `"$CARGO_MANIFEST_DIR/contracts/c.json"`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue