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.