I am trying to build a custom Gradle Task to perform some app maintenance operations. I would like to pass argument(s) to the task to control operations. To illustrate my dilemma, consider to following two trivial custom task classes:
// Custom task with no arguments
public class HelloClassA extends DefaultTask {
#TaskAction
public void printHello()
{
println "Hello, World!"
}
}
// Custom task with one argument
public class HelloClassB extends DefaultTask {
#TaskAction
public void printMsg(String msg)
{
println msg
}
}
They are identical except that "A" prints a hard-coded string while "B" accepts a string as an argument.
Class A is used in the following task:
task helloA(type:HelloClassA) { }
and is invoked by, e.g.,
gradlew.bat helloA
It works fine and prints "Hello, World!" to the Build window. However, I can't figure out the syntax is for a task to invoke Class B.
How do I do that? (or am I just way off base trying to do it this way?)
Some rather Strange Things I've noticed...
The name of the method in the classes (e.g., "printHello") seems to be irrelevant: any reasonable name produces identical output (?).
When invoking by gradlew.bat, any unambiguous substring of the task name works the same, e.g., "GRADLEW helloA" or "GRADLEW hell". I guess this is just GRADLE trying to be helpful (?).
You can't directly pass it as an argument to the method. But, if you wish there is a way using #Input annotation.
Following is the example for you use case:
abstract class HelloClassB extends DefaultTask {
#Input
abstract Property<String> getMsg()//this is an abstract method to get values
#TaskAction
def printMsg() {
println msg.get() // here we get the value set from task.register
}
}
// printB is the task name
tasks.register('printB',HelloClassB) {
msg = 'Hello B' // here we set the Input value
}
Now in your terminal run: gradlew -q printB
If you want to read more about this then please have a look here: https://docs.gradle.org/current/userguide/custom_tasks.html
You can pass an argument(s) directly to a method without using properties. Here's how:
apply plugin: 'com.android.application'
import javax.inject.Inject
abstract class CustomTask extends DefaultTask {
#Inject
CustomTask(String message,int number) {
println "Processing \""+message+"\", "+number // process args
}
}
Create task 'mytask' as follows in the android section appending the arguments (which must agree in count and format with the task definitions):
tasks.register( 'mytask', CustomTask, 'Hello, World!', 123 )
Execute 'mytask' by
gradlew.bat mytask
which produces the output
Processing "Hello, World!", 123
Related
I am trying to mock a sealed class which looks something like this:
sealed class Location
class Home: Location{
val name = "Home"
}
I would like to be able to do the following:
val mockHome = mockk<Home>() {
every { name } answers { "My Home" }
}
But unfortunately when I run this it fails with the following error:
io.mockk.MockKException: Missing calls inside every { ... } block.
What am I doing wrong?
For now you cannot. but this issue was fixed in this PR and we are waiting for next release of Mockk. you can track this PR for more information
I'm stuck trying to mock some stuff with mockk:
I have the following setup on gradle
root:
|-- App (just a sample app for the SDK)
|-- SDK (SDK we develop) << apply plugin: 'com.android.library'
|-- SDKimpl.kt
|-- Foo (wrapper around a .jar library) << apply plugin: 'com.android.library'
|-- Foo.kt
So I'm writing an androidTest for the SDK and trying to mock Foo.kt.
There's nothing unusual about Foo class, just direct class Foo(private val someParams) {
So using androidTestImplementation "io.mockk:mockk-android:1.8.13" the mock goes:
val mock: Foo = mockk()
// val mock: Foo = mockkClass(Foo::class) // also tried this
every { mock.getData() } returns listOf("1", "2", "3")
I'm always getting the following crash:
io.mockk.MockKException: Missing calls inside every { ... } block.
at io.mockk.impl.recording.states.StubbingState.checkMissingCalls(StubbingState.kt:14)
at io.mockk.impl.recording.states.StubbingState.recordingDone(StubbingState.kt:8)
at io.mockk.impl.recording.CommonCallRecorder.done(CommonCallRecorder.kt:42)
Also tried just to gather information:
running inside JVM test folder. It gets mocked without issues, but I can't run my test as JVM
running androidTest inside Foo module. Got the same crash
using mockkClass(Foo::class). Got some crash
using annotation #MockK and MockKAnnotations.init(this). Got some crash.
added Log.d before every { line and inside getData() method and it seems the actual real method from the class is getting called during the mock setup. That seems super weird to me.
Any idea what's going wrong here?
edit:
as requested, full code. I'm current working on an isolated project to try to isolate the error, so Foo is just:
class Foo {
fun getData(): String {
Log.d(TAG, "invoked foo.getData()")
return "trolololo"
}
}
and then I have FooTest in androidTest:
class FooTest {
#Test
fun mock_foo() {
val foo = mockk<Foo>()
every { foo.getData() } returns "zero"
assertEquals("zero", foo.getData())
}
}
It seems to be a Mockk opened issue: https://github.com/mockk/mockk/issues/182
2 possible quick fixes (pick one):
Run the Instrumented Tests in an emulator >= Android-P
Set Foo class as open (and the method(s) you want to mock too)
Try to check the official guide and see what is missing.
In my case, I tried to mock an extension in Kotlin but missed the mockkStatic
fun Date.asMyTime() : DateTime = DateTime(this, DateTimeZone.getDefault())
mockkStatic("packageName.FileNameKt") // This is what I was missing
every {
DateTime().asMyTime()
} returns mock(DateTime::class.java)
In my case I forgot to spyk the class I was applying every {...} to. 😳
val presenter = spyk(MyPresenter())
every { view.myFun(any()) } returns Unit
In my case, I've missed
#Before
fun setUp() {
MockKAnnotations.init(this)
}
In my case I tried to mock using mock() function instead mockk() (double k)
Make sure the object is really a mock, not the real object.
For instance:
- Sdk sdk = Sdk()
+ Sdk sdk = mockk()
every { sdk.crypto } returns mockk()
My problem was that I used a java class without getters
public class KeyStorePasswordPair {
public KeyStore keyStore;
public String keyPassword;
public KeyStorePasswordPair(KeyStore keyStore, String keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
}
}
I needed to add getters for the variables to make mockking work:
public class KeyStorePasswordPair {
public KeyStore getKeyStore() {
return keyStore;
}
public String getKeyPassword() {
return keyPassword;
}
private KeyStore keyStore;
private String keyPassword;
public KeyStorePasswordPair(KeyStore keyStore, String keyPassword) {
this.keyStore = keyStore;
this.keyPassword = keyPassword;
}
}
Mockk Missing calls inside every { ... } block
may also be thrown when you have an every block defined on an object that is not a mockk, for example:
You define stub behavior like this
every { foo.getData() } returns DATA
and then try to:
every { DATA.getVersion() } returns VERSION
Where MY_THING and VERSION objects are declared (and instantiated) in the test class.
The error message is not very informative and a bit misleading in that case.
try like this
`when`(mock.getData()).thenReturn(listOf("1", "2", "3"))
I tried as per example in, and simplified it to just "message".
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension, project)
project.tasks.create('hello', Greeting) {
message = extension.message
}
}
}
class GreetingPluginExtension {
def String message = "aa"
GreetingPluginExtension(Project project) {
message = project.property(String)
}
}
class Greeting extends DefaultTask {
#Input
def String message = project.property(String)
#TaskAction
def testCoverageVerification() {
logger.quiet("Message is")
logger.quiet(message)
}
}
apply plugin: GreetingPlugin
greeting {
message = 'Hi from Gradle'
}
When I run ./gradlew -q hello, I got
Message is
value: null
I expect it to be
Message is
value: Hi from Gradle
Why did my String from the greeting fail to pass in?
Actually, it's quite easy to explain. The plugin (hence the task creation and variable value assignment) is applied and evaluated before the extension block is evaluated. So the task is created even before the extension's value is set. From the link you provided:
The extension declaration in the build script as well as the mapping
between extension properties and custom task properties occurs during
Gradle's configuration phase of the build lifecycle. To avoid
evaluation order issues, the actual value of a mapped property has to
be resolved during the execution phase.
To make it work you can change the plugin declaration to this:
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension, project)
project.tasks.create('hello', Greeting) {
doFirst {
message = extension.message
}
}
}
}
However it doesn't make much sense - so don't use it ;)
I manage to resolve my issue by directly getting it from project.greeting.message
#TaskAction
def testCoverageVerification() {
message = project.greeting.message
logger.quiet("Message is")
logger.quiet(message)
}
I'm trying to understand android plugin, when I look at "defaultConfig", I find the method is
public void defaultConfig(Action<ProductFlavor> action) {
this.checkWritability();
action.execute(this.defaultConfig);
}
and calling action.execute(this.defaultConfit) invokes the closure against this.defaultConfig.
This is confusing, so I looked at doc of interface Action to see what magic it has.
According to doc of Action interface, when calling action.execute(obj), it actually "Performs this action against the given object", and the given object here is obj.
How does this work?
ASAIK, if I wish to call a method against obj, I use "it" to reference to the obj: it.doSth(), otherwise the method will be invoked against "this".
But when using Action interface, "it" is no more necessary and method calls within this interface will just be invoked against "it".
I also write some code to test it:
class Main {
Test test = new Test()
test.actionReceiver {
// actionName "test ok"
it.actionName "test ok"
}
}
static interface MyAction<T> {
void execute(T)
}
static class MyActionReceiver {
void actionName(String name) {
println name
}
}
static class Test {
MyActionReceiver actionReceiver = new MyActionReceiver()
void actionReceiver(MyAction<MyActionReceiver> action) {
action.execute(actionReceiver)
}
}
}
If my interface MyAction had the magic that Action interface has, then calling actionName without "it" should just work, however it didn't.
My question is: how Action interface works and how can I make my interface work the same way?
Gradle tasks can contain one or more Actions. You can find more about Actions here:
https://docs.gradle.org/current/javadoc/org/gradle/api/Action.html#execute(T)
Actions are typically defined in the
doFirst{...}
or
doLast{...}
block of a task definition. See:
task hello {
doLast {
println 'Hello world!'
}
}
When writing custom tasks you can simply annotate your main action with the #TaskAction annotation:
task hello(type: GreetingTask)
class GreetingTask extends DefaultTask {
#TaskAction
def greet() {
println 'hello from GreetingTask'
}
}
Some more helpful links:
https://docs.gradle.org/current/userguide/tutorial_using_tasks.html
https://docs.gradle.org/current/javadoc/org/gradle/api/Task.html
Thanks for the advice, in the doc it says:
TextResource ruleSetConfig
Note: This property is incubating and may change in a future version of Gradle.
The custom rule set to be used (if any). Replaces ruleSetFiles, except that it does not currently support multiple rule sets. See the official documentation for how to author a rule set. Example: ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml"))
But if I try
setRuleSetConfig(project.resources.text.fromFile(project.resources.file("pmd.xml")))
it says:
Caused by: groovy.lang.MissingMethodException: No signature of method: org.gradle.api.internal.resources.DefaultResourceHandler.file() is applicable for argument types: (java.lang.String) values: [pmd.xml]
Possible solutions: find(), find(groovy.lang.Closure), use([Ljava.lang.Object;), is(java.lang.Object), with(groovy.lang.Closure), wait()
at com.barista_v.android_quality.MyPmdTask.<init>(MyPmdTask.groovy:20)
at com.barista_v.android_quality.MyPmdTask_Decorated.<init>(Unknown Source)
at org.gradle.api.internal.DependencyInjectingInstantiator.newInstance(DependencyInjectingInstantiator.java:48)
at org.gradle.api.internal.ClassGeneratorBackedInstantiator.newInstance(ClassGeneratorBackedInstantiator.java:36)
at org.gradle.api.internal.project.taskfactory.TaskFactory$1.call(TaskFactory.java:121)
... 82 more
Also I am trying in this way::smile:
class MyPmdTask extends Pmd {
MyPmdTask() {
project.extensions.pmd.with {
reportsDir = project.file("$project.buildDir/outputs/")
}
source = project.fileTree('src/main/java').toList()
ruleSets = []
reports {
xml.enabled = false
html.enabled = true
}
setIgnoreFailures(true)
setRuleSetConfig(ResourceUtils.readTextResource(project, getClass().getClassLoader(), "pmd.xml"))
}
}
class ResourceUtils {
static TextResource readTextResource(Project project, ClassLoader classLoader, String fileName) {
println "reading config file $fileName"
def inputStream = classLoader.getResourceAsStream(fileName)
project.resources.text.fromString inputStream.text
}
}
and it set the config file but it dont use my rules, dont crash but dont use my rules.
I tried setting in
project.extensions.pmd.with {
// HERE
}
when I try getRuleSetConfig() it returns null but with setRuleSetConfig() appears to be setted but not used.
I dont want to create another repo just for configurations like here https://github.com/MovingBlocks/TeraConfig.
Related to: https://discuss.gradle.org/t/how-can-i-read-setup-resources-from-plugin-jar/13274/4
what can I do?
I was able to create a custom plugin that adds a PMD task of the built-in type, using a custom ruleset packaged in the plugin's src/main/resources directory; I'm using kotlin, but you can probably get the idea:
class BuildPlugin: Plugin<Project> {
override fun apply(project: Project) {
val android = project.extensions.getByType(BaseExtension::class.java)
project.pluginManager.apply(PmdPlugin::class.java)
project.extensions.getByType(PmdExtension::class.java).toolVersion = "5.5.2"
project.tasks.register("pmd", Pmd::class.java) {
it.source(android.sourceSets.getByName("main").java.sourceFiles,
android.sourceSets.getByName("test").java.sourceFiles,
android.sourceSets.getByName("androidTest").java.sourceFiles)
it.ruleSetConfig = project.resources.text.fromUri(javaClass.classLoader.getResource("config/pmd.xml"))
}
}
}
It's still a work in progress (for instance, I'm unsure if there are implications to resolving the android extension directly in my apply), but it seems to use my ruleset as expected!