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
KT-45777: Compute classpath changes for incremental Kotlin compile #4564
KT-45777: Compute classpath changes for incremental Kotlin compile #4564
Conversation
to prevent broken serialization caused by missing fields/methods. (Without this commit, the next commit will break tests.)
to organize tested scenarios better and make it easier to see the effects of the next change when the classpath snapshot feature is enabled.
Test: Updated unit tests + incremental compilation integration tests
165315e
to
e89a0c7
Compare
Updated PR to fix failed tests:
|
} | ||
} | ||
|
||
/** Expected files to be recompiled for [testCompileLibWithGroovy], which may be overridden in subclasses. */ | ||
open fun testCompileLibWithGroovy_expectedFiles(project: Project): Iterable<File> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's override the test case instead? I know there is some duplication, but it is better than having expected values overriden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I override the test case now, and extracted the common code to another method.
) | ||
} | ||
|
||
@Test | ||
fun testMoveFunctionFromLibToApp() { | ||
fun testAddDependencyInLib() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There used to be one more incremental build in this test case, asserting that we can run have build -> (change dep) non-incremental build -> (change source) incremental build i.e that we can "recover" after a non-incremental one. Should we restore old behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good catch! I thought about that too and felt the "recovery" check is not necessary.
The reason is that a non-incremental build should be deterministic, regardless of what happened before (with the exception of cache hit with @LocalState
, but we're not testing caching here). So if a build is non-incremental, the next build should always be incremental (given a typical change).
The "recovery" check has a tiny bit of value, but it is added to several test cases making the tests not focused and increasing test execution time.
If the check is important, I can add a separate test just for it, but it's not clear to me which scenario we should use to set up the test.
Also: When classpath snapshot is enabled, app compilation will be incremental right in the second build, so I guess this won't be an issue in the long run when the feature is enabled by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM, I agree there are other incremental scenario tests that cover this.
@@ -122,7 +122,7 @@ abstract class BaseGradleIT { | |||
|
|||
// gradle wrapper version to wrapper directory | |||
private val gradleWrappers = hashMapOf<String, File>() | |||
private const val MAX_DAEMON_RUNS = 30 | |||
private const val MAX_DAEMON_RUNS = 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, why do we need to increase this? The existing infra should restart daemon when MAX_DAEMON_RUNS is reached.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test infra keeps track of the number of active daemons (MAX_ACTIVE_GRADLE_PROCESSES=1
) and the number of times daemons are invoked (MAX_DAEMON_RUNS=30
). I'm not sure why the second is necessary, but if I keep the old number, this PR--which adds more tests--will cross this threshold and start failing the last few tests. I added a comment below:
if (DaemonRegistry.runCountForDaemon(version) >= MAX_DAEMON_RUNS) {
// Warning: Stopping the Gradle daemon while the test suite has not finished running can break tests in weird ways.
// TODO: Find a safe way to do this or consider deleting this code.
stopDaemon(version, environmentVariables)
}
Increasing this threshold to ~40 would have fixed the issue, but the next time someone else runs into this issue, they would need to increase it again (after some significant time debugging if they're not aware of this code), so I increased it to 100 to avoid that trouble.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
} | ||
val workingDir = | ||
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true) | ||
val incrementalJvmCache = IncrementalJvmCache(workingDir, /* targetOutputDir */ null, FileToCanonicalPathConverter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the next PR(s), we can also explore storing the previous compilation in IncrementalJvmCache
so that we can avoid populating it with the previous snapshot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea. It's a bit tricky since we want to compare the changed parts of the classpath only, but I've noted your suggestion to explore later once we have more insights on what parts of the task are taking time.
return result | ||
// Store previous class snapshots in incrementalJvmCache, the returned ChangesCollector result is not used. | ||
val unusedChangesCollector = ChangesCollector() | ||
for (previousSnapshot in previousClassSnapshots) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(next PR) Can you please add more performance-related logging, using info
level is probably ok? It'd be interesting to know how much time is spent in: computing individual ABI snapshots, loading previous, loading current, diffing the two etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, once we've guaranteed correctness, I'll start measuring & optimizing.
private fun getClasspathChanges(knownChangedFiles: ChangedFiles.Known): ClasspathChanges { | ||
// Find current snapshot files that have been changed (added or modified) | ||
val currentSnapshotFiles = classpathSnapshotProperties.classpathSnapshot.files | ||
val addedOrModifiedFiles = knownChangedFiles.modified.toSet() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Gradle APIs to the the list of changes directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done - I've used InputChanges
now. But as we discussed offline, comparing the changed parts only will be incorrect if the classpath contains duplicate classes, so I'm comparing the entire classpath now to be safe. I'll try to address this in the next PR.
Test: Updated existing incremental compilation integration tests
Added 1 more commit to support compile avoidance. |
5/6 commits of this PR have been merged. The remaining commit will be included in the next PR: #4574. |
No description provided.