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 for Incremental compilation with modules #23119
Conversation
56106bf
to
e046445
Compare
e046445
to
f7398e9
Compare
8b272e9
to
2197739
Compare
ba66088
to
d9257bb
Compare
d9257bb
to
c284e9b
Compare
c284e9b
to
c30c658
Compare
...gradle/java/compile/incremental/BaseIncrementalCompilationAfterFailureIntegrationTest.groovy
Outdated
Show resolved
Hide resolved
...rg/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationIntegrationTest.groovy
Outdated
Show resolved
Hide resolved
description | modulePathParameter | ||
"--module-path=<modules>" | '["--module-path=\${classpath.join(File.pathSeparator)}"]' | ||
"--module-path <modules>" | '["--module-path", "\${classpath.join(File.pathSeparator)}"]' | ||
"-p <modules>" | '["-p", "\${classpath.join(File.pathSeparator)}"]' |
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 do we need to test all these options? I think this test should use one of them.
Maybe add a separate test that checks if all these options do the same thing?
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 is because of a bug we had, where we didn't parse --module-path=<modules>
and -p
correctly. And with that we snapshot module-path only when it is set in --module-path <modules>
form. But this is actually a problem of DefaultJavaCompileSpec
, so I moved test there and removed this where block.
...rg/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationIntegrationTest.groovy
Outdated
Show resolved
Hide resolved
"--module-path=<modules>" | '["--module-path=\${classpath.join(File.pathSeparator)}"]' | ||
"--module-path <modules>" | '["--module-path", "\${classpath.join(File.pathSeparator)}"]' | ||
"-p <modules>" | '["-p", "\${classpath.join(File.pathSeparator)}"]' | ||
} |
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.
🤔 Seems like we are duplicating the same test setup here thrice. Can we somehow reduce the redundancy?
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.
Removed duplicated test and created one test that has two variants: "with inferred module-path" and "with manual module-path"
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.
If you have in mind third test "incremental compilation works for multi-module project with manual module paths" -> this one is a bit different, here we actually have Java multi-module project: so multiple directories with module-info in one project.
@@ -42,6 +42,10 @@ | |||
import java.util.Set; | |||
|
|||
abstract class AbstractRecompilationSpecProvider implements RecompilationSpecProvider { | |||
|
|||
private static final String MODULE_INFO_CLASS = "module-info"; | |||
private static final String PACKAGE_INFO_CLASS = "package-info"; |
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 _CLASS
suffix is a bit misleading for me. How about MODULE_INFO_NAME
etc. instead?
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.
What do you think about _CLASS_NAME? I am thinking to use that, since we use these two for example when calling:
sourceFileClassNameConverter.getRelativeSourcePaths(MODULE_INFO_CLASS_NAME)
Where parameter of getRelativeSourcePaths is className
:
Set<String> getRelativeSourcePaths(String className)
@@ -29,6 +29,7 @@ public class RecompilationSpec { | |||
private final Collection<String> classesToProcess = new LinkedHashSet<>(); | |||
private final Collection<GeneratedResource> resourcesToGenerate = new LinkedHashSet<>(); | |||
private String fullRebuildCause; | |||
private boolean isIncrementalCompilationOfJavaModule; |
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 complicated name is a code smell to me. It seems to combine two things: 1) whether recompilation was incremental, and 2) whether it includes a Java module file.
It seems to me that 1) is already captured by fillRebuildCause
, no? If it's empty, we are doing an incremental compilation?
I wonder if we could capture this better. That said, I think it's fine to follow up in a separate PR if it makes merging this quickly easier.
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 is some data duplication indeed. I removed this from recompileSpec and simplified it a bit. I left it in JavaCompileSpec
for now, since it's needed later when compiling. If we separate these two informations, we would need to set isModule
also in cases when it's not incremental. So it needs some more work as you said.
...n/java/org/gradle/api/internal/tasks/compile/incremental/transaction/CompileTransaction.java
Outdated
Show resolved
Hide resolved
...c/main/java/org/gradle/api/internal/tasks/compile/reflect/GradleStandardJavaFileManager.java
Outdated
Show resolved
Hide resolved
if (isIncrementalCompilationAfterFailure()) { | ||
stagedOutputs.forEach(StagedOutput::restoreSpecOutput); | ||
} | ||
} |
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 many if
s here are a code smell. I think this would be better if we extracted these methods to an interface, and used a no-op implementation for when incremental-after-failure is disabled.
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 see what you mean. Yes, we could split that, that would makes sense. I would do that next time we visit that class maybe. Since we might need to do some other changes, like splitting tests. And I think it's still somehow manageable, since this is used just in these three methods.
What do you think?
This shows that there is an issue also for packages that are exported, but are not recompiled.
…but not recompiled We achieve that by returning also previous compiled classes when javac requests CLASS_OUTPUT files
…ure is disabled That way we keep previous behaviour and in case of problems, users can easily rollback.
Co-authored-by: Lóránt Pintér <lorant@gradle.com>
…t, also reduce tests duplication in CrossTaskIncrementalJavaCompilationIntegrationTest
94564d2
to
5d8adb4
Compare
@bot-gradle test and merge |
OK, I've already triggered a build for you. |
Fix for modules in GradleStandardJavaFileManager is not needed anymore, since we don't change output directories
Fix for modules in GradleStandardJavaFileManager is not needed anymore, since we don't change output directories
Fix for modules in GradleStandardJavaFileManager is not needed anymore, since we don't change output directories
…lation after a failure Changing compile output folder to a temporary folder for incremental compilation after failure causes some issues with modules (#23067) and with annotation processors (#23066). But to restore outputs we can just do (not necessarly in the given order): 1. restore stale classes and resources that were deleted 2. remove all newly generated classes and resources 3. restore any overwritten classes (this happens in practice rarely, and only on the incremental compilation, e.g. if we have `B.java` with `class B` and we don't change that file and we create `B1.java` with `class B`). We already do 1) in the current implementation, for 2) we can use Compiler API data we generate for incremental annotation processors and for "source to class name" mapping. For step 3) I implemented a [CompilationClassBackupService.java](https://github.com/gradle/gradle/pull/23226/files#diff-21d9cec673f39ed2a74d60de1b8862dd6fa18e6eea1769209e8ec9fb47b53919) that is injected in the Compiler API and backups any `.class` file during compilation. Backup is done just for "normal compilation". Annotation processors are very limited so this is not needed. They are limited in a way that If two types generate one class/resource, the annotation processor can only be aggregating, for which we always regenerate class/resources and reprocess all types with aggregating annotation. Since we track types also for Groovy compilation, the same method for restoring outputs works well also with Groovy and Groovy/Java joint compilation. --- Todo: - [x] When #23119 gets merged to master, revert changes done in [GradleStandardJavaFileManager](https://github.com/gradle/gradle/blob/5d8adb4002742924f7327db670eb8f2fffff9414/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/GradleStandardJavaFileManager.java#L100-L111). Co-authored-by: Anže Sodja <asodja@gradle.com>
Fixes #23067 and also makes sure, that if incremental compilation after failure is disabled we don't change compile outputs. So in the case of some troubles, users can easily use old behavior.
This is a bit complicated fix. There are basically 2 problems we need to handle, because we change output location of javac:
Tested also with the Elastic reproducer.
I also found a bug in snapshotting of manual module-path. If user sets
--module-path=<modules>
then we don't snapshot that path, but if user sets--module-path <modules>
then we snapshot module path. The problem is, that we parse only the second option from the compile arguments. That is fixed now.