Vlastimil Brecka
11/16/2025, 11:39 PM// Root
tasks.register("lintAllDebug") {
group = "verification"
dependsOn(
":foo:app:lintAllDebug",
":bar:app:lintAllDebug",
":quax:app:lintAllDebug"
)
}
B) rely on the same named-ness and just ./gradlew lintAllDebugVampire
11/16/2025, 11:44 PMVampire
11/16/2025, 11:44 PMVlastimil Brecka
11/16/2025, 11:47 PM./gradlew lintAllDebug way and let it be depended on?Vampire
11/16/2025, 11:55 PMtasks.check { dependsOn(somehowAllTasksNamedLintAllDebug) } in the root project?Vlastimil Brecka
11/17/2025, 12:04 AMVlastimil Brecka
11/17/2025, 12:05 AMtasks.register("lintAllDebug") {
group = "verification"
dependsOn(... magic)
}Vampire
11/17/2025, 12:07 AMallprojects and for example depend on the task in all projects that are named app if that is sufficiently identifying.
...Vampire
11/17/2025, 12:07 AMVampire
11/17/2025, 12:07 AMVlastimil Brecka
11/17/2025, 12:09 AMVlastimil Brecka
11/17/2025, 12:10 AMYou could for example ensure each and every project has that task, even if it is a no-op and then depend on the task in all projects.how would I do the depending? sounds kinda like what I'm already doing, if you mean some for loop
Vampire
11/17/2025, 12:10 AMVampire
11/17/2025, 12:11 AMsubprojects.forEach { dependsOn("${it.path}:lintAllDebug") }Vampire
11/17/2025, 12:12 AMtasks on it or similar, it should be IP-safe. The project name and project path are immutable once the settings script is finished, so that should be IP-safe I think.Vlastimil Brecka
11/17/2025, 12:12 AM// root
tasks.register("lintAllDebug") {
subprojects.forEach { dependsOn("${it.path}:lintAllDebug") }
}
do you mean like this?Vampire
11/17/2025, 12:13 AMVlastimil Brecka
11/17/2025, 12:13 AMit.tasks contains the taskVampire
11/17/2025, 12:16 AMVlastimil Brecka
11/17/2025, 12:16 AM./gradlew foo can
in that case it will only pickup projects that have the taskVampire
11/17/2025, 12:17 AMVampire
11/17/2025, 12:18 AMVlastimil Brecka
11/17/2025, 12:19 AMtest task, and I want to run test task only on given subset of modules, but I dont want to keep a explicit list because it doesnt scale
would it be smart to have just a "wrapper" task, just to declare the task via my custom name and then aggregate that?
tasks.register("myTest") {
dependsOn "test"
}
so I can then ./gradlew myTest?
or is this "token" task not recommendedVampire
11/17/2025, 12:21 AMVlastimil Brecka
11/17/2025, 12:22 AMcustom and then myCustom as to "annotate" the projects, so their tasks can then be pulled up by the natural aggregator, if you know what I meanVampire
11/17/2025, 12:22 AMtest and myTestVampire
11/17/2025, 12:23 AMtest is the lifecycle task, myTest is the lifecycle taskVlastimil Brecka
11/17/2025, 12:23 AMVampire
11/17/2025, 12:24 AMVampire
11/17/2025, 12:24 AMdependsOn is not a code-smellVampire
11/17/2025, 12:25 AMVlastimil Brecka
11/17/2025, 12:26 AM// Root
tasks.register("lintAllDebug") {
group = "verification"
dependsOn(
":foo:app:lintAllDebug",
":bar:app:lintAllDebug",
":quax:app:lintAllDebug"
)
}
so my original snippet, this is a lifecycle task as well?Vlastimil Brecka
11/17/2025, 12:27 AMVampire
11/17/2025, 12:28 AMlintAllDebug is a lifecycle task tooVampire
11/17/2025, 12:28 AMbuild and check are also lifecycle tasksVampire
11/17/2025, 12:29 AMtest is not a lifecycle task, it is a task that executes the testsVampire
11/17/2025, 12:29 AMcheck is the lifecycle task that depends on test
build is the lifecycle task that depends on check and the lifecycle task assembleVlastimil Brecka
11/17/2025, 12:32 AMdepeondsOn assemble, check,
check just depeondsOn test
so now..how do people plug them selves into check? I'm seeing they do, say in detekt, the static analysis pluginVlastimil Brecka
11/17/2025, 12:32 AMtask.named("check") {
dependsOn detektStuff
}
right?Vampire
11/17/2025, 12:32 AMtasks.check {
dependOn(detekt)
}Vlastimil Brecka
11/17/2025, 12:33 AMtasks.foo { .. } as well?Vlastimil Brecka
11/17/2025, 12:34 AMnamed to see it, it has to be a task defined in that project, right?
so it has to be specialVampire
11/17/2025, 12:36 AMcheck, they have to ensure the lifecycle-base plugin is applied.
If they apply any built-in plugin chances are high the lifecycle-base plugin is also applied.
If not, they can also apply the lifecycle-base plugin directly.
After doing so, the check task is registered and available.
If it is in the context of a Kotlin DSL build script or precompiled Kotlin DSL script plugin, then also the tasks.check accessor is available.
If not, the the tasks.named you used is the right way to go, or val check by tasks.existing { ... } if still in Kotlin.Vampire
11/17/2025, 12:38 AMin order forYes and no. In order forto see it, it has to be a task defined in that project, right?named
named to see a task, the task has to be registered already the moment you call named, for example by applying the lifecycle-base (directly or transitively) before doing the call.
If the task is going to be registered later or you want to configure it if it is registered already or also later, you can do
tasks.named { it == "foo") }.configureEach { ... } for exampleVlastimil Brecka
11/17/2025, 12:39 AMlifecycle-base registers a no-op check tasks to which people can plug into and then global agregator can just pick up all check tasks by name ./gradlew check
is this right? (I mean the empty check task bit, I just inferred it)Vampire
11/17/2025, 12:40 AMVlastimil Brecka
11/17/2025, 12:40 AMVlastimil Brecka
11/17/2025, 12:40 AMVampire
11/17/2025, 12:41 AMVlastimil Brecka
11/17/2025, 2:03 AMVampire
11/17/2025, 2:05 AMgradle-profiler to see where the time is spent. 🤷♂️Vlastimil Brecka
11/17/2025, 2:06 AMVlastimil Brecka
11/17/2025, 3:39 PM// Root
subprojects {
tasks.register("testAllDebugUnitTest") {
group = "verification"
// Lifecycle task
}
tasks.register("testAllReleaseUnitTest") {
group = "verification"
// Lifecycle task
}
}
tasks.register("checkAllDebug") {
group = "verification"
dependsOn(
":foo:app:assemblePreprodDebug",
":foo:app:assembleProdDebug",
":bar:app:assemblePreprodDebug",
":bar:app:assembleProdDebug",
subprojects.collect { "${it.path}:testAllDebugUnitTest" }
)
}
I'm using checkAllDebug to run checks over all of apps in the build, as a basic CICD check of every commit. Works well.
However, when I'm building android app release, that's heavy and I build it separately per app (:foo:app:buildAllRelease)
// Foo app
tasks.named("testAllDebugUnitTest") {
dependsOn(
"testPreprodDebugUnitTest",
"testProdDebugUnitTest",
)
}
tasks.named("testAllReleaseUnitTest") {
dependsOn(
"testPreprodReleaseUnitTest",
"testProdReleaseUnitTest",
)
}
tasks.register("buildAllRelease") {
group = "build"
dependsOn(
":clean",
..
"testAllReleaseUnitTest",
"assembleRelease"
)
}
But now "testAllReleaseUnitTest", obviously references just this project's task, not all of them. So I try the same pattern
tasks.named("testAllReleaseUnitTest") {
dependsOn(
subprojects.collect { "${it.path}:testAllReleaseUnitTest" } <-------------------------
"testPreprodReleaseUnitTest",
"testProdReleaseUnitTest",
)
}
But it returns empty list -- any idea why?Vlastimil Brecka
11/17/2025, 3:41 PMtestAllReleaseUnitTest in root and call that (:testAllReleaseUnitTest), but that will obviously call all the release tasks everywhere, not just that given app's - or should I just eat it?Vampire
11/17/2025, 4:03 PMsubprojects {
tasks.register
is a no-no you are aware, yes?
I mean I think I know why, because gradle project wise it has no childrenExactly
but how can I make it so?You define in the settings script how your project tree looks like But - as you only share half of the information, so guessing here - you probably want to "call" that task on all projects the app project depends on. If that is the case, you should probably totally change the approach. Have an outgoing variant in the projects. (iirc it can have no artifacts just fine) Make sure the outgoing variant has task dependencies on the tasks you want to depend on. Use an artifact view on
compileClasspath or runtimeClasspath or whatever is appropriate, with a component filter that matches only project dependencies, and requesting attributes so that this variant is matched.
This way you actually get the originally requested logic of "trigger the task in the projects that have it and ignore all other projects", as artifact views by default ignore dependencies that do not provide the requested variant.Vampire
11/17/2025, 4:04 PMVampire
11/17/2025, 4:05 PMVlastimil Brecka
11/17/2025, 4:37 PMis a no-no you are aware, yes?yes I ommited that
you probably want to "call" that task on all projects the app project depends on.yes exactly - 2 modes, light check over everything (already works as per yesterday's chat) & a heavy check app specific (it would take like 3x the time to run it over everything, so unnecessary), i.e. to scope it down to just foo app's subgraph
Vlastimil Brecka
11/17/2025, 4:37 PMHave an outgoing variant in the projects.Do you mean this? https://docs.gradle.org/current/userguide/variant_attributes.html I'm only familiar with variants from android
Vampire
11/17/2025, 4:37 PMVlastimil Brecka
11/17/2025, 4:38 PMVampire
11/17/2025, 4:38 PMVlastimil Brecka
11/17/2025, 4:41 PMVampire
11/17/2025, 4:42 PMVampire
11/17/2025, 4:42 PMVampire
11/17/2025, 4:42 PMVampire
11/17/2025, 4:42 PMVampire
11/17/2025, 4:42 PMVampire
11/17/2025, 4:43 PMVlastimil Brecka
11/17/2025, 4:43 PM:foo:app
:foo:data
:foo:ui
?Vlastimil Brecka
11/17/2025, 4:44 PM./gradlew :foo:check runs check over the 3? is that the idea?Vampire
11/17/2025, 4:44 PMfoo with three subprojects app, data, and ui.Vampire
11/17/2025, 4:44 PM:foo:check, then you execute the task check of the project fooVampire
11/17/2025, 4:45 PMcheck in the other three, then it would be what you just saidVlastimil Brecka
11/17/2025, 4:46 PMVlastimil Brecka
11/17/2025, 4:47 PMVlastimil Brecka
11/17/2025, 4:48 PMfoo project might not even declare dependency on app, data, ui, yet be their parent project, because of folder strutcture?Vampire
11/17/2025, 4:49 PMbuild.gradle => irrelevantVampire
11/17/2025, 4:50 PMVampire
11/17/2025, 4:50 PMVampire
11/17/2025, 4:50 PMVampire
11/17/2025, 4:51 PMinclude("foo:bar:baz"), you declare three projects with one callVampire
11/17/2025, 4:51 PMfoo with a subproject bar with a subproject bazVampire
11/17/2025, 4:52 PMfoo/, foo/bar/, and foo/bar/baz, but you can configure it in the settings script to whatever you want.Vlastimil Brecka
11/17/2025, 4:52 PMAs I said a few times, you define in your settings script how the project tree looks likebut how? this is what I'm missing. In settings.gradle I have just the
rootProject.name = "Repo"
include ":foo:app"
include ":foo:data"
include ":foo:ui"
include ":bar:app"
include ":bar:data"
include ":bar:ui"Vampire
11/17/2025, 4:52 PMinclude("baz") and configure its project directory to be foo/bar/baz, then there are no foo or bar projects, even though you have the directory structureVampire
11/17/2025, 4:53 PMbut how? this is what I'm missing. In settings.gradle I have just theI just explained it to you
Vampire
11/17/2025, 4:53 PMVlastimil Brecka
11/17/2025, 4:58 PMinclude ":foo:app"
include ":foo:data"
include ":bar:app"
include ":bar:data"
that then means its 6 projects, foo being parent to app and data etc
does that then mean I can now call say check task on both fooapp an foodata, via just 1 call? or is the ./gradlew check-like syntax always going to be "everything inside build"Vlastimil Brecka
11/17/2025, 4:59 PMsubprojects.collect { .. } should work -- but where do I define it, since in this case :foo doenst have a build,.gradle -- I can just create a semi-empty build.gradle in the foo folder, without any plugins applied whatsoever?Vlastimil Brecka
11/17/2025, 5:00 PMvariants you mentioned?Vampire
11/17/2025, 5:04 PMfoo level that depends on the subprojects.
Well, iirc, you could "go to" the foo folder and do ../gradlew check there to only search in the subtree of foo,
or you can do ./gradlew -p foo check, those should also do what you want iirc.Vampire
11/17/2025, 5:05 PMfoo, just create its build script that you omitted so far.Vampire
11/17/2025, 5:05 PMcheck task, but apply the lifecycle-base plugin that adds the standard check task as described yesterday.Vlastimil Brecka
11/17/2025, 5:08 PMfoo is a project implicitly, regardless if it has a build script, that means it should see stuff lifecycle-base applies, so check, so I should be able to ./gradlew :foo:check right?
yet I'm not .. Cannot locate tasks that match ':foo:check' as task 'check' not found in project ':foo.Vampire
11/17/2025, 5:08 PMVampire
11/17/2025, 5:09 PMlifecycle-base plugin, no-one applied the lifecycle-base plugin, so the tasks it registers are not present.Vlastimil Brecka
11/17/2025, 5:09 PMVlastimil Brecka
11/17/2025, 5:09 PMVampire
11/17/2025, 5:09 PMVampire
11/17/2025, 5:10 PMVampire
11/17/2025, 5:10 PMVlastimil Brecka
11/17/2025, 5:13 PMsubprojects.collect but it would only work in foo's children, which would break if the settings looked like
include ":foo:app"
include ":foo:data"
...
include ":base"
(where :foo:data has a dependency on :base)
and therefore I need the variants method
gotcha, thanks!Vampire
11/17/2025, 5:21 PMbase, yes.
Of course you can also do
include ":foo:app:data"
include ":foo:app:ui"
include ":bar:app:data"
include ":bar:app:ui"
And have the "aggregator task" on "app" level.
You could even do this without changing the directory layout, by configuring the project direcotries in the settings script.Vampire
11/17/2025, 5:21 PMVlastimil Brecka
11/17/2025, 5:23 PMbase project inside both foo and bar "subtrees"Vlastimil Brecka
11/17/2025, 5:23 PMVampire
11/17/2025, 5:24 PMbase-1.2.3.jar which is not really helpful and also prone to clash,
but Gradle will also get confused in some situation afair.Vampire
11/17/2025, 5:25 PMgroup too, so it could be that it is not problem if they have different group.
But if group and name is the same, there will be problems.Vlastimil Brecka
11/17/2025, 5:25 PM:foo:foo-base seems rather redundantVampire
11/17/2025, 5:25 PMVlastimil Brecka
11/17/2025, 5:26 PMVlastimil Brecka
11/17/2025, 5:26 PMVampire
11/17/2025, 5:27 PMVampire
11/17/2025, 5:27 PMVampire
11/17/2025, 5:27 PMVlastimil Brecka
11/17/2025, 5:27 PMVampire
11/17/2025, 5:28 PMVlastimil Brecka
11/17/2025, 5:28 PMVlastimil Brecka
11/17/2025, 5:28 PMapi .. thats like 200 of those 😄
foo:api, bar:api, ...Vampire
11/17/2025, 5:32 PMVlastimil Brecka
11/17/2025, 5:32 PMVampire
11/17/2025, 5:33 PMVampire
11/17/2025, 5:33 PMVampire
11/17/2025, 5:34 PMVlastimil Brecka
11/17/2025, 5:35 PMVampire
11/17/2025, 5:35 PMVlastimil Brecka
11/17/2025, 5:35 PM:foo:John 😄 or :bar:JohnVlastimil Brecka
11/17/2025, 5:35 PMVampire
11/17/2025, 5:36 PMVampire
11/17/2025, 5:36 PMVampire
11/17/2025, 5:36 PMVlastimil Brecka
11/17/2025, 5:36 PMVampire
11/17/2025, 5:36 PMVampire
11/17/2025, 5:36 PMVlastimil Brecka
11/17/2025, 5:37 PM:foo:foo-api looks weird (sure I can have file system be different, I'm aware; but the reference will have to be like that, right?)
do people really do it like that? or should I have just :foo-api gradlew wise and then file system wise foo/api?Vampire
11/17/2025, 5:40 PMbut then referencing the moduleWell, some kids do look weird 😄looks weird:foo:foo-api
Vampire
11/17/2025, 5:41 PMdo people really do it like that?Ask the people 🙂
or should I have justWhatever pleases you most and works for you.gradlew wise and then file system wise:foo-api?foo/api
Vampire
11/17/2025, 5:42 PMfoo a standalone build and bar a standalone build and have a composite build that combines them for joint-checking or other stuff.Vlastimil Brecka
11/17/2025, 5:44 PMapi/impl is a pattern we use everywhere, multiple times even within foo, so everything has it basicallyVlastimil Brecka
11/17/2025, 5:49 PMVlastimil Brecka
11/17/2025, 5:53 PMVampire
11/17/2025, 10:53 PM