Hi, I have found a strange difference between Groo...
# community-support
v
Hi, I have found a strange difference between Groovy and Koltin scripts behavior. We’re having an internal lightweight variant of @Andres Almiray’s Kordamp plugins to provide “smart” directory structure of the multiproject. The version catalog is not found when using the Kotlin DSL throwing
Copy code
org.gradle.api.UnknownDomainObjectException: Extension with name 'libs' does not exist. Currently registered extension names: [ext, base, defaultArtifacts, sourceSets, reporting, javaToolchains, java, testing]	
    at org.gradle.internal.extensibility.ExtensionsStorage.unknownExtensionException(ExtensionsStorage.java:148)	
    at org.gradle.internal.extensibility.ExtensionsStorage.getByName(ExtensionsStorage.java:125)	
    at org.gradle.internal.extensibility.DefaultConvention.getByName(DefaultConvention.java:187)	
    at org.gradle.kotlin.dsl.accessors.runtime.RuntimeKt.extensionOf(Runtime.kt:35)	
    at Build_gradle$1$1$1$2.invoke(build.gradle.kts:15)	
    at Build_gradle$1$1$1$2.invoke(build.gradle.kts:14)
Groovy version:
Copy code
plugins {
    id 'com.agorapulse.gradle.root-project'
}

gradleProjects {
    subprojects {
        dir('libs') {
            repositories {
                mavenCentral()
            }
            dependencies {
                implementation libs.commonsLang
            }
        }
    }
}
Kotlin version:
Copy code
import com.agorapulse.gradle.root.ProjectsExtension

plugins {
    id("com.agorapulse.gradle.root-project")
}

extensions.configure<ProjectsExtension> {
    subprojects {
        dir("libs") {
            repositories {
                mavenCentral()
            }

            dependencies {
                add("implementation", libs.commonsLang)
            }
        }
    }
}
Reproducer with build scan links can be found here https://github.com/musketyr/gradle-kotlin-libs-issue
v
From the top of my head it is already strange and a sign that something is broken, that you have to use
extensions.configure<ProjectsExtension> { ... }
, as
gradleProjects { ... }
should work just fine. Besides that, I guess as soon as you stop to use the highly discouraged and problematic
subprojects { ... }
, your issue would go away anyway.
a
FWIW IIUTC this
subprojects
section is not Gradle’s but from the custom DSL exposed by Vladimir’s plugin based on the Kordamp plugins
v
Which internally calls the built-in
subprojects { ... }
and thus has the same problems. ;-)
a
Perhaps it does, perhaps it doesn’t. It’s been a long time since I’ve looked at the code. I thought it would call project.children or similar
v
Which wouldn't be any better.
The problem is doing cross project configuration. This introduces project coupling and disturbs or disables some more sophisticated Gradle features or optimizations
a
I suppose there’s now a better way to do it? Hopefully not over engineered requiring more than a few lines of code as it did before
v
Whether you use
subprojects { ... }
or
allprojects { ... }
or
project(...) { ... }
or any other means does not really matter
The "better way" is to use convention plugins in
buildSrc
or an included build, for example implemented as precompiled script plugins.
a
Regardless of that, the original question is regarding behavior that works with Groovy but fails with Kotlin
v
Yes, and as I said, from the top of my head, i.e. without investigating and on mobile, I think not using that bad practice will resolve the issue. It is most probably a combination of the differences of Groovy as highly dynamic duck-typed language and Kotlin as statically compiled language, combined with the discouraged practices used. But as I said, just a guess and recommendation. If you know how to resolve it while keeping them stuff, be my guest to chime in. :-)
a
I don’t use Kotlin so I wouldn’t know how to fix this other than discouraging Vladimir from using it
v
Or maybe it's a timing and scope issue. For example that the block is evaluated before the version catalog extension was created and Groovy can use the extension from the root project while Kotlin stops in not finding it on the sub project. It maybe the extension is not added because the Java library plugin is used through some settings script API where it might be too late then. ...
a
As you may understand that is a solution but unlikely the one that Vladimir seeks
v
You never know. And imho it is never wrong to make people aware they are using discouraged bad practice and that there might be better ways. Whether they follow the suggestion or not it's then their decision. But from my experience in almost all cases those people are simply not aware they are doing something they shouldn't do. :-)
a
Yeah, like using Kotlin where Groovy works
v
I don’t use Kotlin
You should though. :-) You immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio. :-)
Yeah, like using Kotlin where Groovy works
Sure, using Groovy is one option too. For various reasons a bad one in my personal opinion, but yes. But it was pointless to me to suggest that, as he is aware that that works. :-)
a
Type safety in build scripts. Narf! That argument will never work for me, so no. I’m fine with Groovy and will keep using it til Gradle removes it.
👌 1
v
I’ve just left for barber and lunch and now this happens 😱 between the lines, you pointed to the good direction @Vampire - I’m able to access
libs
via the
rootProject
. I’m not sure if this is bug or feature 🤷‍♂️ this is all about DX vs performance. having the predefined behavior for a projects in certain directories makes it much easier for developer to add new projects. this is mostly done by a bunch of plugins but I’m trying to avoid managing the dependencies within plugins. after all experiments with trying to use BOMs causing instability in the dependency graph, we’ve happily switched to version the catalogs. this is the valid snippet
Copy code
gradleProjects {
    subprojects {
        dir("libs") {
            repositories {
                mavenCentral()
            }

            dependencies {
                add("implementation", rootProject.libs.commonsLang)
            }
        }
    }
}
and I can use
gradleProjects { }
as well, I’m not sure why it haven’t worked before when I was initially trying (but it doesn’t fix the
libs
access) What feels wrong is that in Kotlin DSL one trust what’s IntelliJ is showing because of the strong typing. and then it looks you can access the property and you actually can’t.
v
having the predefined behavior for a projects in certain directories makes it much easier for developer to add new projects.
Really? There is one line difference. If you put the common configuration into some convention plugin, then the developer that adds a new project applies that one plugin and the conventions are in effect in his project. Imho, that is not so much boilerplate. But use whatever makes you happy and works for you. As I said, I just give recommendations.
after all experiments with trying to use BOMs causing instability in the dependency graph, we’ve happily switched to version the catalogs.
BOMs / platforms and version catalogs are not mutually exclusive. They are complementing features with different use-cases. A version catalog is a list of coordinates and versions that per-se have not any influence on dependency resolution. But they are available to pick from using
libs...
with having the coordinates and versions declared centrally in one place. And additionally when using a TOML as version catalog, just changing a version does not make all the tasks out-of-date like the legacy alternatives like having a class with constants in
buildSrc
would do. A BOM or platform is for influencing version resolution. It effectively is a bundle of version constraints on which you can depend on a whole. You can use it to upgrade transitive dependencies without uncleanly depending on them. You can use it to downgrade transitive dependencies without depending on them. ... And you can use the version catalog entries in the platform project too as those are only a coordinate / version tuple. Also, if what you are after is just adding multiple dependencies easily, you can also simple define a bundle in the version catalog. Then the project could easily depend on the whole bundle at once. And the repository declaration is imho anyway better kept in
dependencyResolutionManagement [ repositories { ... } }
in the settings script, where it then is used uniformly for all projects, especially if you forbid project-specific repositories being added. 🙂
What feels wrong is that in Kotlin DSL one trust what’s IntelliJ is showing because of the strong typing. and then it looks you can access the property and you actually can’t.
That's one of the effects of the bad practice I mentioned. In Kotlin DSL build scripts you only get type-safe accessors for things that Gradle knows will be there at runtime. That means, the
plugins { ... }
block of that build script is extracted, applied to a dummy project, and then this project is investigated about what the plugins added. For these things that were added (and are supported by the algorithm) the type-safe accessors are generated and can be used in the build script. So by doing this cross-project configuration, the accessor is present when compiling that build script. But then it fails at execution time as you do not evaluate the accessor against the project where Gradle knew it will work, but against the subproject where it does work. So actually the IDE is correct in not showing an error, as the accessor itself is present and can be compiled, it is just that at runtime it fails as it does not find what it expects to find.
👌 1
v
I’ve just out of curiosity tried to use precompiled plugin and it is not working.
libs
is not recognized at all (not even on the
rootProject
) https://github.com/musketyr/gradle-kotlin-libs-issue/blob/chore/precompiled-plugins/buildSrc/src/main/kotlin/common-dependencies.gradle.kts
v
Yes, the
libs...
accessors you indeed cannot use unless https://github.com/gradle/gradle/issues/15383 is implemented or you use the hack-around that I documented in https://github.com/gradle/gradle/issues/15383#issuecomment-779893192. Without the hack-around you would need to use the string-y API like
versionCatalogs.named("libs").findLibrary("commonsLang")
and so on
v
what a pity it’s not implemented yet. the ticket is 3 and half year old 😞
thanks for the workaround! it just looses the beauty of type checking
v
And additionally, you need to fix the spelling error you made in
library.gradle.kts
, and the file is not used as you configure
build.gradle.kts
as build script in your settings script. And you should add a namespace to your precompiled script plugin, either by adding a
package
statement, or by renaming the file. Plugin IDs without namespace (i.e. without dot) should be reserved to the built-in plugins.
So this makes your example work without the hack-around to use `libs.`:
Copy code
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 2a80c21..876c922 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -1,6 +1,5 @@
 plugins {
     `kotlin-dsl`
-    `kotlin-dsl-precompiled-script-plugins`
 }

 repositories {
diff --git a/buildSrc/src/main/kotlin/common-dependencies.gradle.kts b/buildSrc/src/main/kotlin/common-dependencies.gradle.kts
index bfea837..33d2397 100644
--- a/buildSrc/src/main/kotlin/common-dependencies.gradle.kts
+++ b/buildSrc/src/main/kotlin/common-dependencies.gradle.kts
@@ -1,3 +1,5 @@
+package my
+
 plugins {
     `java-library`
 }
@@ -7,5 +9,5 @@ repositories {
 }

 dependencies {
-    implementation(libs.commonsLang)
+    implementation(versionCatalogs.named("libs").findLibrary("commonsLang").orElseThrow(::AssertionError))
 }
diff --git a/libs/library/library.gradle.kts b/libs/library/library.gradle.kts
index 1017f50..2d88eec 100644
--- a/libs/library/library.gradle.kts
+++ b/libs/library/library.gradle.kts
@@ -1,3 +1,3 @@
 plugins {
-    `commond-dependencies`
+    my.`common-dependencies`
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 7874056..15d819a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -9,5 +9,5 @@ rootProject.name = "gradle-kotlin-libs-issue"
 include("library")

 project(":library").projectDir = file("libs/library")
-project(":library").buildFileName = "build.gradle.kts"
+project(":library").buildFileName = "library.gradle.kts"
👍 1
v
thanks for the fixes!
👌 1