feat(solc): improve solc detection and reduce install effort (#648)

This commit is contained in:
Matthias Seitz 2021-12-04 18:13:58 +01:00 committed by GitHub
parent 2a3fcbbb40
commit 0464ac9d46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 19 deletions

View File

@ -1,7 +1,7 @@
use crate::{
artifacts::Source,
error::{Result, SolcError},
CompilerInput, CompilerOutput,
utils, CompilerInput, CompilerOutput,
};
use semver::{Version, VersionReq};
use serde::{de::DeserializeOwned, Serialize};
@ -157,17 +157,25 @@ impl Solc {
pub fn detect_version(source: &Source) -> Result<Version> {
// detects the required solc version
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"))]
// take the lock in tests, we use this to enforce that
// a test does not run while a compiler version is being installed
let _lock = LOCK.lock();
// load the local / remote versions
let versions = svm::installed_versions().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 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);
// if there's a better upstream version than the one we have, install it
Ok(match (local_versions, remote_versions) {
(Some(local), None) => local,
@ -191,7 +199,7 @@ impl Solc {
/// Parses the given source looking for the `pragma` definition and
/// returns the corresponding SemVer version requirement.
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)?
.replace(" ", ",");
@ -219,12 +227,14 @@ impl Solc {
/// ```
#[cfg(feature = "svm")]
pub async fn install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
tracing::trace!("installing solc version \"{}\"", version);
svm::install(version).await
}
/// Blocking version of `Self::install`
#[cfg(all(feature = "svm", feature = "async"))]
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))?;
Ok(())
}
@ -489,7 +499,6 @@ mod tests {
]
.iter()
{
// println!("Checking {}", pragma);
let source = source(pragma);
let res = Solc::detect_version(&source).unwrap();
assert_eq!(res, Version::from_str(expected).unwrap());
@ -504,7 +513,7 @@ mod tests {
let _lock = LOCK.lock();
let ver = "0.8.6";
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))
.unwrap_or_default()
{

View File

@ -8,24 +8,24 @@ pub enum SolcError {
/// Internal solc error
#[error("Solc Error: {0}")]
SolcError(String),
#[error("missing pragma from solidity file")]
#[error("Missing pragma from solidity file")]
PragmaNotFound,
#[error("could not find solc version locally or upstream")]
#[error("Could not find solc version locally or upstream")]
VersionNotFound,
#[error("checksum mismatch")]
#[error("Checksum mismatch")]
ChecksumMismatch,
#[error(transparent)]
SemverError(#[from] semver::Error),
/// Deserialization error
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
/// Deserialization error
/// Filesystem IO error
#[error(transparent)]
Io(#[from] std::io::Error),
#[cfg(feature = "svm")]
#[error(transparent)]
SvmError(#[from] svm::SolcVmError),
#[error("no contracts found under {0}")]
#[error("No contracts found at \"{0}\"")]
NoContracts(String),
#[error(transparent)]
PatternError(#[from] glob::PatternError),

View File

@ -3,7 +3,7 @@
pub mod artifacts;
pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
use std::collections::btree_map::Entry;
use std::collections::{btree_map::Entry, hash_map};
pub mod cache;
@ -195,18 +195,32 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
#[cfg(all(feature = "svm", feature = "async"))]
#[tracing::instrument(skip(self, sources))]
fn svm_compile(&self, sources: Sources) -> Result<ProjectCompileOutput<Artifacts>> {
use semver::{Version, VersionReq};
// split them by version
let mut sources_by_version = BTreeMap::new();
// we store the solc versions by path, in case there exists a corrupt solc binary
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");
for (path, source) in sources.into_iter() {
// will detect and install the solc version
// tracing::trace!("finding version {}", path.display());
let version = Solc::detect_version(&source)?;
// tracing::trace!("found {}", version);
// will detect and install the solc version if it's missing
tracing::trace!("detecting solc version for \"{}\"", path.display());
let version_req = Solc::version_req(&source)?;
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
// AND find the solc since it was installed right above
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() {
tracing::trace!("corrupted solc version, redownloading...");
Solc::blocking_install(version)?;
tracing::trace!("done.");
tracing::trace!("reinstalled solc: \"{}\"", version);
}
// once matched, proceed to compile with it
tracing::trace!("compiling_with_version");

View File

@ -2,8 +2,10 @@
use std::path::{Component, Path, PathBuf};
use crate::error::SolcError;
use once_cell::sync::Lazy;
use regex::Regex;
use semver::Version;
use walkdir::WalkDir;
/// 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)]
mod tests {
use super::*;