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::{
|
||||
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()
|
||||
{
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in New Issue