Vlastimil Brecka
10/14/2025, 12:51 AMdetektMain tasks, but from within a current project's (app-a) dependency graph (not globally; I have multiple apps in the total build)
tasks.register("detektAllDebug") {
group = "verification"
def projects = collectDependencyProjects(project(":foo:app"))
def tasks = collectTasks(projects, "detektMain")
tasks += "detektMockFakeFaceRecoFakeEsimStableDebug"
tasks += "detektMockRealFaceRecoRealEsimStableDebug"
dependsOn tasks
}
I have something like this
static Set<Project> collectDependencyProjects(project) {
def collectedProjects = [].toSet()
doCollectDependencyProjects(collectedProjects, project)
collectedProjects.remove(project) // Remove root
return collectedProjects
}
static void doCollectDependencyProjects(collectedProjects, project) {
if (collectedProjects.contains(project)) return
collectedProjects.add(project)
def subProjects = project.getConfigurations()
.inject([]) { acc, config -> // Accumulate all
def configName = config.getName()
// Ignore test projects
if (configName != "testImplementation" && configName != "testApi") {
def dependencies = config.dependencies.withType(ProjectDependency)
if (dependencies.size() > 0) {
acc.addAll(dependencies)
}
}
acc
}
.collect { it.getDependencyProject() } // Map
for (subProject in subProjects) {
doCollectDependencyProjects(collectedProjects, subProject)
}
}
static def collectTasks(subprojects, name) {
def allTasks = []
for (project in subprojects) {
def task = project.tasks.findByName(name)
if (task != null) {
allTasks += task
}
}
return allTasks
}
where I recurse into the graph and collect
Works, but I have feeling there is more gradle-y wayVampire
10/14/2025, 7:29 AMVampire
10/14/2025, 7:38 AMdetektMain in all projects, it is often easiest to ensure that every project has a task with that name even if it does nothing and use subprojects.forEach { dependsOn("${it.path}:detektMain") } to depend on all of them.
If it publishes its result as variant like test suites plugin and jacoco plugin do to support report aggregation, you can also use an artifact view to request these variants which will then also trigger the producing tasks.
If not, you could just create such an outgoing configuration that has the necessary task dependency yourself and then depend on it. The artifact can even be an empty file tree afair.Vlastimil Brecka
10/14/2025, 11:34 AMdef projects = collectDependencyProjects(project(":foo:app")), I added is into the register block
Now it's identical as what I do & accomplishes the goal correctly -- but yea idk if it's kosher, or rather how to make it kosherVlastimil Brecka
10/14/2025, 11:37 AM:foo:app (I have :bar:app, :quax:app) etc ... sort of like :foo:assemble assembles foo & it's subgraph -- not everything in the total build
Is that what your suggestion would do? (hard for me to decypher, I'm not so versed in gradle)Vampire
10/14/2025, 11:46 AMforEach would not do that.
The artifact view way would do what you intend.
You could have a look at the test report aggregation and jacoco report aggregation plugins, they do exactly what you want.
The individual projects have outgoing configurations that provide the jacoco report / test report / ..., those outgoing configurations have the respective task dependencies. The aggregator project then uses an artifact view to get those configurations by requesting the respective attributes and thus get the reports to aggregate, having automatically the necessary task dependencies and all without bad-practice cross-project access.Vampire
10/14/2025, 11:49 AMval providerOfFileCollectionWithTheNecessaryTaskDependencies = configurations.compileClasspath.map {
it.incoming.artifactView {
withVariantReselection()
attributes {
//...
}
componentFilter {
it is ProjectComponentIdentifier
}
}.files
}
to get the variant from all project dependencies in the compileClasspath that provide such an outgoing variant.Vampire
10/14/2025, 11:50 AMdetekt plugin already registers such a variant, you just need to request it, if not, you would need to create such outgoing configurations in the respective projects for example by using a convention plugin in those projects.Vampire
10/14/2025, 12:03 PMfoo subproject
configurations.consumable("detektMain") {
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.VERIFICATION))
attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, objects.named("detekt-main"));
}
outgoing.artifact(File("")) { builtBy("detektMain") }
}
and in the root project
dependencies {
implementation(project(":foo"))
}
val bar by tasks.registering {
dependsOn(
configurations.compileClasspath.map {
it.incoming.artifactView {
withVariantReselection()
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.VERIFICATION))
attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, objects.named("detekt-main"));
}
componentFilter {
it is ProjectComponentIdentifier
}
}.files
}
)
}
Now invoking the bar task will trigger :foo:detektMain.Vlastimil Brecka
10/14/2025, 12:07 PMVlastimil Brecka
10/14/2025, 12:07 PMVampire
10/14/2025, 12:10 PM--configure-on-demand.
...
The variant-aware solution should be clean and compatible with all that.Vlastimil Brecka
10/14/2025, 12:12 PMVampire
10/14/2025, 12:16 PMlenient, it does not do what one might think. 🙂
An artifact view is always lenient in regard to only getting the variants from the dependencies that provide it.
Setting it to lenient would additionally ignore almost any exception that can happen, that is most often not what you want.