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

KT-45777: Compute classpath changes for incremental Kotlin compile #4564

Conversation

hungvietnguyen
Copy link
Contributor

No description provided.

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
@hungvietnguyen hungvietnguyen force-pushed the aprr/hungnv/KT-45777-computeClasspathChanges branch from 165315e to e89a0c7 Compare September 3, 2021 09:22
@hungvietnguyen
Copy link
Contributor Author

Updated PR to fix failed tests:

  • Added commit "KT-45777: Update keep rules for serializable classes"
  • Amended commit "KT-45777: Compute classpath changes for incremental Kotlin compile" (reverted some changes in externalizers.kt)

}
}

/** Expected files to be recompiled for [testCompileLibWithGroovy], which may be overridden in subclasses. */
open fun testCompileLibWithGroovy_expectedFiles(project: Project): Iterable<File> {
Copy link
Contributor

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.

Copy link
Contributor Author

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() {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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()
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@hungvietnguyen
Copy link
Contributor Author

Added 1 more commit to support compile avoidance.

@hungvietnguyen
Copy link
Contributor Author

5/6 commits of this PR have been merged. The remaining commit will be included in the next PR: #4574.

@hungvietnguyen hungvietnguyen deleted the aprr/hungnv/KT-45777-computeClasspathChanges branch September 14, 2021 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants