This message was deleted.
# community-support
s
This message was deleted.
j
Same as in a kt plugin,
extensions.create<T>(…)
you can create classes in the same file for example
but in a kts file you can use classes from other files to as the kts belongs to a kotlin source set too
a
because the conventions plugin kts does not have a class that i am already inside (or, maybe it does but it’s not visible)
yeah, you guessed right: it’s there, but it’s not visible. an explanation, if it helps: all .kts files, be they
.gradle.kts
files or
.main.kts
files, basically just copy and paste the file content into a dummy class’s init block, and add some properties to the class so they’re accessible in the context. if you check the build directory of a project with pre-compiled script plugins you can find the compiled JAR, and see that the
.kts
files are compiled to regular classes.
the difference between a plugin class and a pre-compiled plugin is that a
.gradle.kts
file has a fancy wrapper so that, seamlessly, Project is in the receiver. You can do the same thing in a regular class plugin by doing
Copy code
override fun apply(target: Project) = with(target) {
  // ...
}
(or don’t, because sometimes it gets a bit messy and confusing with too many receivers)
s
ooh, okay. i did not know about extensions.create<T>...that sounds like it'll do what i want 🙂 thanks everyone. and thanks for the explanation @Adam, i didn't think of it as project being in the receiver but that makes sense to me now
okay so...how do i actually create extensions and respond to my plugin being applied...when it is a kts file? I was trying to follow the example, and apply it to the other gradle conventions plugin sample... but, downstream project cannot find the GreetingPlugin or GreetingPluginExtension. why not? in my conventions-plugin project (kts file)
Copy code
interface GreetingPluginExtension {
    val message: Property<String>
    val greeter: Property<String>
}

class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val extension = project.extensions.create<GreetingPluginExtension>("greeting")
        project.task("hello") {
            doLast {
                println("${extension.message.get()} from ${extension.greeter.get()}")
            }
        }
    }
}


//apply<GreetingPlugin>()

// Configure the extension using a DSL block
configure<GreetingPluginExtension> {
    message.set("Hi")
    greeter.set("Gradle")
}
in the downstream plugin consumer:
Copy code
apply<GreetingPlugin>()

// Configure the extension using a DSL block
configure<GreetingPluginExtension> {
    message.set("Hi guid")
    greeter.set("Gradle from guid")
}
a
Gradle needs to be able to fetch the plugin from • a Maven repo • an included build or buildSrc Which are you using?
oh, this is a convention plugin?
s
exactly
and that's the problem. because it seems an interface, class or definition or other symbols don't just automatically become resolved
a
what plugins are applied in
buildSrc/build.gradle.kts
?
it’s preferred using the Plugins DSL over
apply<MyPlugin>()
btw
s
lol dunno, they need to fix their docs then
my setup is: (1) convention plugin. in here, also defining an interface and that GreeterPlugin class (within the kts file, so remember..this class is NOT
this
class) (2) second, totally separate project applies the convention plugin. that works fine. what isn't working, is it not being able to find that symbol WITHIN the convention plugin
a
ahhhh I see. So technically speaking
GreetingPlugin
is a nested class. So you’ll need do something like
Copy code
apply<My_convention_Gradle.GreetingPlugin>()
because Gradle will generate a class based on the file name, if that makes sense?
why do you have the plugin defined inside of a
.gradle.kts
file? I have distant warning alarms ringing… :) I suspect you’re going to run into more issues
s
yeah, it makes sense though i can't seem to resolve anything below that. it's using an autogenerated Com_org_blah_conventionsPlugin, it seems.
and..yeah, this is not my ideal approach
what i am looking for is..i've already got a conventions plugin, as the gradle docs told me to make. i'm using the kts file and i just want to be able to configure the damn thing with properties and DSL and whatever i want
instead, i'm afraid i'm going to get shoehorned into either "do a bunch of extra shit manually for no reason just to make plugins happy, and NOT be able to use a kts file as traditionally as one would everywhere else" or "horrible hack land"
it seems like most everyone creates full plugin classes. but then the gradle samples make a convention plugin with kts but then proceed to not even show how to handle properties and DSL in the project that applies said plugin, and communicate between
a
I suspect you’re getting a bit tangled up, because what you’re describing should be possible First, make your settings interface in
buildSrc/src/main/kotlin/GreetingPluginExtension.kt
Copy code
interface GreetingPluginExtension {
    val message: Property<String>
    val greeter: Property<String>
}
then you can create a precompiled script plugin that applies it
Copy code
// buildSrc/src/main/kotlin/greeting-plugin.gradle.kts

val extension = project.extensions.create<GreetingPluginExtension>("greeting")
and now in the root project you’ll be able to apply the plugin, and Gradle will automagically create a DSL accessor
Copy code
// ./build.gradle.kts

plugins {
  `greeting-plugin`
}

greeting {
  println(message.orNull)
}
(this assumes you have the
kotlin-dsl
plugin applied in
buildSrc/build.gradle.kts
)
and then if you want to configure the default values of the extension you can do so in
buildSrc/src/main/kotlin/greeting-plugin.gradle.kts
Copy code
// buildSrc/src/main/kotlin/greeting-plugin.gradle.kts

val extension = project.extensions.create<GreetingPluginExtension>("greeting")

extension.message.convention("hello!")
extension.greeter.convention("greetings!")
does that help?
s
yeah it does that seems to be what i need, i'll take a stab at it next week and report back. thanks 🙂
👍 1
a
let me know how it goes
and yeah, I agree that the Gradle docs are confusing about this
s
@Adam okay so i tried this.. https://github.com/sreich/gradle-greeting-plugin-extension-kotlin-dsl it's from a gradle sample and i modified it, but it is still getting that symbol issue. i can tell intellij to resolve it, but clearly gradle must be missing something on its end. see here where it still can't resolve the symbol for GreetingPluginExtension https://github.com/sreich/gradle-greeting-plugin-extension-kotlin-dsl/blob/master/buildSrc/src/main/kotlin/myproject.greeting.gradle.kts
a
so I can see that GreetingPluginExtension is in a separate project, build-conventions, to buildSrc, but I don’t see anything in
buildSrc/build.gradle.kts
that says “I depend on the subproject ‘build-conventions’”
I think you just need to move GreetingPluginExtension into
buildSrc/src/main/kotlin
it looks like you’re trying two different approaches, and they’re not compatible. This should be all you need:
Copy code
├── buildSrc/
│   ├── src/main/kotlin/
│   │   ├── GreetingPluginExtension.kt
│   │   └── myproject.greeting.gradle.kts
│   ├── build.gradle.kts
│   └── settings.gradle.kts
├── build.gradle.kts
└── settings.gradle.kts
s
sorry, the project sample i used makes this more unclear, doesn't it? my intention is, using the myproject.java-conventions conventions plugin...and also adding that GreetingPluginExtension into it. so that it is shipped with 1 convention plugin, all the tasks,e xtensions etc. ignore GreetingTask. the 'buildsrc' project that has GreetingTask is applying the conventions plugin and therefore i expect it to pick up everything the conventions plugin has. does that help clarify what i mean?
a
ahh I see, you want to have two convention plugins?
s
no, i just want 1. but i should be able to have classes that this plugin uses..
that's the gist of my problem. i can't reference and create my own dsl, property, configuration, and handle "onApply() " etc...with a conventions.gradle.kts
currently i see no way to do that, then i thought sure..i can add in whatever extension classes i want, just like the examples. Except then, that plugin does not seem to "ship" any of the other classes that come with it
a
how you’ve got it set up now means that • the main build can use plugins & classes from build-conventions and buildSrc • but buildSrc can’t use build-conventions in order for buildSrc to be able to access the contents of build-conventions you’d need to
includeBuild("../build-conventions)
in
buildSrc/settings.gradle.kts
(probably in the
pluginsManagement {}
block
buildSrc is essentially a completely separate Gradle project, that happens to have some behind-the-scenes defaults so that Gradle will automatically pick it up.
it’s possible to have two includedBuild projects that both provide convention plugins, but it can be a really tangled mess. And from looking at
myproject.java-conventions.gradle.kts
, are you sure it’s worth the complication? It’s okay if
buildSrc/build.gradle.kts
just looks like this:
Copy code
plugins {
  `kotlin-dsl`
}

dependencies {
  /* plugins */
}
And maybe you need to have a little duplication between some build configs, but a little duplication is much more simple than trying to set up multiple included builds
s
think we're talking past each other here. my point is, having 1 convention plugin, specified through gradle-kts, and having whatever classes/interfaces i want, be accessible. literally, how do i get properties be accessible when applying the convention plugin. the example you gave me, does not simulate that this one does simulate that because as you said,
buildsrc
in this context, doesn't have everything on the classpath. but...that's my point, if i tell you to create my-conventions-plugin, then i should just be able to apply that in my application, assuming it can resolve t hat in the first place
but my problem is that, i am able to apply a plugin artifact...but then, i am not able to reference any symbols that i would expect to be shipped with that plugin artifact. and it needs those symbols, for e.g. how there is SonarQubeExtension class. or in this case, GreetingPluginExtension class...
the example you posted last week is basically what i want in theory, but doesn't work that way in practice. again, the symbols are not getting resolved if i am simply "applying" the plugin externally. forget combined, nested builds, none of that crap. just a simple app applying the plugin
i'm sure i'm missing something dumb. maybe i would get better control if i switched to the manually made gradlePlugin {} and set the implementationclass, instead of it using autogenerated
a
okay, so the requirement is that GreetingPluginExtension is defined in a separate project to the one with the plugin that uses it?
because that’s a really weird requirement 🙃
s
isn't that the standard requirement? e.g. sonarqube creates a sonarqube plugin class. they create a plugin extension class. i, some random user, applies that plugin, and is able to configure extensions, and it shows up on the classpath so i can configure it with the DSL using sonarqube { ... }
that's the use case that i am trying to replicate. just replace sonarqube with myconventions
in that scenario, it would make no sense for me to consume a sonarqube plugin...but then create my own ShaunsSonarQubeExtension and put that along with the application applying the sonarqube plugin
a
I suspect that’s not what Sonarqube is doing, I’ll bet that they have the plugin + extension in the same project
s
that's fine. as long as it isn't MyApplication project that is applying the plugin
if it's in MyConventionsPlugin project or whatever, that's what i want
but that's not the thing i can get. Hence why the code i linked to, the build breaks. Not because of the buildsrc issue...but because the plugins aren't getting on the classpath for anyone who applies. whether it's separate build or not
a
if you have a project like this:
Copy code
├── buildSrc/
│   ├── src/main/kotlin/
│   │   ├── GreetingPluginExtension.kt
│   │   └── myproject.greeting.gradle.kts
│   ├── build.gradle.kts
│   └── settings.gradle.kts
├── build.gradle.kts
└── settings.gradle.kts
then the Kotlin DSL Gradle will create a JAR with an auto-generated plugin class, and the extension class, and a Plugin ID marker. And that whole JAR will get added to the
build.gradle.kts
classpath if there’s a plugin ID that matches the plugin ID marker in the JAR
but that’s not the thing i can get. Hence why the code i linked to, the build breaks
I haven’t checked it out so maybe I’ve missed something, but from what I can see the build will break because 1. it tries to compile the file
buildSrc/src/main/kotlin/myproject.greeting.gradle.kts
2. but that needs the GreetingPluginExtension class 3. but in
build.gradle.kts
there’s no dependency on the
build-conventions
project - so it can’t fetch the file In order for the class to be available you’d have to add a dependency (probably using dependency substitutions)… but I’m not 100% sure that would work, because buildSrc has some limitations so I don’t think it can use composite builds
s
hmm indeed the .class file is getting in there, along with the Com_myorg_javaConventionsPlugin.class...yet when applying the plugin, shouldn't i receive those in a resolvable state?
sonar uses gradlePlugin and pluginBundle {}, neither of which i do outright by myself.
a
sorry, I’m not sure what you mean by ‘is getting in there’ or ‘a resolveable state’ Can you make a screenshot? :)
sonar uses gradlePlugin and pluginBundle {}, neither of which i do outright by myself.
Ah yeah, that’s part of the Kotlin DSL plugin magic. Normally you have to manually define an implementation class and a plugin ID, but the Kotlin DSL plugin will scan your source files and automatically generate a plugin ID (based on package + filename) that points towards the generated implementation class If you look in
$myProject/buildSrc/build/pluginDescriptors
you’ll see the descriptors
s
yup, exactly. and i think that's exactly where it's falling off. or the only theory i have for it right now
unless you have a different theory? there should be some way to get, in my example, the buildsrc (ignoring the fact that it is a buildsrc) dir should be able to resolve GreetingPluginExtension from the java-conventions project
just from applying the plugin. just like sonar does
seems to be a gap there, in the docs. at least from all the samples/docs i've read
a
I have no theory haha, I’m still confused about what you’re trying to do
s
yes, that approach is quite basically this approach. there is nothing fundamentally different about it, as i can see
a
it might help to take a look at a couple of projects that I am guessing is similar to the approach you want
in Dokka https://github.com/Kotlin/dokka/ there’s
build-logic/src/main/kotlin/org/jetbrains/DokkaBuildProperties.kt
which is a project-local extension that’s applied in a ‘base’ convention plugin
build-logic/src/main/kotlin/org/jetbrains/conventions/base.gradle.kts
I like DokkaBuildProperties because it uses
provider.gradleProperty()
to get the default value, which means the values can be overridden on the command line, or a gradle.properties file, or using a environment variable this is most useful when setting the Java version used to run the project’s tests, so in a GitHub action loads of different Java versions can be tested https://github.com/Kotlin/dokka/blob/0cad056d84bf1dbe285ca008be17ca5fbb224818/build-logic/src/main/kotlin/org/jetbrains/conventions/base-java.gradle.kts#L31-L33
s
yeah, might help...i'm convinced it's the automagic gradle kts crap that is breaking it. and nobody else seems to actually use that. even the docs, either you use 1, or you use the other(full plugin, or kts, never both)
hm, yeah Dokka is some kind of example
i agree, that's the kinda thing i would like with properties and such
a
a similar example is ks3 https://github.com/Kantis/ks3, which has a similar build-config extension, but it has a more complicated property that conditionally enables/disables Kotlin Multiplatform targets https://github.com/Kantis/ks3/blob/3fc70d9e66fe1ad875214bddb3ee5c15763047f2/build-logic/src/main/kotlin/ks3/conventions/Ks3BuildLogicSettings.kt#L27-L46