feat(solc): improve solc detection and reduce install effort (#648)
This commit is contained in:
parent
2a3fcbbb40
commit
0464ac9d46
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
artifacts::Source,
|
artifacts::Source,
|
||||||
error::{Result, SolcError},
|
error::{Result, SolcError},
|
||||||
CompilerInput, CompilerOutput,
|
utils, CompilerInput, CompilerOutput,
|
||||||
};
|
};
|
||||||
use semver::{Version, VersionReq};
|
use semver::{Version, VersionReq};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
@ -157,17 +157,25 @@ impl Solc {
|
||||||
pub fn detect_version(source: &Source) -> Result<Version> {
|
pub fn detect_version(source: &Source) -> Result<Version> {
|
||||||
// detects the required solc version
|
// detects the required solc version
|
||||||
let sol_version = Self::version_req(source)?;
|
let sol_version = Self::version_req(source)?;
|
||||||
|
Self::ensure_installed(&sol_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a Solidity version requirement, it detects the latest compiler version which can be
|
||||||
|
/// used to build it, and returns it.
|
||||||
|
///
|
||||||
|
/// If the required compiler version is not installed, it also proceeds to install it.
|
||||||
|
#[cfg(all(feature = "svm", feature = "async"))]
|
||||||
|
pub fn ensure_installed(sol_version: &VersionReq) -> Result<Version> {
|
||||||
#[cfg(any(test, feature = "tests"))]
|
#[cfg(any(test, feature = "tests"))]
|
||||||
// take the lock in tests, we use this to enforce that
|
// take the lock in tests, we use this to enforce that
|
||||||
// a test does not run while a compiler version is being installed
|
// a test does not run while a compiler version is being installed
|
||||||
let _lock = LOCK.lock();
|
let _lock = LOCK.lock();
|
||||||
|
|
||||||
// load the local / remote versions
|
// load the local / remote versions
|
||||||
let versions = svm::installed_versions().unwrap_or_default();
|
let versions = utils::installed_versions(svm::SVM_HOME.as_path()).unwrap_or_default();
|
||||||
let local_versions = Self::find_matching_installation(&versions, &sol_version);
|
|
||||||
let remote_versions = Self::find_matching_installation(&RELEASES.1, &sol_version);
|
|
||||||
|
|
||||||
|
let local_versions = Self::find_matching_installation(&versions, sol_version);
|
||||||
|
let remote_versions = Self::find_matching_installation(&RELEASES.1, sol_version);
|
||||||
// if there's a better upstream version than the one we have, install it
|
// if there's a better upstream version than the one we have, install it
|
||||||
Ok(match (local_versions, remote_versions) {
|
Ok(match (local_versions, remote_versions) {
|
||||||
(Some(local), None) => local,
|
(Some(local), None) => local,
|
||||||
|
@ -191,7 +199,7 @@ impl Solc {
|
||||||
/// Parses the given source looking for the `pragma` definition and
|
/// Parses the given source looking for the `pragma` definition and
|
||||||
/// returns the corresponding SemVer version requirement.
|
/// returns the corresponding SemVer version requirement.
|
||||||
pub fn version_req(source: &Source) -> Result<VersionReq> {
|
pub fn version_req(source: &Source) -> Result<VersionReq> {
|
||||||
let version = crate::utils::find_version_pragma(&source.content)
|
let version = utils::find_version_pragma(&source.content)
|
||||||
.ok_or(SolcError::PragmaNotFound)?
|
.ok_or(SolcError::PragmaNotFound)?
|
||||||
.replace(" ", ",");
|
.replace(" ", ",");
|
||||||
|
|
||||||
|
@ -219,12 +227,14 @@ impl Solc {
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "svm")]
|
#[cfg(feature = "svm")]
|
||||||
pub async fn install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
|
pub async fn install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
|
||||||
|
tracing::trace!("installing solc version \"{}\"", version);
|
||||||
svm::install(version).await
|
svm::install(version).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blocking version of `Self::install`
|
/// Blocking version of `Self::install`
|
||||||
#[cfg(all(feature = "svm", feature = "async"))]
|
#[cfg(all(feature = "svm", feature = "async"))]
|
||||||
pub fn blocking_install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
|
pub fn blocking_install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
|
||||||
|
tracing::trace!("blocking installing solc version \"{}\"", version);
|
||||||
tokio::runtime::Runtime::new().unwrap().block_on(svm::install(version))?;
|
tokio::runtime::Runtime::new().unwrap().block_on(svm::install(version))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -489,7 +499,6 @@ mod tests {
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
// println!("Checking {}", pragma);
|
|
||||||
let source = source(pragma);
|
let source = source(pragma);
|
||||||
let res = Solc::detect_version(&source).unwrap();
|
let res = Solc::detect_version(&source).unwrap();
|
||||||
assert_eq!(res, Version::from_str(expected).unwrap());
|
assert_eq!(res, Version::from_str(expected).unwrap());
|
||||||
|
@ -504,7 +513,7 @@ mod tests {
|
||||||
let _lock = LOCK.lock();
|
let _lock = LOCK.lock();
|
||||||
let ver = "0.8.6";
|
let ver = "0.8.6";
|
||||||
let version = Version::from_str(ver).unwrap();
|
let version = Version::from_str(ver).unwrap();
|
||||||
if svm::installed_versions()
|
if utils::installed_versions(svm::SVM_HOME.as_path())
|
||||||
.map(|versions| !versions.contains(&version))
|
.map(|versions| !versions.contains(&version))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,24 +8,24 @@ pub enum SolcError {
|
||||||
/// Internal solc error
|
/// Internal solc error
|
||||||
#[error("Solc Error: {0}")]
|
#[error("Solc Error: {0}")]
|
||||||
SolcError(String),
|
SolcError(String),
|
||||||
#[error("missing pragma from solidity file")]
|
#[error("Missing pragma from solidity file")]
|
||||||
PragmaNotFound,
|
PragmaNotFound,
|
||||||
#[error("could not find solc version locally or upstream")]
|
#[error("Could not find solc version locally or upstream")]
|
||||||
VersionNotFound,
|
VersionNotFound,
|
||||||
#[error("checksum mismatch")]
|
#[error("Checksum mismatch")]
|
||||||
ChecksumMismatch,
|
ChecksumMismatch,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SemverError(#[from] semver::Error),
|
SemverError(#[from] semver::Error),
|
||||||
/// Deserialization error
|
/// Deserialization error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerdeJson(#[from] serde_json::Error),
|
SerdeJson(#[from] serde_json::Error),
|
||||||
/// Deserialization error
|
/// Filesystem IO error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[cfg(feature = "svm")]
|
#[cfg(feature = "svm")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SvmError(#[from] svm::SolcVmError),
|
SvmError(#[from] svm::SolcVmError),
|
||||||
#[error("no contracts found under {0}")]
|
#[error("No contracts found at \"{0}\"")]
|
||||||
NoContracts(String),
|
NoContracts(String),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
PatternError(#[from] glob::PatternError),
|
PatternError(#[from] glob::PatternError),
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
pub mod artifacts;
|
pub mod artifacts;
|
||||||
|
|
||||||
pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
|
pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::{btree_map::Entry, hash_map};
|
||||||
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
|
||||||
|
@ -195,18 +195,32 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
|
||||||
#[cfg(all(feature = "svm", feature = "async"))]
|
#[cfg(all(feature = "svm", feature = "async"))]
|
||||||
#[tracing::instrument(skip(self, sources))]
|
#[tracing::instrument(skip(self, sources))]
|
||||||
fn svm_compile(&self, sources: Sources) -> Result<ProjectCompileOutput<Artifacts>> {
|
fn svm_compile(&self, sources: Sources) -> Result<ProjectCompileOutput<Artifacts>> {
|
||||||
|
use semver::{Version, VersionReq};
|
||||||
|
|
||||||
// split them by version
|
// split them by version
|
||||||
let mut sources_by_version = BTreeMap::new();
|
let mut sources_by_version = BTreeMap::new();
|
||||||
// we store the solc versions by path, in case there exists a corrupt solc binary
|
// we store the solc versions by path, in case there exists a corrupt solc binary
|
||||||
let mut solc_versions = HashMap::new();
|
let mut solc_versions = HashMap::new();
|
||||||
|
|
||||||
// TODO: Rayon
|
// tracks unique version requirements to minimize install effort
|
||||||
|
let mut solc_version_req = HashMap::<VersionReq, Version>::new();
|
||||||
|
|
||||||
// tracing::trace!("parsing sources");
|
// tracing::trace!("parsing sources");
|
||||||
for (path, source) in sources.into_iter() {
|
for (path, source) in sources.into_iter() {
|
||||||
// will detect and install the solc version
|
// will detect and install the solc version if it's missing
|
||||||
// tracing::trace!("finding version {}", path.display());
|
tracing::trace!("detecting solc version for \"{}\"", path.display());
|
||||||
let version = Solc::detect_version(&source)?;
|
let version_req = Solc::version_req(&source)?;
|
||||||
// tracing::trace!("found {}", version);
|
|
||||||
|
let version = match solc_version_req.entry(version_req) {
|
||||||
|
hash_map::Entry::Occupied(version) => version.get().clone(),
|
||||||
|
hash_map::Entry::Vacant(entry) => {
|
||||||
|
let version = Solc::ensure_installed(entry.key())?;
|
||||||
|
entry.insert(version.clone());
|
||||||
|
version
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::trace!("found installed solc \"{}\"", version);
|
||||||
// gets the solc binary for that version, it is expected tha this will succeed
|
// gets the solc binary for that version, it is expected tha this will succeed
|
||||||
// AND find the solc since it was installed right above
|
// AND find the solc since it was installed right above
|
||||||
let mut solc = Solc::find_svm_installed_version(version.to_string())?
|
let mut solc = Solc::find_svm_installed_version(version.to_string())?
|
||||||
|
@ -236,7 +250,7 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
|
||||||
if let Err(_e) = solc.verify_checksum() {
|
if let Err(_e) = solc.verify_checksum() {
|
||||||
tracing::trace!("corrupted solc version, redownloading...");
|
tracing::trace!("corrupted solc version, redownloading...");
|
||||||
Solc::blocking_install(version)?;
|
Solc::blocking_install(version)?;
|
||||||
tracing::trace!("done.");
|
tracing::trace!("reinstalled solc: \"{}\"", version);
|
||||||
}
|
}
|
||||||
// once matched, proceed to compile with it
|
// once matched, proceed to compile with it
|
||||||
tracing::trace!("compiling_with_version");
|
tracing::trace!("compiling_with_version");
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::error::SolcError;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use semver::Version;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
/// A regex that matches the import path and identifier of a solidity import
|
/// A regex that matches the import path and identifier of a solidity import
|
||||||
|
@ -102,6 +104,25 @@ pub fn resolve_library(libs: &[impl AsRef<Path>], source: impl AsRef<Path>) -> O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads the list of Solc versions that have been installed in the machine. The version list is
|
||||||
|
/// sorted in ascending order.
|
||||||
|
/// Checks for installed solc versions under the given path as
|
||||||
|
/// `<root>/<major.minor.path>`, (e.g.: `~/.svm/0.8.10`)
|
||||||
|
/// and returns them sorted in ascending order
|
||||||
|
pub fn installed_versions(root: impl AsRef<Path>) -> Result<Vec<Version>, SolcError> {
|
||||||
|
let mut versions: Vec<_> = walkdir::WalkDir::new(root)
|
||||||
|
.max_depth(1)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(std::result::Result::ok)
|
||||||
|
.filter(|e| e.file_type().is_dir())
|
||||||
|
.filter_map(|e: walkdir::DirEntry| {
|
||||||
|
e.path().file_name().and_then(|v| Version::parse(v.to_string_lossy().as_ref()).ok())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
versions.sort();
|
||||||
|
Ok(versions)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Reference in New Issue