Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

preprocessor to skip recompiling tests on non-interface source file changes #252

Draft
wants to merge 32 commits into
base: main
Choose a base branch
from

Conversation

grandizzy
Copy link
Contributor

@grandizzy grandizzy commented Feb 19, 2025

supersedes #198
closes #197

  • rebased with main
  • uses solar instead solang to compute interface representation

Implements preprocessor allowing us to skip recompiling tests on non-interface source file changes. Still testing, but looks like it's working correctly, decreasing single-change build times from ~1 minute to 5s in some cases.

TODOs

@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from 1c02f7f to 325a627 Compare February 19, 2025 08:03
@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from 325a627 to 0f78197 Compare February 19, 2025 08:16
@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from b4c0746 to 7d0edb8 Compare February 19, 2025 14:37
@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from b5ebece to 4025e5a Compare February 19, 2025 16:56
@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from be6c378 to d37cafb Compare February 20, 2025 12:56
@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from 4efbf23 to c86c4aa Compare February 27, 2025 11:03
Comment on lines 792 to 794
if self.cache.preprocessed {
return self.missing_extra_files();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the missing_extra_files logic should always run, it's unrelated to preprocessor, we should always run it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha, will then call it in is_missing_artifacts() only, previously was in is_dirty_impl(), is this OK?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 2d1e59f

}

// Walks over all cache entries, detects dirty files and removes them from preprocessed cache.
fn find_and_remove_dirty_preprocessed(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that we already have the self.cache.preprocessed flag I don't think we need two separate methods? those share most of the logic and only different in a way we traverse imports so I'd prefer to only have a single find_and_remove_dirty to avoid redundancy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 2d1e59f

Comment on lines 931 to 938
if !self.cache.preprocessed {
// Purge all sources on graph resolution error.
self.dirty_sources.extend(files);
} else {
// Purge all sources on graph resolution error and return.
self.cache.files.clear();
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be handled differently depending on this flag

@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch 2 times, most recently from a967188 to 99b5a06 Compare March 4, 2025 08:37
@grandizzy grandizzy force-pushed the klkvr/pp-test-rebase branch from 99b5a06 to 719d4e8 Compare March 4, 2025 08:38
jobs.push((input, profile, actually_dirty));
}
}

cache.add_mocks(mocks);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't this only take effect on the next compiler run? I guess ideally we should detect mocks before searching for dirty files? this means that we need to somehow integrate parsing into caching logic though which might get complex quickly

Copy link
Contributor Author

@grandizzy grandizzy Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep that's correct, but the first compiler run does compile and run all tests anyways so don't think should be a problem to take effect from next run. Do I miss any scenario?

- pass the set of cached mocks to preprocessor
- preprocessor updates the set accordingly (add new mocks and cleanup
  old mocks - handles case when a mock is refactored to non mock impl)
- replace set of mocks in cache
Comment on lines +421 to +422
/// hash of the interface representation of the file, if it's a source file
pub interface_repr_hash: Option<String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: noticed interfaceReprHash is sometimes null in the cache, would it make sense to skip this field when it is None during serialization w/ #[serde(skip_serializing_if = "Option::is_none")]?

Screenshot from 2025-03-05 12-45-41

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, that's null for all files under test / script dirs, will check if any side effect if we don't serialize them but shouldn't


// convert paths on windows to ensure consistency with the `CompilerOutput` `solc` emits,
// which is unix style `/`
sources.slash_paths();

let mut cache = ArtifactsCache::new(project, edges)?;
let mut cache = ArtifactsCache::new(project, edges, preprocessor.is_some())?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the cache changes apply to any preprocessor that modifies sources or specifically to the one implemented in this PR? If the latter, this should be .as_ref().is_some_and(|p| p.some_method())

@@ -481,10 +514,18 @@ impl<L: Language, S: CompilerSettings> CompilerSources<'_, L, S> {

input.strip_prefix(project.paths.root.as_path());

if let Some(preprocessor) = preprocessor.as_ref() {
input =
preprocessor.preprocess(&project.compiler, input, &project.paths, mocks)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take by input reference?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: optimize compilation through preprocessing and smarter caching
4 participants