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

Android Gradle: add javac intermediates to classpath #4723

Merged
merged 7 commits into from Apr 25, 2022

Conversation

kvn-stgl
Copy link
Contributor

This PR is mainly a rebuild of #3867 from @damianw and would fix a lot of issues together with Android (#4676 and #4689 as example). The description is almost the same:

This adds the classes from build/intermediates/javac to the classpath used to compile the code, in order for the compiler to be able to resolve references to Java code (included that which is generated). This resolves issues such as #3488 among probably others.

A test is included which enables viewBinding on a sample project and verifies that the Detekt invocation doesn't produce any errors about unresolved references to the generated classes.

I simply updated the tests and changed that the task detektIntermediatesJavacJarVARIANT depends on the java compile task and not the full assemble task. This should be enough to create the intermediate *.class files and is way faster as the whole packaging process.
Also I wasn't able to reproduce #3952 with this configuration. But maybe someone with deeper gradle knowledge (@chao2zhang ?) could take a look to be sure.

@codecov
Copy link

codecov bot commented Apr 15, 2022

Codecov Report

Merging #4723 (7ccee45) into main (583ab1e) will increase coverage by 0.00%.
The diff coverage is n/a.

@@            Coverage Diff            @@
##               main    #4723   +/-   ##
=========================================
  Coverage     84.71%   84.71%           
- Complexity     3423     3425    +2     
=========================================
  Files           490      490           
  Lines         11255    11256    +1     
  Branches       2069     2069           
=========================================
+ Hits           9535     9536    +1     
  Misses          675      675           
  Partials       1045     1045           
Impacted Files Coverage Δ
...rturbosch/detekt/rules/style/OptionalWhenBraces.kt 94.11% <0.00%> (+0.36%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 583ab1e...7ccee45. Read the comment docs.

}

internal fun Project.registerIntermediatesJavacJarTask(variant: BaseVariant): TaskProvider<Jar> {
Copy link
Member

Choose a reason for hiding this comment

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

Q: Why do we need a new task for getting the intermediates classes? Wouldn't it be sufficient to just include them in the classpath?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest, I thought it was necessary because the PR #3867 also implemented it. But using the path directly is also possible and it saves a lot overhead.

task.from(
layout
.buildDirectory
.dir("intermediates/javac/${variant.name}/classes")
Copy link
Member

Choose a reason for hiding this comment

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

This feels a bit fragile 🤔 IIRC the new Variant API from AGP allowed to access the compilation classpath for each variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, you're right. I found a way to find out the directory directly over the javaCompileProvider. I think that's a much cleaner solution.

setFrom(variant.getCompileClasspath(null).filter { it.exists() })
from(bootClasspath)
from(javaCompileDestination(variant))
}
Copy link
Member

Choose a reason for hiding this comment

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

You should be able to do this:

classpath.setFrom(
    variant.getCompileClasspath(null).filter { it.exists() },
    bootClasspath,
    javaCompileDestination(variant)
}

from(bootClasspath)
from(javaCompileDestination(variant))
}
dependsOn(variant.javaCompileProvider)
Copy link
Member

Choose a reason for hiding this comment

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

Manually adding task dependencies should not be required if the new inputs to this task are properly declared. Including javaCompileDestination(variant) in the classpath should be enough - if not, that's something that should be investigated instead of adding the manual task dependency.

Copy link
Member

Choose a reason for hiding this comment

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

This might be necessary though because DetektTask does not necessarily declare on .java or .class as the sources input.

Copy link
Member

Choose a reason for hiding this comment

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

It doesn't have to, if the properties are wired up correctly. Task dependencies are added implicitly when the input to classpath includes types that carry task dependencies. DirectoryProperty (returned by javaCompileDestination(variant)) is one of those types, and if that's the property that contains the correct path to the .java or .class files then Gradle has all the information it needs to do the right thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good to know, thanks!

val javaCompile = variant.javaCompileProvider.orNull
if (javaCompile == null) {
logger.warn(
"Unable to find Java compiler on variant '{}'. Detekt analysis can show false negatives.",
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to rewrite the warning to make it actionable for users? Even if that's just updating the AGP version.

Actually while I think of it, will this cause an exception if an older version of AGP is used, if javaCompileProvider was only recently added?

Copy link
Member

Choose a reason for hiding this comment

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

We have the compatibility table listed here, so that backward incompatibility with older AGP versions shouldn't be a major concern.

Copy link
Member

Choose a reason for hiding this comment

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

That doesn't address my feedback. If the correct action is to update AGP then the warning message should recommend the update and/or direct users to the compatibility table so they know what to do and why, so that the user understands that using older AGP versions will lead to false positives.

Or if the action to correct this is something else, then that should be shown instead.

Copy link
Member

Choose a reason for hiding this comment

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

Note that the .getJavaCompileProvider() was added back in 2018-08 through this PR.
and .destinationDirectoy was built since Gradle 6.1
https://cs.github.com/gradle/gradle/blob/b593969ee3fe63c50466019722cb847d6cd89f4c/subprojects/language-jvm/src/main/java/org/gradle/api/tasks/compile/AbstractCompile.java?q=AbstractCompile

I can't tell easily which AGP version is incompatible with this call, but I assume the current PR is good enough that if customer's build is broken from detekt, it is likely broken from many other plugins and the AGP itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

.getJavaCompileProvider was introduced with AGP 3.3 (https://developer.android.com/studio/releases/gradle-plugin#3-3-0), which means Gradle 4.10.

In the end, I don't know what I should recommend to the user. The .destinationDirectory should work since Gradle 6.1 (thanks chao2zhang for the research) and detekt has already the minimum of Gradle 6.1+ in the Readme.

Copy link
Member

@cortinico cortinico left a comment

Choose a reason for hiding this comment

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

Change looks good to me. Thanks for adding tests

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

Successfully merging this pull request may close these issues.

None yet

4 participants