Hello, we have a Kotlin project with a lot of modu...
# gradle
a
Hello, we have a Kotlin project with a lot of modules (see image). I am looking for recommendation on how to structure it following the latest gradle recommendation (did not look into gradle for a while). We are currently using Gradle Kotlin 7.1 and the following dsl to apply common dependencies to submodules: backend:build.gradle.kts
Copy code
subprojects {
    apply(plugin = "org.jetbrains.kotlin.jvm")
	apply(plugin = "com.google.devtools.ksp")

	repositories {
		maven(url = "<https://repo.osgeo.org/repository/release/>")
		mavenCentral()
	}

    ...

    dependencies {
		implementation(project(":core:core-logging"))
		implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
        ...
}
I have heard that using this approach is not recommended and that one should use
buildSrc
instead. I also use
gradle.properties
to write the dependencies version : eg
kotlinxCoroutinesVersion=1.6.4
but I am not sure if this is the recommended approach. For Plugins I have the following in the root module (eg:
backend:build.gradle.kts
):
Copy code
plugins {
	kotlin("jvm") version "1.7.21" apply false
	kotlin("plugin.serialization") version "1.7.21" apply false
	id("com.google.devtools.ksp") version "1.7.21-1.0.8" apply false
}
Then in the module that needs it I do the following : eg:
backend:backend-frameworks:build.gradle.kts
Copy code
subprojects {

	apply(plugin = "org.jetbrains.kotlin.plugin.serialization")

	dependencies {
        ...
I would love some feedback and some pointers on how I should structure the dependencies using gradle recommended approach. Some bullet points could help : eg: To share dependencies use ABC, to version dependencies use XYZ. I'm pretty sure the way I set it up is not the recommended approach nowadays and I'm willing to change it.
v
I am looking for recommendation on how to structure it following the latest gradle recommendation (did not look into gradle for a while).
https://github.com/jjohannes/idiomatic-gradle
I have heard that using this approach is not recommended
Exact, practically any usage of
allprojects { ... }
or
subprojects { ... }
is bad and instead convention plugins should be used. Whether you write them in
buildSrc
or an included build or a standalone build that you publish doesn't matter and is totally up to you. I personally prefer an included build, or multiple.
I also use
gradle.properties
to write the dependencies version : eg
kotlinxCoroutinesVersion=1.6.4
but I am not sure if this is the recommended approach.
Nope, the recommended approach is using a version catalog: https://docs.gradle.org/current/userguide/platforms.html
Then in the module that needs it I do the following :
Well, again - and here even more as it is in an intermediate project -
subprojects { ... }
is bad as it introduces project coupling and so prevents more sophisticated features like configure-on-demand or parallel configuration, besides that it makes builds harder to understand and harder to maintain. And also requires to use the legacy way of applying plugins
apply(...)
instead of using the recommended
plugins {... }
block.
a
Thank you so much @Vampire, I will look at all the links you provided, that's exactly the kind of infromation I was looking for!
v
Also make sure to only use task-configuration avoidance compatible API. So for example no
task("...")
, no
tasks.withType<...> { ... }
, no
tasks.getByName(...)
, only very conservative
tasks....matching { ... }
if at all, no
tasks.create
, ... https://docs.gradle.org/current/userguide/task_configuration_avoidance.html
a
In some cases I have to use it (eg: third party flag to enable):
Copy code
extensions.getByType<KspExtension>().apply {
		arg("mockative.stubsUnitByDefault", "true")
	}
But I will try to minimize such cases, thanks!
v
What is that against?
Besides that I would write it simply as
Copy code
configure<KspExtension> {
    arg("mockative.stubsUnitByDefault", "true")
}
(yes, without
extensions.
)
If you confused that with
tasks.withType
,
withType
!=
getByType
. And also while you can follow the same rules for all containers (always using
bla.register
, not
bla.create
and so on, it is currently mostly relevant for task collections. As far as I know no other collection is really used lazily yet, so while more future proof, it is not tragic if you don't follow the rules on them. And besides that,
tasks.withType<...>().configureEach { ... }
is fine, that is lazy. But
tasks.withType<...> { ... }
is bad as it would always realize and configure all tasks of that type whether needed or not. (don't ask why, this is because the bad API was there before the task configuration avoidance approach, so it cannot change its meaning easily)