I am confused with Groovy method visibility in the context of my Gradle build.
For some tests in my project, I have to first start a server.
For this I created a custom task class that extends Gradle's Test like so:
class TestWithServer extends Test {
TestWithServer() {
super()
beforeTest {
startServer()
}
}
private void startServer() {
println('placeholder')
}
}
But if I try to run such a task, I get an error:
Could not find method startServer() for arguments [] on task ':testWithServer' of type TestWithServer.
I found that when I change the visibility of startServer() to the default (public), the task runs fine.
How come I can't use the private method from within its own class?
It is not the same class, because Gradle adds some magic to the task types. Just add println this.class into the beforeTest closure to see the name of the actual class (something like TestWithServer_Decorated). This additional magic also explains why the error message contains the task name and how the class knows about being a task (type) at all. Since the decorated class is a subclass of your class you can use the protected modifier to encapsulate your method.
Related
Gradle documentation states that using the getProject() method in the #TaskAction method of a Task class should not be used if you want compatibility with Gradle Configuration Cache. The question I have is that, suppose you have something like this:
public abstract class AbstractMyTask extends DefaultTask {
#Internal
protected abstract DirectoryProperty getRootDirectory();
protected AbstractMyTask() {
getRootDirectory().convention(getProject().getRootProject().getLayout().getProjectDirectory());
}
}
The general intent of the code snippet is to have a Directory property representing the root project directory (ie. a safe replacement for getProject().getRootDir()), and it seems like the getProject() call in the constructor would be okay. I'd like some sober second thought on whether that is the case.
Suppose I have a custom Gradle task that uses ExecOperations.javaexec() in its code:
public class MyTask extends DefaultTask {
private final ExecOperations execOps
#Inject
public MyTask(ExecOperations execOps) {
this.execOps = execOps;
}
#TaskAction
public void run() {
ExecResult result = execOps.javaexec(spec -> {
// classpath is set to some jar with a main class
spec.classpath(...);
spec.args(...);
});
// Do more stuff with result
}
}
The issue I have is that I am not sure how I can create a Spock test fixture for it to validate that the jar is being run with the correct arguments without actually running the jar itself. (The jar makes remote calls, for example, which I want to avoid).
It seems like the only plausible way forward is to potentially find a way to expose the ExecOperations for testing and possibly replace it with a stub, along with turning the Action<JavaExecSpec> from a mere lambda to something more substantial. Any ideas would be appreciated.
For context, I'm attempting to write a plugin which will generate source code (Java classes), and it exposes multiple tasks (for different purposes). These tasks rely on information given by the user via extensions. Without this user defined information, these tasks wouldn't know what to do, and so they shouldn't be run at all if the extension hasn't been defined in build.gradle by the user.
One key thing to note is that, since I'm generating source code, I'm making the JavaCompile task depend on each of these tasks. However, I only want to define this relationship if the extension has been configured in build.gradle. How do I achieve this?
The following is a simple example for one task.
The MyPlugin class which is the implementation class:
public class MyPlugin implements Plugin<Project> {
// define extensions
MyPluginConfig myPluginConfig = project.getExtensions().create("myPluginConfig", MyPluginConfig.class);
((ExtensionAware) myPluginConfig).getExtensions().create("fileConfig", FileConfig.class);
// register task
TaskProvider<MyPluginTask> taskProvider = project.getTasks().register("myPluginTask", MyPluginTask.class, task -> {
task.getFilesConfigs().set(myPluginConfig.getFileConfigs());
});
// ensure JavaCompile task depends on this plugin's task
project.getTasks().withType(JavaCompile.class).configureEach(task -> task.dependsOn(taskProvider));
}
MyPluginConfig is an extension class which just contains a list of FileConfig:
public abstract class MyPluginConfig {
public abstract ListProperty<FileConfig> getFileConfigs();
}
FileConfig is an extension class (only used within MyPluginConfig) which contains the actual information the task may use to generate Java classes:
public abstract class FileConfig {
public abstract Property<String> getX();
public abstract Property<String> getY();
public abstract Property<String> getZ();
}
And finally, MyPluginTask uses the information from these extensions to generate the Java classes:
public abstract class MyPluginTask {
#Input
public abstract ListProperty<FileConfig> getFileConfigs();
#TaskAction
public void execute() {
System.out.println("Executing task...");
}
}
In build.gradle, a user can now use this plugin like so:
plugins {
id 'com.example.my-plugin' version '0.0.1'
}
/*
myPluginConfig {
fileConfigs = [
fileConfig {
x = 'x'
y = 'y'
z = 'z'
},
fileConfig {
x = '2x'
y = '2y'
z = '2z'
},
]
}
*/
Notice that I actually commented out the myPluginConfig configuration above. As such, I don't want my task (i.e, MyPluginTask#execute) to run at all. With the way I've implemented it so far, it will run because of the dependency I created between it and the JavaCompile task.
An easy way to work around this would be to check if MyPluginTask#getFileConfigs returns an empty list. However, this doesn't feel "clean"? It seems a bit fragile since the business logic within the task could change in the future. Is there an official or proper way to do what I'm trying to achieve?
I have the following groovy class as part of my gradle plugin:
class MyClass {
final Expando someOptions
MyClass() {
someOptions = new Expando()
}
def call(Closure configure) {
configure.delegate = someOptions
configure.resolveStrategy = Closure.DELEGATE_ONLY
configure()
}
}
Now I want to user to have the ability to configure this class by adding extra properties to it, but those properties should be stored in someOptions.
I tried doing this in the class:
def call(final Closure configure) {
configure.delegate = someOptions
configure.resolveStrategy = Closure.DELEGATE_ONLY
configure()
}
The user of the plugin can do:
myClass {
hello='world'
}
However, gradle does not seem to understand that the hello property does not exist on the myClass instance but rather on someOptions within the class. Whenever I use the above, I get errors about hello not existing in the MyClass instance.
How do I do this? Is it possible?
FWIW, it works in the groovy console, but not in gradle.
Any classes you define in your plugin are not directly used in Gradle, but wrapped in proxy classes by Gradle. As an example,
Gradle will create a proxy class for the actual class implementation and adds (among other things) also a property setter method. The method has the name of the property and has a single argument of the same type as the property. It is different from the setProperty and getProperty methods already added by Groovy. For example if we have a task with a property with the name message of type String then Gradle will add the method message(String) to the proxy class. (Source)
This is the reason, why you can omit the assignment sign in Gradle scrips:
task myTask {
myProperty true // uses Gradle generated method
myProperty = true // uses Groovy generated setter
}
Gradle also adds a method similar to yours to allow the configuration of any object in the DSL:
myExtension {
// this works thanks to Gradle
}
Without this proxy method, it would be necessary to use the method with(Closure) from the Groovy language for any block:
myExtension.with {
// this works thanks to Groovy
}
It seems like this proxy method overrides the call(Closure) method of your example.
To solve this, you could use the Delegate annotation in Groovy on someOptions. This would make all its properties available to the MyClass instance. You could also register someOptions as convention on MyClass.
EDIT
You can see that your method is never called by comparing the stacktrace of your current example and a second stacktrace, after you changed the name of the call method and called it explicitly (you need to use another property to get the same exception).
I'm working on a Gradle plugin that has a Custom Task with two member variables that I would like to initialize using an extension class. I am extending the plugin class as follows:
class CustomPlugin implements Plugin<Project> {
#Override void apply(Project project) {
def extension = project.extensions.create("Custom", CustomExtension)
project.task("doTask", type: CustomTask, {
group = "Awesome"
description = "Runs a custom routine"
filePath = extension.filePath
name = extension.name
})
}
}
Is this the proper way to initialize a Task that extends DefaultTask? I'm trying to understand if initialization is done in CustomPlugin or if within the CustomTask one would use:
#TaskAction void removeUnusedResources() {
String filePath = project.Custom.lintXmlFilePath
String name = project.Custom.ignoreResFiles
// Proceed with the task action
}
Only the second approach seems to work for me. One common pattern I am noticing in other plugins is that tasks tend to be defined within the plugin using project.task("taskName") << { // task actions here} instead of creating a separate class that extends DefaultTask. What is the right convention and where can I find more information?