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::{ 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()
{ {

View File

@ -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),

View File

@ -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");

View File

@ -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::*;