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

Fix for Incremental compilation with modules #23119

Merged
merged 14 commits into from Dec 19, 2022

Conversation

asodja
Copy link
Member

@asodja asodja commented Dec 12, 2022

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:

  1. External modules are not recognized when compiling incrementally
  2. Packages inside the module we compile, but are not recompiled are not recognized by javac

  1. Is solved by always compiling module-info.java
  2. Is a bit harder. We modify GradleStandardJavaFileManager, so when javac tries to list files in CLASS_OUTPUT for module, we return also files in previous output location (normally build/classes/)

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.

@asodja asodja self-assigned this Dec 12, 2022
@asodja asodja force-pushed the asodja/fix-incremental-modules branch from 56106bf to e046445 Compare December 13, 2022 13:36
@asodja asodja changed the base branch from master to release7x December 13, 2022 13:37
@asodja asodja force-pushed the asodja/fix-incremental-modules branch from e046445 to f7398e9 Compare December 13, 2022 13:42
@asodja asodja added this to the 7.6.1 milestone Dec 13, 2022
@asodja asodja linked an issue Dec 13, 2022 that may be closed by this pull request
@asodja asodja modified the milestones: 7.6.1, 8.0 RC1 Dec 14, 2022
@asodja asodja force-pushed the asodja/fix-incremental-modules branch 5 times, most recently from 8b272e9 to 2197739 Compare December 14, 2022 14:57
@gradle gradle deleted a comment from bot-gradle Dec 14, 2022
@asodja asodja force-pushed the asodja/fix-incremental-modules branch from ba66088 to d9257bb Compare December 14, 2022 17:18
@gradle gradle deleted a comment from asodja Dec 14, 2022
@asodja asodja changed the base branch from release7x to master December 15, 2022 09:40
@asodja asodja force-pushed the asodja/fix-incremental-modules branch from d9257bb to c284e9b Compare December 15, 2022 09:40
@asodja asodja changed the base branch from master to release7x December 15, 2022 09:41
@asodja asodja changed the base branch from release7x to master December 15, 2022 09:42
@gradle gradle deleted a comment from bot-gradle Dec 15, 2022
@gradle gradle deleted a comment from bot-gradle Dec 15, 2022
@asodja asodja force-pushed the asodja/fix-incremental-modules branch from c284e9b to c30c658 Compare December 15, 2022 09:43
@asodja asodja requested a review from a team December 15, 2022 10:51
@asodja asodja marked this pull request as ready for review December 15, 2022 10:51
@asodja asodja requested a review from a team as a code owner December 15, 2022 10:51
@asodja asodja requested a review from ghale December 15, 2022 10:51
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)}"]'
Copy link
Member

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?

Copy link
Member Author

@asodja asodja Dec 16, 2022

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.

"--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)}"]'
}
Copy link
Member

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?

Copy link
Member Author

@asodja asodja Dec 16, 2022

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"

Copy link
Member Author

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";
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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.

Copy link
Member Author

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.

if (isIncrementalCompilationAfterFailure()) {
stagedOutputs.forEach(StagedOutput::restoreSpecOutput);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

🤔 The many ifs 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.

Copy link
Member Author

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?

asodja and others added 10 commits December 19, 2022 08:25
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
@asodja asodja force-pushed the asodja/fix-incremental-modules branch from 94564d2 to 5d8adb4 Compare December 19, 2022 07:25
@asodja asodja changed the base branch from master to release December 19, 2022 07:25
@asodja
Copy link
Member Author

asodja commented Dec 19, 2022

@bot-gradle test and merge

@gradle gradle deleted a comment from asodja Dec 19, 2022
@bot-gradle
Copy link
Collaborator

OK, I've already triggered a build for you.

@bot-gradle bot-gradle merged commit 4517d39 into release Dec 19, 2022
asodja added a commit that referenced this pull request Dec 20, 2022
Fix for modules in GradleStandardJavaFileManager is not needed anymore, since we don't change output directories
@blindpirate blindpirate deleted the asodja/fix-incremental-modules branch December 21, 2022 00:25
asodja added a commit that referenced this pull request Jan 9, 2023
Fix for modules in GradleStandardJavaFileManager is not needed anymore, since we don't change output directories
asodja added a commit that referenced this pull request Jan 10, 2023
Fix for modules in GradleStandardJavaFileManager is not needed anymore, since we don't change output directories
bot-gradle added a commit that referenced this pull request Jan 20, 2023
…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>
asodja added a commit that referenced this pull request Feb 16, 2023
bot-gradle added a commit that referenced this pull request Feb 17, 2023
…modules" to 7.6.1

Backport from #23119 to 7.6.1.

Also backport from #23251 to 7.6.1

Fixes #23224

Co-authored-by: Anže Sodja <asodja@gradle.com>
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.

Incremental compilation of java modules is broken with Gradle 7.6
3 participants