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

Enhancement request: public static final variables cause full recompilation of project #13493

Closed
jfbibeau opened this issue Jun 17, 2020 · 2 comments · Fixed by #16495
Closed

Comments

@jfbibeau
Copy link

Currently, as soon as you have a project that has a public static final variable, Gradle will automatically recompile the entire project, even if that constant hasn't changed and the project only contains a single unrelated class change.

Expected Behavior

It would be nice if Gradle could tell whether the constant was really changed or not and avoid full recompilation.

Or if this is not possible, it would be nice if there was a toggle/mode with incremental recompilation with Gradle where we could choose to trade this full recompilation in exchange for only changed file recompilation. This would be really useful to achieve fast IDE iteration times.

Current Behavior

Full recompilation as soon as you change a file in a sourceSet that has a public static final variable.

Context

We are trying to move from the old idea IntelliJ integration (where IntelliJ does the compilation) to the native Gradle+IntelliJ integration, however a significant blocker is the re-compilation time that this introduces since Gradle recompiles much more than only the changed files due to this quirk.

Our mono repo build currently has ~30k instances of this use case (+another 10k in generated code). As much as it might be considered "bad practice", they appear to be here to stay and are in almost every project.

Steps to Reproduce

Self-contained project attached. Note that maven behaves differently as well, pom included in the repro project.

Your Environment

macOS / Java 11 / Gradle 6
gradle_incremental_compilation.zip

@melix
Copy link
Contributor

melix commented Jun 18, 2020

Gradle used to analyze the constants in use but the analysis was wrong. See #1474 for context. Basically, the compilers inline constants, so we can't track where they come from. Imagine your code contains something like this:

int x() {
   return A.CONST_X + 2;
}

and A.CONST_X = 2.

Then the compiler will inline the result of the computation, and the body of the method will, bytecode-wise, be equivalent to:

int x() { return 4; }

We don't have any information to let us know that it came from A. So we had to disable this "smart" analysis. Some compilers actually keep a reference of inlined constants, but not all of them.

This is why we said that whenever a class which has a constant changes, we need to rebuild everything.

The point in this ticket is "but my constants didn't change and you still recompiled everything". That's because while we keep track of constants, we don't keep track of what symbol is used. So, say A contains:

class A {
    public static int C1 = 10;
    public static int C2 = 11;
}

and that it's changed to:

class A {
    public static int C1 = 11;
    public static int C2 = 10;
}

in both cases, the constant set is {10, 11}, but we need to rebuild all consumers of C1 and C2. It would be very expensive to keep track of the symbol/constant value pairs, so we don't do it.

Does it mean we can't do anything?

No. First of all, we could detect that the compiler you are using is actually one of those keeping track of inlined constants. I think this should be the case for all JDK 9+ compilers, but we need to double check that.

Second, for every Java 8+ compiler that we call using the API, we use a compiler plugin to perform some incremental compilation analysis. It means we could actually register where the constants come from in this case. The problem is that we don't always use the compiler API.

@asodja
Copy link
Member

asodja commented Sep 9, 2020

That would be an awesome improvement to build times in our case. The problem is, that constants sometimes need to be inlined (switch statements, annotation parameters etc) so you cannot workaround it with static {} block or static method or some other way. You can of course be careful to have them in some rarely modified class. But any practice can easily break on a project with many contributors and then you have to hunt down those constants that cause full recompilation.

Also I believe constants are really common in Java projects which probably means that full recompilation due to constants is one of the top reasons why incremental compilation was not possible with Gradle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants