Slackbot
04/13/2022, 8:16 AMVampire
04/13/2022, 9:19 AMjacoco plugin for each subproject in suproject.afterEvaluate {} block
Actually two reasons to strongly consider not using that plugin imho. The first half means cross-project configuration which introduces project coupling and does not work well with more advanced Gradle optimizations like the upcoming configuration cache, the second half like any afterEvaluate most probably mainly introduces race conditions and timing problems as it for example does not see changes made in later afterEvaluate blocks. Most usages of afterEvaluate are a code smell and should be changed to something else.
• Actually what the SonarQube plugin does also does not sound much cleaner and should for example be replaced by the JaCoCo aggregation plugin to collect all JaCoCo reports instead of doing unsafe cross-project publication as detailed in the Userguide.
But if you want to follow the bad practice of the plugin, just use the same tactic to depend on, using withType<JacocoReport>() as that is lazy and will also catch all tasks of that type added later. Of course your control "println" would still print an empty list, as at the time you evaluate it, the tasks are not yet there.
And you also must not eagerly resolve the provider using stream and toList or you also destroy the laziness, but instead depend on the actual providers.
If you also need to check the skipProject property, you probably have to further follow the bad practice and do the configuration in an afterEvaluate or you are not seeing the skipProejct values that were set as they are not set yet.Maxim Alov
04/13/2022, 9:49 AMtasks.withType<SonarQubeTask>().configureEach {
val jacocoTasks = project.subprojects.stream()
.map { p->
p.tasks.withType<JacocoReport>()
}
.collect(Collectors.toList())
dependsOn(jacocoTasks)
}```
Regardless of bad practices this snippet follows, as well as both 3rd-party plugins (cross-config, non-laziness, etc), my main concern is -
how to make
some task A which is supposed to be invoked from the root project (SonarQubeTask)
to depend on all tasks of a particular type (JacocoReport) from every subproject (given that their names might be dynamic)?
——
Also, what could be “the best practices” approach to accomplish that? With configuration avoidance/caching in mind.Vampire
04/13/2022, 10:14 AMdependsOn you write is wrong.
Whenever you do a manual dependsOn (except for lifecycle tasks like check or build that are designed to just trigger some other tasks),
it is a sign of something not being idiomatic.
Usually some task A does not depend on some other task B being executed.
Usually some task A needs the output of some other task B as input.
So task A should use the corresponding output property of task B as value for its input property and thus get the task dependency automatically implied.
But this is only to be done within one project.
Cross-project you then have configurations or variants to share outputs of tasks with other tasks.
For more information on how to do that properly, you can read https://docs.gradle.org/current/userguide/cross_project_publications.html
And especially for aggregating JaCoCo results cross-project there is even https://docs.gradle.org/current/userguide/jacoco_report_aggregation_plugin.html.
It adds a task for an aggregated JaCoCo report, but it also provides the JaCoCo result files needed for the report and thus needed for SonarQube in an added configuration
like described in above mentioned cross project publication section.Maxim Alov
04/13/2022, 10:25 AM,/gradlew sonarqube from the root project still requires all jacoco XML reports to be generated in advance, so I have to run:
./gradlew jacocoTestReport jacocoTestReport<BuildVariant> sonarqube
(Which relies on sonarqube.mustRunAfter(jacocoReport)).
Do you think it doesn’t work because those 3rd-party plugins (com,vanniktech.android.junit.jacoco, org.sonarqube) are not implemented properly?
So they don’t work correctly when i try to do whatever looks logical, because of internal bad-timing, concurrency and code-smell?
So that means, these plugins prevent me from having what I want to achieveMaxim Alov
04/13/2022, 9:25 PMtasks.withType<SonarQubeTask>().configureEach {
rootProject.subprojects.forEach { p ->
dependsOn(p.tasks.withType<JacocoReport>())
}
}
now, ./gradlew sonarqube also involves running all test<Variant>UnitTest and jacocoTestReport<Variant> tasks first, generation jacoco reporst, so SonarQube could find them. My principle here was to rely strongly on liveness of withType() method w/o adding extra complexity that might let that liveness averted.
@Vampire what do you think, how compliant with the best practices that code snippet might be? My main concern is whether this code compliant with Configuration Avoidance? Regardless of usage dependsOn.Vampire
04/13/2022, 9:42 PMMaxim Alov
04/14/2022, 3:53 PMwithType<JacocoReport>? This filter can collect both jacocoTestReportDebug and jacocoTestReportRelease variants of the task. When I build for release build type, how could I exclude the latter from consideration (ignore it completely)? Basically, I want this code snippet to only work for debug-variants of the tasks (android subprojects), but still collect jacocoTestReport (“unscoped”) tasks for pure Java/Kotlin subprijects.Vampire
04/14/2022, 4:34 PMtasks.withType<JacocoReport>().matching { !it.name.endsWith("Release") } maybe?
Actually this will again work against task configuration avoidance though, as for matching all tasks on the left have to be realized to be given to the predicate.Vampire
04/14/2022, 4:34 PMMaxim Alov
04/14/2022, 4:51 PMwithType and hence, with configuration avoidance? Unless we use matching for some purpose.
Is there any use case, where we could use matching for whatever reason to filter tasks, but preserving configuration avoidance?Vampire
04/14/2022, 4:54 PMmatching, the task must be realized, so you cannot have configuration avoidance while using matching. That's also the reason you should never do tasks.matching if you can avoid it, because it would cause each and every task to be eagerly realized, but always do a withType before that at least only tasks of that type are realized due to the matching.
As I said, if you want to do it while preserving configuration avoidance, you could for example enumerate the task names manually, using tasks.named instead.