How should configuration be passed to Gradle Task from a Gradle extension? - java

I'm creating a Gradle plugin with its corresponding objects for use in the Groovy DSL. I'm confused between the difference and extension and a task and how configuration should be passed between the two along with where the input and out annotations should be put. Here's my task
abstract public class UrlVerify extends DefaultTask {
#Input
abstract public Property<String> getUrl();
#TaskAction
public void verify() {
System.out.println(getUrl().get().toString());
}
}
Here's the extension
abstract public class UrlVerifierExtension {
abstract public Property<String> getUrl();
abstract public Property<Configuration> getConfiguration();
abstract public Property<Boolean> getIgnoreFailures();
public Set<ConflictCategory> getIncludeCategories() {
return includeCategories;
}
}
This plugin simply accepts a URL and validates it.
verification {
url = 'https://www.moooooereee.com/'
configuration = configurations.runtimeClasspath
ignoreFailures = false
}
I have the following plugin. I manually needed to pass the URL from the extension to the task and wondered if this is the correct way?
public class UrlVerifierPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getPluginManager().apply(JavaLibraryPlugin.class);
UrlVerifierExtension extension = project.getExtensions().create("verification", UrlVerifierExtension.class);
UrlVerify verifyUrlTask = project.getTasks().create("verifyUrl", UrlVerify.class);
verifyUrlTask.getUrl().set(extension.getUrl());
}
}
Along with this, it is also unclear whether the #Input annotation belongs to the properties of the extension or the task?

You seem to have followed the examples from the Gradle documentation very precisely. This is the correct way to configure your custom tasks. The exact purpose of extensions is to have user-provided settings which are then consumed by your plugin to configure it and tasks.
Extensions are for the user to provide settings.
Tasks are for executing an action while Gradle is running.
The #Input annotation is used by Gradle to determine if the tasks needs to run. If the tasks has not run before, or if the input value has changed since the previous execution, then the tasks will run again.
Outputs declare some result produced by running the task. An example is a task that compiles Java files. The outputs would the the class files produced from the compilation process. If output files are modified or deleted by something other than the task that created them, then the task that created them is out-of-date, and Gradle will run it again.
Also, a task can declare the outputs of another task as its input. If task A creates some output files, and task B uses the outputs of task A as an input, then task B will be run when task A updates or creates its files.
In your case with the #Input annotation, my guess is that you do not want that in this case, because it tells Gradle that your tasks only needs to run once, then after that, only if the user updates the setting.

Related

How to define gradle tasks dependency in a task in custom gradle plugin

I'm writing a custom gradle plugin in which I want to have a bunch of common for several of my projects tasks and a sort of a 'main' task to control which of these tasks to turn on.
Regular tasks in the plugin are e.g.:
CopyDockerResourcesTask
CopyContainerFilesTask
PerformAnalysisTask
and the 'main' task is:
BaseProjectTask
so then in the project in build.gradle I'd like to be able to do this:
BaseProjectTask {
copyDockerResources = true
copyContainerFiles = true
performAnalysis = true
}
I want the default behaviour of the plugin to be to not to do anything, only add certain tasks if they are turned on in BaseProjectTask.
I wanted to achieve this with adding task dependency in #TaskAction method of BaseProjectTask:
class BaseProjectTask extends DefaultTask {
private final BaseProjectExtension extension
private final Project project
#Optional
#Input
Boolean copyContainerFiles = false
...
#Inject
BaseProjectTask(Project project, BaseProjectExtension extension) {
this.project = project
this.extension = extension
}
#TaskAction
def execute() {
if (copyContainerFiles) {
project.tasks.assemble.dependsOn(project.tasks.copyContainerFiles)
}
...
}
}
Creating task dependency, this line:
project.tasks.assemble.dependsOn(project.tasks.copyContainerFiles)
doesn't work.
Edit:
My current findings are that defining task dependency in #TaskAction is too late as this is execution phase. I could do it in the constructor (this way it works) but its too early as property copyContainerFiles isn't set yet.
Does anyone know a way of adding code in the task class that would be fired in the configuration phase? I think this is what I'm missing.
You need to configure task dependencies during the build configuration phase, as you surmised.
It's not possible to do it in the #TaskAction method. It's fundamental to the way Gradle works that it needs to know how tasks depend on each other before it starts executing the build. That allows Gradle to do some useful things, such as only executing the tasks that are not up to date, or working out what tasks will execute without actually executing them.
In general, tasks should not be aware of one another1.
When you are trying to do this in a plugin using values in a project extension, you must wait until after the project has evaluated so that the build script code executes first. You can do this with project.afterEvaluate()2.
So you can do the following (using Kotlin DSL3):
project.afterEvaluate {
tasks.register("baseTask") {
if (baseProjectExtension.copyDockerResources)
dependsOn(tasks.getByName("copyDockerResources"))
if (baseProjectExtension.copyContainerFiles)
dependsOn(tasks.getByName("copyContainerFiles"))
if (baseProjectExtension.performAnalysis)
dependsOn(tasks.getByName("performAnalysis"))
}
}
1See How to declare dependencies of a Gradle custom task?
2See https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:project_evaluation
3What I am familiar with. Hopefully not too much trouble to convert to Groovy.

Does Maven offer hooks at the very beginning of runtime

Say we are running mvn test.
I am wondering if there is a way to configure Maven to run some files before executing tests. In my case, I want to configure a library, but don't want to have to configure this library for every entrypoint in my app/tests. I am just looking to configure the lib for every mvn lifecycle hook which invokes a runtime.
Something like this:
#MavenRuntimeLifecycle
public class Whatever {
public void runtimeBegin(){
// right when the java process starts up
Mylib.configure("foo");
}
public void runtimeEnd(){
// right before the process shuts down
}
}
I assume this would be a Maven specific thing - not that it has to be in the same Java process as my server or tests etc.
Note that using Node.js, I would simply do it like so:
export class MyLib {
isConfigLoaded = false;
static loadConfig(){
// ...
}
static void run(){
if(!this.isConfigLoaded){
MyLib.loadConfig(require('../some/path/to/.mylib.config.js'));
this.isConfigLoaded = true;
}
this.doTheThing();
}
}
I could do the same thing with Java or Maven project, and just store a .java file in the resources directory. It's more manual, but it could be done.

Timing how long each file takes to compile with ant

Occasionally a slight modification to a Java source file like some additional explicit casts to help the compiler can improve compile time from 4 minutes to 3 seconds for a single java file (Especially in Java 8).
The problem is: In a large java project, how do you find which particular .java files are compiling slowly?
Is there a way to get Ant to time how long it takes to compile each individual .java file?
I think that this might be possible. Here's what I've found:
If you're using Java 8, you can register a Plugin with the compiler to add some additional functionality during compilation. The documentation has this to say about plugins:
It is expected that a typical plug-in will simply register a TaskListener to be informed of events during the execution of the compilation, and that the rest of the work will be done by the task listener.
So you can setup a plugin to use a TaskListener, and have the task listener log timestamps when class are being generated.
package xyz;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
public class TimestampPlugin implements Plugin {
#Override
public String getName() {
return "Timestamp_Plugin";
}
#Override
public void init(JavacTask task, String... strings) {
task.setTaskListener(new FileTimestampListener());
}
}
Documentation for TaskListener. A task listener is passed a TaskEvent, which has a Kind. In your case it sounds like you're interested in generation.
package xyz;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import java.util.HashMap;
public class FileTimestampListener implements TaskListener {
HashMap<String, Long> timeStampMap = new HashMap<>();
#Override
public void started(TaskEvent taskEvent) {
if(TaskEvent.Kind.GENERATE.equals(taskEvent.getKind())) {
String name = taskEvent.getSourceFile().getName();
timeStampMap.put(name, System.currentTimeMillis());
}
}
#Override
public void finished(TaskEvent taskEvent) {
if(TaskEvent.Kind.GENERATE.equals(taskEvent.getKind())) {
String name = taskEvent.getSourceFile().getName();
System.out.println("Generated " + name + " over " + (System.currentTimeMillis() - timeStampMap.get(name)) + " milliseconds");
}
}
}
This is a simple example but it should be straightforward from here to set up something like a log file to store the information gathered. As you can see in the plugin's init function, arguments can be passed to the Plugin from the command line.
The plugin is configured by specifying it with the -Xplugin compiler argument. I'm not sure why but there doesn't appear to be any documentation on this page about it, but it can used by setting up a file called com.sun.source.util.Plugin (the FQ class name of the interface to implement) in your META-INF/services directory. So:
META-INF
|-- services
|-- com.sun.source.util.Plugin
And in that file list the FQ class name of your implementation of this class. So the file contents would be:
xyz.TimestampPlugin
In your Ant task you'll just need to specify a compiler flag -Xplugin:Timestamp_Plugin (note this is the name provided by the Plugin's getName() function). You'll also need to provide the compiled Plugin and runtime dependencies on the classpath, or the annotation processor path, if one is specified.

Injecting & Configuring Gradle Builds

I'm reading up on Gradle and am very interested in it, specifically because (it appears) that it allows the introduction of inheritance into the build process. For instance, if I have a Java web app that might be packaged and deployed to Google App Engine instances as well as Amazon EC2 instances, I need a sophisticated build that can take the same Java, XML, PROPERTIES, CSS and image files and package/deploy them into 2 drastically-differently packaged WAR files.
GAE apps are very specific as to how they are packaged; EC2 (pretty much) just require that you conform to servlet specs. GAE apps get "deployed" by running an update command from the appcfg.sh script that comes with your SDK; EC2 has their own way to deploy apps. The point is, they are very different packaging/deployment processes for both PaaS providers:
public abstract class PackageTask {
// ...
}
// Package my Eclipse project for deployment to GAE.
public class AppEnginePackageTask extends PackageTask {
// ...
}
// Package my Eclipse project for deployment to EC2 instances.
public class AmazonPackageTask extends PackageTask {
// ...
}
public abstract class DeployTask {
// ...
}
// Deployment to GAE.
public class AppEngineDeployTask extends DeployTask {
// ...
}
// Deployment to EC2.
public class AmazonDeployTask extends DeployTask {
// ...
}
Then, I might have a myapp.gradle buildfile that templates the build order of tasks:
clean()
compile()
package()
deploy()
...and somehow, I can configure/inject AppEnginePackageTask/AppEngineDeployTask in place of package()/deploy() for a GAE-based build, or I can configure/inject AmazonPackageTask/AmazoneDeployTask into those templated tasks. Again, I'm not sure how to do this (or even if Gradle can do this), but it's what I'm after.
My understanding was that Gradle can do this. Ant can also be forced to have highly-modular, elegant builds that work this way, but being XML-based, it takes some finessing, whereas an OOP-based language like Groovy makes this cleaner and simpler.
However, all the examples I see of Gradle tasks take the following form:
task package(dependsOn: 'compile') {
// ...
}
task deploy(dependsOn: 'package') {
// ...
}
So I ask: these look/feel like non-OOP task definitions. Is my understanding of Gradle (and its OOP nature) fundamentally incorrect? What am I missing here? How can I accomplish these notions of "configurable/injectable build templates" and inheritance-based tasks? Thanks in advance!
Edit I re-tagged this question with "groovy" because Gradle buildscripts are written in a Groovy DSL, and someone who happens to be a Groovy-guru (say that 5 times fast) might also be able to chime in even if they know little about Gradle.
As described here, there are simple tasks and enhanced tasks. The latter are much more flexible and powerful.
The following example isn't exactly what you describe, re:injection, but it illustrates OOP.
Here is the sample build.gradle file. It avoids "package" as that is a keyword in Java/Groovy. The 'build' target depends on 'compile' and some flavour of 'doPack', depending on a property called 'pkgTarget'.
task compile << {
println "compiling..."
}
task build() << {
}
build.dependsOn {
compile
}
build.dependsOn {
if (pkgTarget == "Amazon") {
task doPack(type: AmazonPackageTask)
} else if (pkgTarget == "Google") {
task doPack(type: GooglePackageTask)
} else {
task doPack(type: MyPackageTask)
}
}
where the tasks are defined later in the same file. (Per doc, this code can go into a "build src" directory):
// -----
class MyPackageTask extends DefaultTask {
def init() { println 'common stuff' }
#TaskAction
def doPackage() {
println 'hello from MyPackageTask'
}
}
class AmazonPackageTask extends MyPackageTask {
#TaskAction
def doPackage() {
init()
println 'hello from AmazonPackageTask'
}
}
class GooglePackageTask extends MyPackageTask {
#TaskAction
def doPackage() {
init()
println 'hello from GooglePackageTask'
}
}
and here is the gradle.properties file:
pkgTarget=Amazon

Calling ant target in known xml file from within an Ant Task

I'm writing an Ant Task:
public class MyTask extends Task {
public void execute() {
....
}
}
Now I'm wondering whether it is possible for me to call a target that exists in another known xml file from within the above execute() method?
Something like:
public void execute() {
AntCaller.call("anotherBuildFile.xml", "someTarget");
}
You are on the right track. If you wanted to all another task from XML, you would use <ant> (since it is another file.) You can call a task from Java only if you have the .class file for it. Luckily, you do have the .class file for the Ant task itself so you can use the same technique as you would in a build xml:
Ant helper = new Ant();
helper.setTarget("someTarget");
helper.setAntFile("anotherBuildFile.xml");
helper.execute();

Categories

Resources