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.
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.
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'm developing a Gradle custom plugin and I'm having issues on how to test it.
The plugin creates an extension to receive configuration and after evaluation (project.afterEvaluate {) creates a tasks with the received configuration, those values are #Input on tasks.
Following the documentation https://docs.gradle.org/current/userguide/custom_plugins.html to create a test for the plugin, I use the following to create the project and apply the plugin
#Before fun setup() {
project = ProjectBuilder.builder().build()
project.pluginManager.apply("my.plugin.name")
and then test that extension got created:
assertTrue(project.extensions.findByName("name") is MyConfigType)
and the task got created:
assertTrue(project.tasks.findByName("mytask") is MyTaskType)
The issue I'm having is that the task is only created afterEvaluate, so this test is failing. As far as I understood, it has to be afterEvaluate so that it can receive the configuration values.
So the only way I could see if I could on the test force this project to be evaluated, but how?
Is there maybe a different way to receive values?
I posted my similar question in the gradle forums and was able to solve the issue:
https://discuss.gradle.org/t/unit-test-plugins-afterevaulate/37437/3
Apparently afterEvaluate is not the best/right place to perform the task creation. If you have a DomainObjectCollection in your extension and want to create a task for each element in the collection, task creation should be done in the all-Callback of the collection:
final MyExtension extension = project.getExtensions().create("extension", MyExtension.class);
extension.configurations.all((c) -> {
// register task here
});
If you have simple properties in the extension that are feed to the task as input, you should use lazy configuration:
public class MyExtension {
public final Property<String> property;
public final NamedDomainObjectContainer<Configuration> configurations;
#Inject
public MyExtension(final ObjectFactory objectFactory) {
property = objectFactory.property(String.class).convention("value");
configurations = objectFactory.domainObjectContainer(Configuration.class);
}
}
public abstract class MyTask extends DefaultTask {
#Input
private final Property<String> property = getProject().getObjects().property(String.class);
public Property<String> getProperty() {
return property;
}
}
And the apply method:
public class MyPlugin implements Plugin<Project> {
#Override
public void apply(final Project aProject) {
final MyExtension extension = aProject.getExtensions().create("extension", MyExtension.class);
aProject.getTasks().register("myTask", MyTask.class).configure((t) -> {
t.getProperty().set(extension.property);
});
}
}
You should call project.evaluationDependsOn(":"):
#Before fun setup() {
project = ProjectBuilder.builder().build()
project.pluginManager.apply("my.plugin.name")
project.evaluationDependsOn(":") // <<--
...
}
It executes your afterEvaluate callback.
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.
I would like to parse an android app's build.gradle file in Java and I am trying to use Groovy CodeVistorSupport for that as follows:
public class parseBuildGradle extends CodeVisitorSupport{
#Override
public void visitMethodCallExpression(MethodCallExpression call)
{
//My code
}
}
In order to use this class, I assume I should somehow get the compilation unit or ast and then call the class. However, I am not sure what APIs I should be using and unfortunately I could not find any related documentation. I am wondering if anyone can help me with that.
Below is a possible answer to my question:
SourceUnit unit = SourceUnit.create("gradle", gradleFileToString);
unit.parse();
unit.completePhase();
unit.convert();
visitScriptCode(unit, new parseBuildGradle());
private void visitScriptCode(SourceUnit source, GroovyCodeVisitor transformer) {
source.getAST().getStatementBlock().visit(transformer);
for (Object method : source.getAST().getMethods()) {
MethodNode methodNode = (MethodNode) method;
methodNode.getCode().visit(transformer);
}
}