Shared #Input properties on Gradle Plugin tasks, ideally nested config - groovy

My gradle plugin generates a number of tasks that have shared configuration. This configuration needs to be marked as #Input, so when it's changed, the task is marked as stale and re-evaluated. I'm finding it challenging to share the config when it should apply to multiple tasks. I'm using avoiding project.afterEvaluate to allow incremental compilation. This example is a reduced version of what I currently have:
Current Plugin Code:
class MyPluginTaskOne extends DefaultTask {
#Input config = "default"
#TaskAction
public void action() {
// something that depends on config
}
}
class MyPluginTaskTwo extends DefaultTask {
#Input config = "default"
#TaskAction
public void action() {
// something that depends on config
}
}
class MyPluginExtension {
// blank for now
}
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("myPluginConfig", MyPluginExtension)
project.tasks.create(name: 'myPluginTaskOne', type: MyPluginTaskOne) {}
project.tasks.create(name: 'myPluginTaskTwo', type: MyPluginTaskTwo) {}
}
}
Current Config:
Currently the best way I have to share state is the following. This has the problem is that it's error prone and doesn't automatically sharing the setting:
apply plugin: MyPlugin
// Kludgy way of sharing configuration across two tasks:
def sharedConfig = "SHARED-CONFIG"
myPluginTaskOne {
config sharedConfig
}
myPluginTaskTwo {
config sharedConfig
}
Preferred Config:
What I'd like to do is a configuration something like the following, but with all the benefits of tracking #Input dependencies and up-to-date tests.
myPluginConfig {
config "SHARED-CONFIG"
// myPluginTaskOne and myPluginTaskTwo both gets automatic
// 'SHARED-CONFIG' through Gradle
}
It appears that you can automatically add dependencies between tasks (see below). Is it possible to configure only the first task and then have that #Input trickle down to the #Input on the second task?
Let us try removing the task dependencies by relying on how CopySpec.from() evaluates arguments with Project.files(). Gradle can automatically add task dependencies for us. This also adds the output of the generator task as inputs to the zip task.
From https://gradle.org/feature-spotlight-incremental-builds/

To build on Mark's comments. Here is an example of a property that applies to all tasks and cannot be overridden (config).
class MyPluginTaskOne extends DefaultTask {
#Input String getConfig() { project.myPluginConfig.config }
#TaskAction
public void action() {
// something that depends on config
}
}
class MyPluginTaskTwo extends DefaultTask {
#Input String getConfig() { project.myPluginConfig.config }
#TaskAction
public void action() {
// something that depends on config
}
}
class MyPluginExtension {
String config
}
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.with {
extensions.create("myPluginConfig", MyPluginExtension)
tasks.create(name: 'myPluginTaskOne', type: MyPluginTaskOne) {}
tasks.create(name: 'myPluginTaskTwo', type: MyPluginTaskTwo) {}
}
}
}

The most common convention is to use an extension to do this. It looks like you've started to do this. You would then define the property on the extension, then your plugin would read the extension and set the property on all relevant tasks.
myPluginConfig {
sharedConfig 'value'
}
In your plugin:
def extension = extensions.create("myPluginConfig", MyPluginExtension)
project.afterEvaluate {
// read prop from `extension` and set prop on tasks
}

Related

SpringBootTest does not load context

I am trying to write functional-test case for a rest controller. According to plan I'd like to start application and using TestRestTemplate call appropriate endpoint. Unfortunately, I am unable to do so because occurring error:
Cannot invoke method exchange() on null object
java.lang.NullPointerException: Cannot invoke method exchange() on null object
...
I can't figure out why and it is the only error message that I get. I feel confused ://
In addition, I'm using Spock framework for testing.
Thanks for your help in advance.
gradle.build
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'org.unbroken-dome.test-sets' version '4.0.0'
id 'java'
id 'groovy'
id 'maven-publish'
}
apply from: "${rootDir}/gradle/test.gradle"
repositories {
mavenCentral()
}
dependencies {
implementation(group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.6.3')
testImplementation(group: 'org.codehaus.groovy', name: 'groovy-all', version: '3.0.9')
testImplementation(group: 'org.spockframework', name: 'spock-core', version: '2.0-groovy-3.0')
testImplementation(group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.6.3')
}
// some more usual Gradle stuff
test.gradle
testSets {
integrationTest { dirName = 'integration-test' }
functionalTest { dirName = 'functional-test' }
}
integrationTest {
mustRunAfter(test)
}
check.dependsOn(integrationTest)
functionalTest {
mustRunAfter(integrationTest)
}
check.dependsOn(functionalTest)
tasks.withType(Test) {
useJUnitPlatform()
}
GreetingControllerSpec
package tchorzyksen
/* imports */
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GreetingControllerSpec extends Specification {
#Autowired
private TestRestTemplate testRestTemplate
void "get greeting"() {
when:
ResponseEntity<GreetingEntity> response = get("/greeting", GreetingEntity.class)
then:
response.getStatusCode() == HttpStatus.OK
}
protected <T> ResponseEntity<T> get(String uri, Class<T> responseClass) {
return testRestTemplate.exchange(uri, HttpMethod.GET, null, responseClass, [:])
}
}
MyWs.java
package tchorzyksen;
/* imports */
#SpringBootApplication
public class MyWs {
public static void main(String[] args) {
SpringApplication.run(MyWs.class, args);
}
}
and GreetingController
package tchorzyksen.ui.model.controller;
/* imports */
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#GetMapping("/greeting")
public ResponseEntity<GreetingEntity> greeting(
#RequestParam(value = "name", defaultValue = "World") String name) {
return ResponseEntity.ok(
new GreetingEntity(counter.incrementAndGet(), String.format(template, name)));
}
}
Instead
#Autowired
private TestRestTemplate testRestTemplate
Enough
private TestRestTemplate testRestTemplate = new TestRestTemplate();
For all people interested in solution to this question. We are missing one dependency here
testImplementation group: 'org.spockframework', name: 'spock-spring', version: '2.1-M2-groovy-3.0'
Which basically makes it possible to write tests in Spock and use spring test context.
Save yourselfs guys it took me 4 hours to discover (very sad face) ://

Correct way to configure gradle plugin extensions with groups of dynamic objects

I am trying to write my own gradle plugin and it needs to be able to configure a set of objects - how many of these objects and what they're called is up to the user.
The doco for creating custom gradle plugins with advanced customisability is quite poor. It mentions project.container() method to do this kind of thing, but I couldn't figure out how to make it work in my usecase.
This is an example of my plugin's configuration DSL as it stands:
teregrin {
terraformVersion = '0.6.6'
root("dev"){
accessKey = "flobble"
}
root("prd"){
}
}
And this is my plugin extension object that allows me to configure it:
class TeregrinPluginExtension {
boolean debug = false
boolean forceUnzip = false
String terraformVersion = null
Set<TeregrinRoot> roots = []
def root(String name, Closure c){
def newRoot = new TeregrinRoot(name)
c.setDelegate(newRoot)
c()
roots << newRoot
}
}
The extensions wired up in my plugin in the standard way:
project.extensions.create("teregrin", TeregrinPluginExtension)
This works ok, but it's a pretty ugly configuration style, not really in the style of the typical gradle DSL.
How can I change my plugin configuration DSL to be something like this:
teregrin {
terraformVersion = '0.6.6'
roots {
dev {
accessKey = "flobble"
}
prd {
}
}
}
The gradle way of implementing such DSL is by using extensions and containers:
apply plugin: SamplePlugin
whatever {
whateverVersion = '0.6.6'
conf {
dev {}
qa {}
prod {
accessKey = 'prod'
}
}
}
task printWhatever << {
println whatever.whateverVersion
whatever.conf.each { c ->
println "$c.name -> $c.accessKey"
}
}
class SamplePlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create('whatever', SampleWhatever)
project.whatever.extensions.conf = project.container(SampleConf)
project.whatever.conf.all {
accessKey = 'dev'
}
}
}
class SampleWhatever {
String whateverVersion
}
class SampleConf {
final String name
String accessKey
SampleConf(String name) {
this.name = name
}
}
while groovy way of doing implementing such DSL is meta programming - you need to implement methodMissing in this particular case. Below is a very simple example that demonstrates how it works:
class SomeExtension {
def devConf = new SomeExtensionConf()
void methodMissing(String name, args) {
if ('dev'.equals(name)) {
def c = args[0]
c.resolveStrategy = Closure.DELEGATE_FIRST
c.delegate = devConf
c()
} else {
throw new MissingMethodException("Could not find $name method")
}
}
def getDev() {
devConf
}
}
class SomeExtensionConf {
def accessKey
}
project.extensions.create('some', SomeExtension)
some {
dev {
accessKey = 'lol'
}
}
assert 'lol'.equals(some.dev.accessKey)
Of course it has no error checking - so the args size and type of each argument need to be validated - it's omitted for the sake of brevity.
Of course there's no need to create a separate class for each configuration (I mean dev, prod, etc.). Create a single class that holds configuration and store them all in a Map where key is configuration name.
You can find a demo here.

Unit tests - run task programmatically

I'm creating custom task for gradle. I don't know how I can create task which will use my custom task class. Is it possible? I want to create this task for functional tests which will be runned on jenkins.
This is my custom task:
package pl.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class MyCustomTask extends DefaultTask {
public MyCustomTask() {
// do something
}
#TaskAction
def build() {
ant.echo(message: "only for tests")
}
}
And this is my test class:
package pl.gradle
import static org.junit.Assert.*
import org.gradle.testfixtures.ProjectBuilder
import org.gradle.api.Project
import org.junit.Before;
import org.junit.Test
class MyCustomTaskTest {
private Project project;
def task
#Before
public void setUp() {
project = ProjectBuilder.builder().build()
task = project.task("build", type: MyCustomTask)
}
#Test
public void taskCreatedProperly() {
assertTrue(task instanceof MyCustomTask)
}
#Test
public void shouldRunTask() {
// task.execute() // how to run this task? I want to run build() method from MyCustomTask class which is #TaskAction
}
}
ProjectBuilder is meant for lower-level tests that don't execute tasks. Its sweet spot is testing plugins. In addition you'd write higher-level tests that execute real builds (and therefore also tasks). You'd typically use the Gradle tooling API to kick off these builds. Check out the tooling API samples in the full Gradle distribution.
Call Action.execute yourself for each of the task actions:
project.tasks.getByName("myTask").with { Task task ->
assertEquals task.group, 'My Group'
task.actions.each { Action action ->
action.execute task
}
}
I've previously mentioned this here: https://stackoverflow.com/a/63613564/410939

Groovy way to selectively mixin methods from multiple classes

I'm writing a Groovy script based on commons-io that monitors some source directory and synchronizes its files with some destination directory.
#Grab(group='commons-io', module='commons-io', version='2.4')
import org.apache.commons.io.monitor.*
import org.apache.commons.io.FileUtils
class BaseSynchronizedFileListener extends FileAlterationListenerAdaptor {
def srcDir
def destDir
/* Given a source file, returns the destination file */
File destination(File file) {
new File(destDir, file.getAbsolutePath() - srcDir.getAbsolutePath())
}
}
class CopyOnWriteListener extends BaseSynchronizedFileListener {
#Override
void onFileChange(File file) {
FileUtils.copyFile(file, destination(file))
}
#Override
void onFileCreate(File file) {
FileUtils.copyFile(file, destination(file))
}
}
class DeleteOnDeleteListener extends BaseSynchronizedFileListener {
#Override
void onFileDelete(File file) {
FileUtils.deleteQuietly(destination(file))
}
}
In addition to straight file copies, I want to support Less->CSS compilation, wherein .less files in the source directory are synchronized with .css files in the destination directory.
#Grab(group='org.lesscss', module='lesscss', version='1.3.3')
import org.lesscss.LessCompiler
class CompileLessOnWriteListener extends BaseSynchronizedFileListener {
def compiler = new LessCompiler()
#Override
File destination(File file) {
File dest = super.destination(file)
new File(dest.parentFile, dest.name - '.less' + '.css')
}
void compile(File less) {
compiler.compile(less, destination(less))
}
#Override
void onFileChange(File less) {
compile(less)
}
#Override
void onFileCreate(File less) {
compile(less)
}
}
The problem I'm encountering is when I attempt to create class DeleteCssOnDeleteLessListener to handle the situation when .less files are deleted (which, in turn, deletes the corresponding .css file) -- the code I need to do this exists in two different inheritance trees.
CompileLessOnWriteListener contains the destination() method
DeleteOnDeleteListener contains the onFileDelete() method to delete the CSS file returned by the destination() method
Is there a "Groovy way" to selectively mixin or inherit methods from both of these classes into a new class?
Or do I just need to bite the bullet and create a common super class for CompileLessOnWriteListener and DeleteCssOnDeleteLessListener?
Update
Changed the implementation. Lets see if i got the idea. You need:
Inherit two methods
"Inherit" constructor
It needs to be an instance of an interface
I think a heavy metaprogramming helps here. We can declare two objects to DeleteCssOnDeleteLessListener delegate methods to, and these objects will be accessing properties from it.
For the interface, i think you are better using the as Interface operator.
Dynamically "inherit" the constructors may get tricky. Since it is only two properties, i've declared them. You can delegate the getProperty/setProperty to one of the other two objects, if you prefer DRYing your code:
class DeleteCssOnDeleteLessListener {
def destDir, srcDir
def onLessDelete(file) {
onFileDelete destination( file )
}
}
class CompileLessOnWriteListener {
def destination(file) {
"destination $file from $srcDir"
}
}
class DeleteOnDeleteListener {
def onFileDelete(file) {
"onFileDelete $file and $destDir"
}
}
def delete = new DeleteCssOnDeleteLessListener(destDir: "dest/dir", srcDir: "src/dir")
def compileLess = new CompileLessOnWriteListener()
def deleteOnDelete = new DeleteOnDeleteListener()
delete.metaClass {
destination = compileLess.&destination
onFileDelete = deleteOnDelete.&onFileDelete
}
compileLess.metaClass.getProperty = { property -> delete.getProperty property }
deleteOnDelete.metaClass.getProperty = { property -> delete.getProperty property }
assert delete.onLessDelete("style.less") == "onFileDelete destination style.less from src/dir and dest/dir"
It's not very "Groovy", in my opinion, nor very efficient looking, but at least this approach solves my problem without having to create a common superclass:
class DeleteCssOnDeleteLessListener extends DeleteOnDeleteListener {
#Override
File destination(File f) {
new CompileLessOnWriteListener(destDir: this.destDir, srcDir: this.srcDir).destination(f)
}
}

Use Groovy Category implicitly in all instance methods of class

I have simple Groovy category class which adds method to String instances:
final class SampleCategory {
static String withBraces(String self) {
"($self)"
}
}
I want to use this category in my unit tests (for example). It looks like this:
class MyTest {
#Test
void shouldDoThis() {
use (SampleCategory) {
assert 'this'.withBraces() == '(this)'
}
}
#Test
void shouldDoThat() {
use (SampleCategory) {
assert 'that'.withBraces() == '(that)'
}
}
}
What I'd like to achieve, however, is ability to specify that category SampleCategory is used in scope of each and every instance method of MyTest so I don't have to specify use(SampleCategory) { ... } in every method.
Is it possible?
You can use mixin to apply the category directly to String's metaClass. Assign null to the metaClass to reset it to groovy defaults. For example:
#Before void setUp() {
String.mixin(SampleCategory)
}
#After void tearDown() {
String.metaClass = null
}
#Test
void shouldDoThat() {
assert 'that'.withBraces() == '(that)'
}
Now you have the option to use extension modules instead of categories:
http://mrhaki.blogspot.se/2013/01/groovy-goodness-adding-extra-methods.html
On the plus side Intellij will recognize the extensions. I've just noticed that it doesn't even need to be a separate module as suggested by the link, just add META-INF/services/org.codehaus.groovy.runtime.ExtensionModule to the project:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = module
moduleVersion = 1.0
extensionClasses = SampleExtension
The extension class is pretty much defined like a normal category:
class SampleExtension {
static String withBraces(String self) {
"($self)"
}
}
Can be used like:
def "Sample extension"() {
expect: 'this'.withBraces() == '(this)'
}
If you are using Spock there is a #Use annotation that can be used on the specifications. The drawback with that is that Intellij will not recognize it.

Resources