This message was deleted.
# plugin-development
s
This message was deleted.
m
No. In order for a plugin to be compatible with both the Groovy and Kotlin DSLs, you need to use Gradle's own extension mechanism. For example, you can imagine the following convention plugin (e.g in `buildSrc/src/main/groovy/my-plugin.gradle`:
Copy code
plugins {
   id 'java-library'
}

def ext = repositories.extensions.create("my", MyExtension, repositories)


abstract class MyExtension {
    private final RepositoryHandler repositories
    
    @Inject
    MyExtension(RepositoryHandler repositories) {
        this.repositories = repositories
    }

    void foo() {
    	repositories.maven { it.url = "<https://foo.com>" }
    }
}
Then you can use it in the Groovy DSL like this:
Copy code
plugins {
   id 'my-plugin'
}

repositories {
   my.foo()
}
and in the Kotlin DSL:
Copy code
plugins {
   id("my-plugin")
}

repositories {
   my.foo()
}
(note, for
RepositoryHandler
this actually only works in 7.4, not before, but in general this is how it should work) The drawback of this approach is that you cannot directly "extend" the repository handler type: the extension is scoped with a name.
l
The drawback of this approach is that you cannot directly "extend" the repository handler type: the extension is scoped with a name.
I was trying to avoid the extension mechanism for that reason but I guess this is the best way until support for custom repository types is added then. Thanks!
t
Couldn't you have a Kotlin class that overrides the
invoke()
operator for Kotlin, and implements a
call()
method for Groovy? Not entirely sure it'd actually work because of Gradle's special handling of extensions that be used as
my { … }
with a closure/action.
v
Of course you can do it and without the extra namespacing the extension requires and without the need for 7.4, you just have to provide a Groovy-specific way and a Kotlin specific way that is used by the Groovy-specific way.
Have in some
Foo.kt
(not
kts
, that's important, it must be in a
.kt
even if you otherwise have a precompiled script plugin)
Copy code
fun RepositoryHandler.foo() = maven("...")
this makes it usable from Kotlin DSL scripts. And then add somewhere in your plugin
Copy code
(repositories as ExtensionAware).extra["foo"] = delegateClosureOf<Any> { repositories.foo() }
which makes it usable from Groovy DSL.
This way you can use it like
repositories { foo() }
from both DSLs.
m
This isn't a generic solution, though, and it's fairly ugly IMHO
v
I said it needs special treatment for both DSLs. And I don't disagree that it is somewhat ugly. But from a usage PoV it is nice and convenient and exactly what the OP asked for.
Well, actually I did not fully answer the OP question as it was about overloaded methods for the repository handler. But even that works, it just gets even more ugly on the plugin side. 😄 In the
.kt
file:
Copy code
fun RepositoryHandler.foo() = maven("repo")
fun RepositoryHandler.foo(bar: String) = maven("string/$bar")
fun RepositoryHandler.foo(bar: Any) = maven("any/$bar")
And in the plugin code:
Copy code
(repositories as ExtensionAware).extra["foo"] = object : Closure<Unit>(this, this) {
    fun doCall(vararg args: Any) {
        when (args.size) {
            0 -> repositories.foo()
            1 -> {
                args.first().let {
                    when (it) {
                        is String -> repositories.foo(it)
                        else -> repositories.foo(it)
                    }
                }
            }
            else -> throw NoSuchMethodException()
        }
    }
}
t
What I was proposing, fwiw:
Copy code
open class MyExtension @Inject constructor(
    private val repositories: RepositoryHandler
) {
    operator fun invoke() = repositories.maven(url = "…")
    operator fun invoke(bar: String) = repoitories.maven(url = "string/$bar")
    // for Groovy:
    fun call() = invoke()
    fun call(bar: String) = invoke(bar)
}
repositories.extensions.create<MyExtension>("my", repositories)
Still kind of ugly if you ask me, and I'm worried about forward compatibility.
v
Well, it depends on the Gradle versions you want to support. Before 7.4 your code will not work, as for
RepositoryHandler
no Kotlin DSL accessors are generated, so you need to provide the extension functions. And besides that, even with 7.4
RepositoryHandler
is not formally
ExtensionAware
, so you still need to cast it. Starting with 7.4 the
invoke
methods for the extensions would work for the Kotlin DSL. But just having a
call
method still does not work for Groovy. You need it to be a
Closure
for it to work and then you also have to manually instantiate it. So for Gradle 7.4 you could do it like
Copy code
open class MyExtension : Closure<ArtifactRepository>(this, this) {
    operator fun invoke() = repositories.maven(url = "repo")
    operator fun invoke(bar: String) = repositories.maven(url = "string/$bar")
    operator fun invoke(bar: Any) = repositories.maven(url = "any/$bar")
    fun doCall() = invoke()
    fun doCall(bar: String) = invoke(bar)
    fun doCall(bar: Any) = invoke(bar)
}
(repositories as ExtensionAware).extensions.add<MyExtension>("foo", MyExtension())
For Gradle <7.4 (also compatible with 7.4+ of course) you need to do it like I suggested, but you can of course do it less ugly then I did originally, for example like this: in a `.kt`:
Copy code
fun RepositoryHandler.foo() = maven("repo")
fun RepositoryHandler.foo(bar: String) = maven("string/$bar")
fun RepositoryHandler.foo(bar: Any) = maven("any/$bar")
and in your plugin:
Copy code
(repositories as ExtensionAware).extensions.add("foo", object : Closure<ArtifactRepository>(this, this) {
    fun doCall() = repositories.foo()
    fun doCall(bar: String) = repositories.foo(bar)
    fun doCall(bar: Any) = repositories.foo(bar)
})
But with the latter you then get an unnecessary accessor when used with Kotlin DSL in 7.4+. So maybe this would be better as it is just for the Groovy part:
Copy code
(repositories as ExtensionAware).extra["foo"] = object : Closure<Unit>(this, this) {
    fun doCall() = repositories.foo()
    fun doCall(bar: String) = repositories.foo(bar)
    fun doCall(bar: Any) = repositories.foo(bar)
}