I'm getting quite frustrated trying to make a conv...
# community-support
s
I'm getting quite frustrated trying to make a convention plugin for my project. I currently have a root project that is doing configuration via a
subprojects
block, which is apparently not the recommended way. So instead I am using an included build
build-logic
project: `settings.gradle`:
Copy code
pluginManagement {
    includeBuild('build-logic')
}
plugins {
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = 'myproject'
include 'core'
include 'libraryA'
include 'libraryB'
`build-logic/build.gradle`:
Copy code
plugins {
  id 'groovy-gradle-plugin
}
with a single source file `src/main/groovy/project-conventions.gradle`:
Copy code
plugins {
    id 'java'
    id 'jacoco'
    id 'maven-publish'

    // version catalog references to plugins don't work from a convention plugin
    // alias(libs.plugins.shadow)
    // alias(libs.plugins.cyclonedx)
    // alias(libs.plugins.spotless)
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}
repositories {
    mavenCentral()
    mavenLocal()
    maven {
        url = "<https://redisson.pro/repo/>"
    }
}

dependencies {
    implementation(libs.myproject.somelibrary)

    implementation(libs.bundles.log4j)

    testImplementation "org.junit.jupiter:junit-jupiter-api"
    testImplementation "org.mockito:mockito-core"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
}

test {
    useJUnitPlatform()
    finalizedBy jacocoTestReport
}

jacocoTestReport {
    dependsOn tasks.withType(Test) // tests are required to run before generating the report
    reports {
        xml.required = true
        html.required = true
    }
}
I then have other sub projects try to reference this plugin: `core/build.gradle`:
Copy code
plugins.apply 'project-conventions'
also tried:
Copy code
plugins {
  id 'project-conventions'
}
but this doesn't work. I get an error:
Copy code
❯ gradle build

FAILURE: Build failed with an exception.

* Where:
Build file '/home/scott/dev/myproject/core/build.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':core'.
> Plugin with id 'project-conventions' not found.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at <https://help.gradle.org>.

BUILD FAILED in 517ms
What have I messed up?
t
Have you tried using the plugins DSL rather than
plugins.apply
?
Copy code
plugins {
  id('project-conventions')
}
s
yes. same result.
actually sorry -slightly different result, still a failure:
Copy code
* What went wrong:
An exception occurred applying plugin request [id: 'project-conventions']
> Failed to apply plugin 'project-conventions'.
   > org.gradle.internal.service.UnknownServiceException: No service of type ClassLoaderScope available in project services.
I used
plugin.apply
because it was mentioned in an issue referencing that error. https://github.com/gradle/gradle/issues/14517
t
I'd strongly suggest using Kotlin for your precompiled script plugins. Your conventions plugin is missing applying the
java
and
jacoco
plugins, and who knows what kind of side-effects that'll have.
s
Actually it does apply those.. I was trimming to post and accidentally removed that part. I've edited my original post.
I will eventually change to kotlin scripts. I've held off because it means everyone on the team (including me) has to adjust to kotlin.
a
using version catalogs to define dependencies inside convention plugins doesn't work out of the box https://github.com/gradle/gradle/issues/15383
s
I am discovering this. I thought this was going to work:
Copy code
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}
but nothing is ever easy.
t
For plugins it's rather easy (declare implementation dependencies to the plugins in your build-logic/build.gradle, use the plugin ID in the precompiled script plugins), for dependencies you can still use the non-typesafe API.
That doesn't solve or explain the UnknownServiceException though…
s
I've converted the convention plugin to use kotlin build scripts... I'm now trying to use https://github.com/radoslaw-panuszewski/typesafe-conventions-gradle-plugin which solves the problem of referencing plugins from the version catalog use
alias(libs.plugins.blah)
but for some reason it is not working for my regular dependencies defined in the convention plugin. For them I get an error:
Copy code
e: file:///home/scott/dev/myproject/build-logic/src/main/kotlin/project-conventions.gradle.kts:55:49 Name expected
e: file:///home/scott/dev/myproject/build-logic/src/main/kotlin/project-conventions.gradle.kts:64:62 Expecting ')'
The line it is referring to is the first reference to the version catalog in my dependencies block:
Copy code
dependencies {
    implementation(platform("my.company:project-platform:${platformVersion}"))
    implementation(libs.libraryA) // <-here
    implementation(libs.libraryB)
v
Some points: • "version catalog references to plugins don't work from a convention plugin" Who said that? Did you try it? Assuming you mean precompiled script plugins, not convention plugins, I mean to remember that it should work just fine with Groovy DSL, one of the few advantages of its duck typing. Only in Kotlin DSL it does not work even with my hack -around. • You should avoid Maven Local whenever possible. It is broken by design in Maven already, and makes your builds slow at best, flaky, unreliable, or even silently misbehaving at worst. If really necessary it should at least always come last in the list an optimally always with a repository content filter to control what exactly is resolved from it. •
plugins.apply
is bad in at least three ways. 1. If you look at the JavaDoc of
plugins
(i.e.
getPlugins
) you learn that you should not use it. 2. You should always use the
plugins { ... }
block to apply plugins. 3. By not using the plugins block, the plugin can also not be found in an included build, as only those are searched for and added to the classpath, so if you want to use the legacy discouraged way to apply the plugin using
apply(...)
, you also have to add the missing part of adding the dependency to the buildscript classpath manually or via the plugins block with "apply false". • You own plugins' ids should always at least contain one dot, either by using a
package
statement or by having it in the file name. Plugin ids without namespace should stay reserved for built-in plugins. • For the
UnknownServiceException
you probably need to share the full
--stacktrace
to get any helpful comments, optimally shared as build
--scan
URL if possible. • The issue you mention is (hopefully) totally unrelated to your situation. There the user wanted to apply a discouraged legacy script plugin (the things you use with "apply from"), from within a precompiled script plugin which is a quite bad idea and in his situation he should just have used a version catalog. • "using version catalogs to define dependencies inside convention plugins doesn't work out of the box" @Adam this statement is a very broad overstatement and wrong in multiple ways. You can use the type-unsafe string-y
VersionCatalogsExtension
in any kind of convention plugin, be it written in Kotlin, Groovy, Java, any other JVM language, and be it written as normal binary plugin or precompiled script plugin in any DSL available. You can use the
libs.
accessors from Groovy DSL precompiled script plugins without any problem thanks to its duck typing. The only thing that does not work out-of-the-box so far is using the
libs.
accessors from Kotlin DSL precompiled script plugins. This needs either my hack-around, or the code generator project another user posted to the relevant issue. • "I thought this was going to work" This only makes the version catalog available in the build scripts of that build, not within the precompiled script plugins it is building, because at application time the version catalog of the consuming build is relevant, not the one if the building build. The setting is just one small part of my hack-around mentioned in that ticket to make the accessors work in Kotlin DSL precompiled script plugins. • "The line it is referring to is the first reference to the version catalog in my dependencies block" you showed two majorly different errors on two different lines, which one did you show? I guess the latter. This could will be just a follow-up issue, start with looking at the first error. Besides that, one of the major advantages of Kotlin DSL compared to Groovy DSL is the amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio. If the sync does not work currently as it is broken, comment everything except for the plugins block, then sync again, then comment in the stuff again and the IDE should show you quite good what problems you have.
s
Thanks for the detailed reply, @Vampire! • Yes I tried it. I saw your hack in the issues I found while trying to make it work. I ultimately went with the typesafe-conventions plugin because I found a reference to it in the issues. The terminology is confusing to me, I think a plugin can be both a pre-compiled script plugin and a convention plugin, can't it? In any case, that part wasn't working but I have it working now. • Maven local is awkward, yes. However, I think it is necessary if I don't want to publish half-baked work in progress to the rest of the team. (I never know where to put it in the repo order as Gradle at one point had an issue where it would stop looking for a resource in other repos if it found an earlier version of it in one repo, so the later version in maven-local wasn't considered. That is probably fixed long ago?) •
plugins.apply
is gone now after using the typesafe-conventions plugin • I unfortunately had to sanitize the script for public posting. My actual plugin has a enough of a unique project name before the
-convention
that I am not concerned about collisions with internal Gradle plugins, but point taken. I will add a package, if I can figure out how. I tried adding a package line to the convention script but that broke all the version catalog references! "Unresolved reference: libs". Maybe it must be in the filename for pre-compiled script plugins? This is the first time I'm using Kotlin DSL so there is lots of learning. •
UnknownServiceException
is gone now after switching to typesafe-conventions plugin. There have been many tweaks, so I'm not sure which fixed it at this point. One was that a name in the version catalog had a
-interface
and that caused issues do to interface being a keyword, but the error messages made that difficult to diagnose. • Issue 15383 was likely more relevant. • "...doesn't work out of the box" - well it doesn't work the way I expected at least. It is not clear or intuitive without the typesafe-convention plugin. • "This only makes the version catalog available in the build scripts of that build, not within the precompiled script plugins it is building" - Keeping the scopes straight in my head is tricky... • I believe this was the error caused by having a name in the version catalog
core-interface
. The use of
interface
caused the problem. I do not like IntelliJ. I have tried it multiple times. I takes too much effort to get it to work smoothly, if I can get it to work well at all. Perhaps the Gradle support is better now. (Sadly the only IDE that does work smoothly for me out of the box, NetBeans, doesn't get any love.) Most of our team was using IntelliJ but many have dropped it and now use either Windsurf or Cursor. I am using Windsurf. These VSCode-derived editors sort of pretend to be IDEs, but they do a poor job at most IDE stuff. I miss simple rename refactorings that actually know what symbol they are renaming. We use the newer editors to get the modern AI assistants. Hopefully one day they will get in the way less and produce code that actually compiles and isn't full of mistakes 😄 . I haven't found VSCode to be that much better with Kotlin DSL, but the AI assistants did, after many attempts, help me work out the issues such that my build is now working. Though I'm fairly certain there are things I could be doing better.
v
The terminology is confusing to me, I think a plugin can be both a pre-compiled script plugin and a convention plugin, can't it?
Definitely. Even though often mixed up and used wrongly (the term), a convention plugin is a plugin that employs your own conventions like applying certain plugins and / or doing certain configurations, no matter how they are implemented. You can implement them in any way you can implement plugins, that is in any JVM language and in any form. Two of those forms are precompiled script plugins in Groovy DSL or Kotlin DSL. Convention plugins are often implemented as precompiled Kotlin DSL script plugins and thus often those two terms are mixed up even though that is just wrong.
Maven local is awkward, yes. However, I think it is necessary if I don't want to publish half-baked work in progress to the rest of the team. (I never know where to put it in the repo order as Gradle at one point had an issue where it would stop looking for a resource in other repos if it found an earlier version of it in one repo, so the later version in maven-local wasn't considered. That is probably fixed long ago?)
Dunno whether that is fixed or still the same and "working as intended" as I practically never use Maven Local for very good reasons. And I also never use version ranges, but always specific versions. If you use a version range and an earlier repo has it, it could well be that the latest version available there is used. But as you should always use repository content filter with Maven Local when using it at all, you could also declare it as exclusive content, so that no other repository is searched for that dependency anyway. Usually - as long as the Gradle versions are compatible or optimally identical - a much better way to test things together is using composite build feature. That use-case is the original intention for the feature. So you automatically get the dependency built and used if necessary, and also get both open in one IDE automatically.
I will add a package, if I can figure out how. I tried adding a package line to the convention script but that broke all the version catalog references! "Unresolved reference: libs". Maybe it must be in the filename for pre-compiled script plugins? This is the first time I'm using Kotlin DSL so there is lots of learning.
Hmm, seems this changed at some point, I'm pretty sure package statements worked in the past: https://github.com/gradle/gradle/issues/28851 So just add it to the filename.
I do not like IntelliJ. I have tried it multiple times. I takes too much effort to get it to work smoothly, if I can get it to work well at all.
That's actually surprising to me. For me it is the best IDE under the sun and always worked smoothly out-of-the-box. If any employer would force me to use something different, he would have my quit letter next day. :-D But mileages vary, to everyone their preferred tools. :-)
Perhaps the Gradle support is better now.
In IntelliJ? The support is great and always was, and better than in any other tool I've seen.
use either Windsurf or Cursor.
Never heard of them, will give them a try, but pretty unlikely I'll love them more than IJ. :-D
These VSCode-derived editors sort of pretend to be IDEs, but they do a poor job at most IDE stuff.
Ah, are they? Then I probably don't give them a try, VSC itself already is not an IDE, but just a fancy text editor in my opinion, and for that I much prefer jEdit. :-)
Hopefully one day they will get in the way less and produce code that actually compiles and isn't full of mistakes 😄 .
Not in the next few decades, the current AIs are practically and much over-simplified just very advanced next-word-guessers. :-D
I haven't found VSCode to be that much better with Kotlin DSL,
Quite possible, you could use IJ at least to author the build files. 🤷‍♂️
s
> a much better way to test things together is using composite build feature. That use-case is the original intention for the feature. I do try that when possible, but it does require modifying the project to include the build. Something that can accidentally get checked in. >> Perhaps the Gradle support is better now. > In IntelliJ? > The support is great and always was, and better than in any other tool I've seen. I just tried it again... Taking a minute "Importing 'xxxxx' gradle project." In NetBeans there is no "importing", it just works. No icons to differentiate between plain folders and subprojects. No collapsing of empty package levels to make the tree more compact - wasting tons of UI space and forcing horizontal scrolling. Hidden folders shown but apparently no way to hide them? Basically the project view is cluttered and awkward to navigate. I see on the other side of the UI there is a separate view of the Gradle project hierarchy, maybe because it doesn't integrate properly into the regular IDE project view? Still no way to open more than one project per window. I don't like that, though I've had to get used to it for the other editors I mentioned as well. I much prefer one IDE window with multiple projects, like NetBeans or even Eclipse. I tried to edit files in my project and it is totally confused and claims the source files don't belong to a project!!?? My current experience is still that IntelliJ fails to work smoothly with Gradle projects. I don't have time to fight my IDE. NetBeans works better (and believe me, it is also far from perfect). I know IntelliJ gets high praise and it obviously works well for many people. Apparently I'm not one of them 😆 . I wanted to like it, I just find the UI awkward for the way I work. But I'm fussy.. It got to the point where I actually started writing my own Java IDE because I couldn't find a single one that could do everything the way I wanted. 😆 And sorry, this is not the place for IDE rants. After renaming the convention plugin script I have a unique reverse-domain prefix. The typesafe-convention seems to have addressed all the other issues. > ...the current AIs are practically and much over-simplified just very advanced next-word-guessers Agreed. But sometimes they do an amazing job at guessing that next "word".
v
> I do try that when possible, but it does require modifying the project to include the build. Something that can accidentally get checked in. > Not necessarily. You can do the include build via commandline argument, you probably just loose that both projects are opened in the IDE alongside automatically. Or you could probably use an init script in your
GRADLE_USER_HOME/init.d
to do the build include so that you cannot commit it accidentally but still have it without extra argument. > Taking a minute "Importing 'xxxxx' gradle project." In NetBeans there is no "importing", it just works. > If it does not import, taking exactly the same amount of time, it probably is not doing it properly. Without letting Gradle configuring the build and asking it to give you the project information like project structure and dependencies, it simply cannot have the correct information. It can try to manually parse the build scripts, but this can almost never be correct for to the power Gradle provides. > No icons to differentiate between plain folders and subprojects. > After the successful import? Of course the icons are different in the project view. > No collapsing of empty package levels to make the tree more compact - wasting tons of UI space and forcing horizontal scrolling. > For me they are compacted, might just not be the default setting, no idea, my IJ config evolved over 20 years. Should be just one and a half click away, cannot check right now, I'm on vacation. > Hidden folders shown but apparently no way to hide them? > No idea, I hate hidden folders be hidden, I always enable showing hidden folders in any tool I use that does not show them by default. Most probably also a simple switch you can toggle to your liking. > Basically the project view is cluttered and awkward to navigate. > I can not agree, for me it feels exactly the opposite, IJ UI almost always being intuitive and clear. 🤷‍♂️ > I see on the other side of the UI there is a separate view of the Gradle project hierarchy, maybe because it doesn't integrate properly into the regular IDE project view? > It integrates properly and nicely in the project view. The separate Gradle tool window is mainly for having the tasks to execute displayed and executable on click which does not belong in the project view. > Still no way to open more than one project per window. I don't like that, though I've had to get used to it for the other editors I mentioned as well. > If they are for example Gradle projects, sure, just link the other Gradle projects and they are opened alongside. For me it doesn't really make sense as each project should be separate, but it works just fine. The only occasions where I want multiple projects in the same window is, when working on a providing project and a consuming project at the same time. For that I use composite build anyway and with that, all involved projects even get opened alongside automatically without any manual action needed > I tried to edit files in my project and it is totally confused and claims the source files don't belong to a project!!?? > Probably the Gradle sync failed or something? I can hardly guess what state you see, but it definitely sounds like something that never happened to me, unless something bad happened like Gradle sync failed or similar. That also would match the "directories do not look different" comment. > My current experience is still that IntelliJ fails to work smoothly with Gradle projects. > Well, I don't mean that offensive, but it has to be something with your local situation or the projects you use. I with with dozens and varying Gradle builds each week and it always works perfectly smooth and with excellent support and integration unless something is bad like the build being broken and unable to sync or similar. 🤷‍♂️ > I don't have time to fight my IDE. NetBeans works better (and believe me, it is also far from perfect). > Well, use whatever tool fits you best. I'm just recommending what I think makes the most sense and is best. What you do with that recommendation is totally up to you, and that's fine for me, you have to live with it. :-) If anyone would try to force me to use NB or Eclipse or VSC, I wouldn't accept that major loose of comfort and lose of lifetime and nerves by having to use those. :-D > But I'm fussy.. It got to the point where I actually started writing my own Java IDE because I couldn't find a single one that could do everything the way I wanted. 😆 > Nice, where can I find it? After renaming the convention plugin script I have a unique reverse-domain prefix. The typesafe-convention seems to have addressed all the other issues. ...the current AIs are practically and much over-simplified just very advanced next-word-guessers Agreed. But sometimes they do an amazing job at guessing that next "word".