-
Notifications
You must be signed in to change notification settings - Fork 88
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
fix incremental behavior when the compilation target has IR merging but no KAPT stub generation #1024
Conversation
50311ae
to
fef828f
Compare
…ut no KAPT stub generation KGP's "classpath snapshot" incremental logic uses a previous build's `.class` files to track references and invalidate files. Assume there are two projects: `:lib1` and `:lib2`. `:lib2` depends on `:lib1`. If a change is made to an existing type in `:lib1` since the last successful build, KGP will inspect the snapshot of `:lib2`'s class files to see if it references anything in `:lib1`. If `:lib2` didn't already reference `:lib1`, then the `:lib2:compileKotlin` task will be treated as `UP_TO_DATE` and will not execute again. Anvil's module and interface merging logic only changes the IR symbols. This means that all merged references are added to the resultant binary, but not to class files. Those changes are invisible to the classpath snapshots. This is why we disable incremental compilation in KAPT stub generation tasks, but it's also a problem in non-KAPT modules that are using other merging annotations. ### The fix If a compilation merges any interfaces or modules, a file is created with their names. The `AnvilPlugin` checks for the existence of this file during task configuration, and disables incremental compilation for that task if it's present. The file won't exist on a fresh build, regardless of whether there will be merging, but that's okay because the build wouldn't be incremental anyway.
fef828f
to
c274d53
Compare
PluginOption( | ||
pluginId = anvilCommandLineProcessor.pluginId, | ||
optionName = "track-source-files", | ||
optionValue = (trackSourceFiles && mode is Embedded).toString(), |
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.
Why not keep this in the Embedded block below since this is Embedded only?
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.
It isn't technically Embedded only -- it's just only true for Embedded
mode. It's now accessed for the IrContributionMerger
parameters and that technically isn't exclusive to Embedded
, so the argument must always be defined.
gradle-plugin/src/gradleTest/java/com/squareup/anvil/plugin/IncrementalTest.kt
Show resolved
Hide resolved
gradle-plugin/src/gradleTest/java/com/squareup/anvil/plugin/IncrementalTest.kt
Show resolved
Hide resolved
) { | ||
stubsTaskProvider.configure { stubsTask -> | ||
stubsTaskProvider.configure { stubsTask -> | ||
if (!mergingIsDisabled()) { |
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.
Is there any downside (or benefit) to moving this check into the config block instead of avoiding the config block entirely?
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.
When the check happens outside the config block, it reads the variant's settings eagerly, as soon as the compilation is configured. When the check happens inside the stub gen task's config block, it has a better chance of catching any late changes made by a convention plugin or something.
|
||
if (variant.variantFilter.trackSourceFiles && !mergingIsDisabled()) { | ||
if (task is AbstractKotlinCompile<*>) { | ||
// The `ir-merges.txt` file indicates that the last compilation resulted in adding module |
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.
This comment is great ❤️
KGP's "classpath snapshot" incremental logic uses a previous build's
.class
files to track references and invalidate files. Assume there are two projects::lib1
and:lib2
.:lib2
depends on:lib1
. If a change is made to an existing type in:lib1
since the last successful build, KGP will inspect the snapshot of:lib2
's class files to see if it references anything in:lib1
. If:lib2
didn't already reference:lib1
, then the:lib2:compileKotlin
task will be treated asUP_TO_DATE
and will not execute again.Anvil's module and interface merging logic only changes the IR symbols. This means that all merged references are added to the resultant binary, but not to class files. Those changes are invisible to the classpath snapshots. This is why we disable incremental compilation in KAPT stub generation tasks, but it's also a problem in non-KAPT modules that are using other merging annotations.
The fix
If a compilation merges any interfaces or modules, a file is created with their names. The
AnvilPlugin
checks for the existence of this file during task configuration, and disables incremental compilation for that task if it's present. The file won't exist on a fresh build, regardless of whether there will be merging, but that's okay because the build wouldn't be incremental anyway.