fix(solc): only modify files that are required to compile the project (#1050)
This commit is contained in:
parent
42ead6252d
commit
1e1aba19b1
|
@ -12,7 +12,7 @@ use rand::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeSet, HashMap},
|
collections::{BTreeSet, HashMap, HashSet, VecDeque},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,6 +25,13 @@ pub struct MockProjectSkeleton {
|
||||||
pub libraries: Vec<MockLib>,
|
pub libraries: Vec<MockLib>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MockProjectSkeleton {
|
||||||
|
/// Returns a list of file ids the given file id imports.
|
||||||
|
pub fn imported_nodes(&self, from: usize) -> impl Iterator<Item = usize> + '_ {
|
||||||
|
self.files[from].imports.iter().map(|i| i.file_id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a virtual project
|
/// Represents a virtual project
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct MockProjectGenerator {
|
pub struct MockProjectGenerator {
|
||||||
|
@ -272,11 +279,27 @@ impl MockProjectGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the file for the given id
|
||||||
|
pub fn get_file(&self, id: usize) -> &MockFile {
|
||||||
|
&self.inner.files[id]
|
||||||
|
}
|
||||||
|
|
||||||
/// All file ids
|
/// All file ids
|
||||||
pub fn file_ids(&self) -> impl Iterator<Item = usize> + '_ {
|
pub fn file_ids(&self) -> impl Iterator<Item = usize> + '_ {
|
||||||
self.inner.files.iter().map(|f| f.id)
|
self.inner.files.iter().map(|f| f.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all file ids that are source files or imported by source files
|
||||||
|
///
|
||||||
|
/// In other words, all files that are relevant in order to compile the project's source files.
|
||||||
|
pub fn used_file_ids(&self) -> impl Iterator<Item = usize> + '_ {
|
||||||
|
let mut file_ids = BTreeSet::new();
|
||||||
|
for file in self.internal_file_ids() {
|
||||||
|
file_ids.extend(NodesIter::new(file, &self.inner))
|
||||||
|
}
|
||||||
|
file_ids.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
/// All ids of internal files
|
/// All ids of internal files
|
||||||
pub fn internal_file_ids(&self) -> impl Iterator<Item = usize> + '_ {
|
pub fn internal_file_ids(&self) -> impl Iterator<Item = usize> + '_ {
|
||||||
self.inner.files.iter().filter(|f| !f.is_external()).map(|f| f.id)
|
self.inner.files.iter().filter(|f| !f.is_external()).map(|f| f.id)
|
||||||
|
@ -469,6 +492,15 @@ pub enum MockImport {
|
||||||
External(usize, usize),
|
External(usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MockImport {
|
||||||
|
pub fn file_id(&self) -> usize {
|
||||||
|
*match self {
|
||||||
|
MockImport::Internal(id) => id,
|
||||||
|
MockImport::External(_, id) => id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Container of a mock lib
|
/// Container of a mock lib
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct MockLib {
|
pub struct MockLib {
|
||||||
|
@ -552,6 +584,33 @@ impl Default for MockProjectSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An iterator over a node and its dependencies
|
||||||
|
struct NodesIter<'a> {
|
||||||
|
/// stack of nodes
|
||||||
|
stack: VecDeque<usize>,
|
||||||
|
visited: HashSet<usize>,
|
||||||
|
skeleton: &'a MockProjectSkeleton,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NodesIter<'a> {
|
||||||
|
fn new(start: usize, skeleton: &'a MockProjectSkeleton) -> Self {
|
||||||
|
Self { stack: VecDeque::from([start]), visited: HashSet::new(), skeleton }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for NodesIter<'a> {
|
||||||
|
type Item = usize;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let file = self.stack.pop_front()?;
|
||||||
|
|
||||||
|
if self.visited.insert(file) {
|
||||||
|
// push the file's direct imports to the stack if we haven't visited it already
|
||||||
|
self.stack.extend(self.skeleton.imported_nodes(file));
|
||||||
|
}
|
||||||
|
Some(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use ethers_solc::{
|
use ethers_solc::{
|
||||||
error::Result,
|
error::Result,
|
||||||
project_util::{
|
project_util::{
|
||||||
mock::{MockProjectGenerator, MockProjectSettings},
|
mock::{MockProjectGenerator, MockProjectSettings, MockProjectSkeleton},
|
||||||
TempProject,
|
TempProject,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -85,7 +85,7 @@ fn can_compile_mocked_modified() {
|
||||||
run_mock(MockProjectSettings::random(), |project, gen| {
|
run_mock(MockProjectSettings::random(), |project, gen| {
|
||||||
project.ensure_no_errors_recompile_unchanged()?;
|
project.ensure_no_errors_recompile_unchanged()?;
|
||||||
// modify a random file
|
// modify a random file
|
||||||
gen.modify_file(gen.file_ids().count() / 2, project.paths(), DEFAULT_VERSION)?;
|
gen.modify_file(gen.used_file_ids().count() / 2, project.paths(), DEFAULT_VERSION)?;
|
||||||
project.ensure_changed()?;
|
project.ensure_changed()?;
|
||||||
project.artifacts_snapshot()?.assert_artifacts_essentials_present();
|
project.artifacts_snapshot()?.assert_artifacts_essentials_present();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -97,7 +97,7 @@ fn can_compile_mocked_modified_all() {
|
||||||
run_mock(MockProjectSettings::random(), |project, gen| {
|
run_mock(MockProjectSettings::random(), |project, gen| {
|
||||||
project.ensure_no_errors_recompile_unchanged()?;
|
project.ensure_no_errors_recompile_unchanged()?;
|
||||||
// modify a random file
|
// modify a random file
|
||||||
for id in gen.file_ids() {
|
for id in gen.used_file_ids() {
|
||||||
gen.modify_file(id, project.paths(), DEFAULT_VERSION)?;
|
gen.modify_file(id, project.paths(), DEFAULT_VERSION)?;
|
||||||
project.ensure_changed()?;
|
project.ensure_changed()?;
|
||||||
project.artifacts_snapshot()?.assert_artifacts_essentials_present();
|
project.artifacts_snapshot()?.assert_artifacts_essentials_present();
|
||||||
|
@ -105,3 +105,27 @@ fn can_compile_mocked_modified_all() {
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a test useful to manually debug a serialized skeleton
|
||||||
|
#[test]
|
||||||
|
fn can_compile_skeleton() {
|
||||||
|
let mut project = TempProject::dapptools().unwrap();
|
||||||
|
let s = r#"{"files":[{"id":0,"name":"SourceFile0","imports":[{"External":[0,1]},{"External":[3,4]}],"lib_id":null,"emit_artifacts":true},{"id":1,"name":"SourceFile1","imports":[],"lib_id":0,"emit_artifacts":true},{"id":2,"name":"SourceFile2","imports":[],"lib_id":1,"emit_artifacts":true},{"id":3,"name":"SourceFile3","imports":[],"lib_id":2,"emit_artifacts":true},{"id":4,"name":"SourceFile4","imports":[],"lib_id":3,"emit_artifacts":true}],"libraries":[{"name":"Lib0","id":0,"offset":1,"num_files":1},{"name":"Lib1","id":1,"offset":2,"num_files":1},{"name":"Lib2","id":2,"offset":3,"num_files":1},{"name":"Lib3","id":3,"offset":4,"num_files":1}]}"#;
|
||||||
|
let gen: MockProjectGenerator = serde_json::from_str::<MockProjectSkeleton>(s).unwrap().into();
|
||||||
|
let remappings = gen.remappings_at(project.root());
|
||||||
|
project.paths_mut().remappings.extend(remappings);
|
||||||
|
project.mock(&gen, DEFAULT_VERSION).unwrap();
|
||||||
|
|
||||||
|
// mattsse: helper to show what's being generated
|
||||||
|
// gen.write_to(ðers_solc::ProjectPathsConfig::dapptools("./skeleton").unwrap(),
|
||||||
|
// DEFAULT_VERSION).unwrap();
|
||||||
|
|
||||||
|
let compiled = project.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
assert!(!compiled.is_unchanged());
|
||||||
|
for id in gen.used_file_ids() {
|
||||||
|
gen.modify_file(id, project.paths(), DEFAULT_VERSION).unwrap();
|
||||||
|
project.ensure_changed().unwrap();
|
||||||
|
project.artifacts_snapshot().unwrap().assert_artifacts_essentials_present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue