How to write extension method for Gradle dependencies {} block - gradle

I'm trying to write extension methods for DependencyHandler.
One of main goals to have autocompletion of these methods.
So I wrote extension function in buildSrc project like this (Shortcuts.kt):
fun DependencyHandler.autoValue() {
add("compileOnly", Libs.Auto.autoValueAnnotations)
add("annotationProcessor", Libs.Auto.autoValueCompiler)
}
And registered it as extension module as described here:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = buildSrc
moduleVersion = 1.0
extensionClasses = com.example.test.ShortcutsKt
I want to use these methods in build.gradle files like:
dependencies {
...
autoValue()
}
It appears in autocompletion list inside dependencies{} block, but at configuration time I got error:
org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method autoValue() for arguments [] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
at org.gradle.internal.metaobject.AbstractDynamicObject.methodMissingException(AbstractDynamicObject.java:179)
at org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:87)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeOnDelegationObjects(ClosureMetaClass.java:430)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:369)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
at build_dh4v5lw1dkt4b2nii5ope5rmy$_run_closure1.doCall(/.../app/build.gradle:29)

Gradle DefaultDependencyHandler implements custom method resolution strategy (to handle scopes notation like testCompile(smth)), so additional methods can be added via dependencies.ext property:
dependencies.ext.autoValue = {
dependencies.add("compileOnly", Libs.Auto.autoValueAnnotations)
dependencies.add("annotationProcessor", Libs.Auto.autoValueCompiler)
}
But in this case you don't get autocompletion.
To enable autocompletion you can mix this two approaches and proxy extension methods from buildSrc via dependencies.ext:
import com.example.test.ShortcutsKt
import java.lang.reflect.Method
import java.lang.reflect.Modifier
// DependencyHandler implementation resolves all undeclared methods by self,
// so we need to add these extension methods to dependencies.ext
def methodNames = ShortcutsKt.declaredMethods
.findAll { isDependencyHandlerExtension(it) }
.collect { it.name }
.unique()
methodNames.each { String methodName ->
dependencies.ext[methodName] = { Object... args ->
ShortcutsKt."$methodName"(dependencies, *args)
}
}
private static boolean isDependencyHandlerExtension(Method method) {
return Modifier.isPublic(method.getModifiers()) &&
Modifier.isStatic(method.getModifiers()) &&
method.parameterCount > 0 &&
method.parameterTypes[0] == DependencyHandler.class
}

Related

How to access extra properties defined in build.gradle in a Groovy class?

I am defining an extra property in build.gradle:
ext {
GRGIT_TARGET_REPO = ...
}
I have a groovy class under buildSrc/src/main/groovy
class Utils {
def test() {
println GRGIT_TARGET_REPO
}
}
I have another gradle file which can access GRGIT_TARGET_REPO just fine. But if it calls the function in the class:
Utils utils = new Utils()
utils.test()
On calling the above function, I get the following error:
No such property: GRGIT_TARGET_REPO for class: Utils
Is there a way to access project/extra properties in Groovy classes?
I believe you would need to send the gradle project object into your Utils class to accomplish what you want. In other words:
class Utils {
def project
def test() {
println(project.ext.GRGIT_TARGET_REPO)
}
}
and
def utils = new Utils(project: project)
utils.test()
in your build.gradle file.
Every gradle build file has a project instance as its delegate which means that you can call all methods in the project class directly from the build file code. The example here is that the above project access calls the getProject method on the project object.
For extra properties, there is an "extra properties" section in the above groovy/javadoc for the Project object.

Kotlin not able to convert gradle's Action class to a lambda

So, while this is quite a kotlin-dsl for gradle specific issue, I think it overall applies to the kotlin language itself, so I am not going to use that tag.
In the gradle API, the class Action<T> is defined as:
#HasImplicitReceiver
public interface Action<T> {
/**
* Performs this action against the given object.
*
* #param t The object to perform the action on.
*/
void execute(T t);
}
So ideally, this should work in kotlin (because it is a class with a SAM):
val x : Action<String> = {
println(">> ${it.trim(0)}")
Unit
}
But I get the following two errors:
Unresolved reference it
Expected Action<String> but found () -> Unit
Fwiw, even Action<String> = { input: String -> ... } doesn't work.
Now here's the really intriguing part. If I do the following in IntelliJ (which btw, works):
object : Action<String> {
override fun execute(t: String?) {
...
}
}
IntelliJ pops the suggestion Convert to lambda, which when I do, I get:
val x = Action<String> {
}
which is better, but it is still unresolved. Specifying it now:
val x = Action<String> { input -> ... }
gives the following errors Could not infer type for input and Expected no parameters. Can someone help me with what is going on?
This is because the Action class in gradle is annotated with HasImplicitReceiver. From the documentation:
Marks a SAM interface as a target for lambda expressions / closures where the single parameter is passed as the implicit receiver of the invocation (this in Kotlin, delegate in Groovy) as if the lambda expression was an extension method of the parameter type.
(emphasis mine)
So, the following compiles just fine:
val x = Action<String> {
println(">> ${this.trim()}")
}
You could even just write ${trim()} and omit the this in front of it.
You need reference the function with class name, like:
val x: Action<String> = Action { println(it) }

Use file features on Job DSL

I´m using Job DSL and I would like to download a file, read it, and set some env variables.
def static setSecrets(Job delegate, Map overrides = [:]) {
def liveUsername
def livePassword
def file
new URL('https://path/file').withInputStream { i ->
file.withOutputStream {
it << i
}
}
file.withReader { liveUsername = it.readLines().get(0) }
file.withReader { livePassword = it.readLines().get(1) }
def options = [
IDENTITY_USER: liveUsername,
IDENTITY_PASSWORD: livePassword]
setEnv(delegate, options, overrides)
}
This is the exception that I´m receiving
java.lang.NullPointerException: Cannot invoke method withOutputStream() on null object
Seems like the features of file cannot being used. But being in groovy file I was expecting can use the Job DSL templates and also, all the groovy features.
File is null so is throwing NPE when you call a method on it
def file
...
file.withOutputStream { // BANG!

Creating a closure in ext

I am implementing the texturePacker task given in LibGDX's TexturePacker with gradle.
project.ext {
// ...
texturePacker = ["assets", "../android/assets", "texture"]
}
import com.badlogic.gdx.tools.texturepacker.TexturePacker
task texturePacker << {
if (project.ext.has('texturePacker')) {
logger.info "Calling TexturePacker: "+ texturePacker
TexturePacker.process(texturePacker[0], texturePacker[1], texturePacker[2])
}
}
I got it working with the suggested modifications for the classpath and added extension variable. Now I want to modify the textPacker extension variable to be a closure (Is that the right terminology?) with descriptive member names rather than an array. I tried doing this:
project.ext {
// ...
texturePacker {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
This gives the following error:
Error:Could not find method texturePacker() for arguments [build_4dusyb6n0t7j9dfuws8cc2jlu$_run_closure1$_closure7#6305684e] on project ':desktop' of type org.gradle.api.Project.
I am very new to gradle and groovy, so I have no idea what this error means. More importantly, what is the correct way to do what I want?
I suppose, closure is not the thing you need, since it's used not to store variables, but to store some executable code. By the way, if need to store it, you have to add = as follows:
project.ext {
texturePacker = {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
Anyway, if need to store variables within texturePacker variable, you rather have to use a Map type, then a Closure. This could be done like this:
project.ext {
texturePacker = [
inputDir : "assets",
outputDir : "../android/assets",
packFileName : "texture"
]
}
And then you can access this variable just by names, as:
println texturePacker.inputDir
Or, I think you can also go for implementing your own task with those properties. You can use DefaultTask which is a standard implementation of a regular task (and I'm sure it'd be enough for you);
class TexturePacker extends DefaultTask {
String inputDir; // a property - not a field!
String outputDir; // a property - not a field!
...
#TaskAction
void doSth(){
// do sth with properties above - that will be called automatically by gradle as a task-execution
}
}
task packer (type:TexturePacker) {
inputDir '<your-input-dir>'
outputDir '<your-output-dir>'
}
Syntax might not be super correct, but I think you get the idea.

How to use gradle extension correctly in plugins using GradleBuild task?

EDIT : I rephrased my question in taken the propositon of David M. Karr into account.
I am writing a gradle plugin. This plugin is launching a task extending GradleBuild. The external gradle build file needs some info as parameters. These parameters are given in project extension.
Plugin code
class MyPlugin implements Plugin<Project> {
def mExt
void apply(Project project) {
mExt = project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
def param = new StartParameter()
param.setProjectProperties([target:getTarget()])
// Problem here
startParameter = param
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
}
def getMyBuildPath(){
...
}
// Problem here
def getTarget(){
return {mExt.target}
}
}
class MyExt {
def String target = "uninitialised"
}
Gradle build file :
apply plugin : 'com.example.myplugin'
ext{
target = "myTarget"
}
External Gradle build file :
task build(){
println project.target
}
If I put a closure in getTarget(), println project.target shows the closure and not the string.
If I don't put the closure :
// Problem here
def getTarget(){
return mExt.target
}
Then I got "uninitialised" instead of "myTarget".
How can I get the value of myext.target here ?
I am using gradle 2.3
Try this:
Define an instance variable called "myext", of type "MyExt".
In the "apply" method, do this:
myext = project.extensions.create('myext',MyExt)
In the "getTarget" method, return "myext.target".
I have succeeded in getting what I wanted to in using project.afterEvaluate method. Thanks to this question
1) In gradle build task, startParameter.projectProperties is waiting for a map, not a closure. So the idea to put a closure for a lazy definition cannot work.
2) If I put directly in my plugin a reference to mExt.target or project.myext.target, then the initial value is set. The value put in my build.gradle file is not used because the plugin is already evaluated.
3) project.afterEvaluate() solve my problem. The closure ends configuring myTask in using the project's extension.
void apply(Project project) {
project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
project.afterEvaluate { proj ->
proj.myTask.startParameter.projectProperties = [target:proj.myext.target]
}
}

Resources