How to instantiate a FileTree in a Groovy class managed by Gradle? - groovy

I have a Gradle build script, that grew too big so I made a utility class.
In this class I want to use the Gradle fileTree (or any other Gradle class), how can I do it?
To be clear, this is in build.gradle:
ext {
utils = new Utils()
}
and in Utils.groovy (which is in buildSrc/src/main/groovy):
def chopBackgroundImage(String inPath, String outPath, int scale) {
new File(outPath).mkdirs();
def tree = fileTree(dir: inPath, include: '*.png') // doesnt work
}

fileTree is a method defined on Project interface so there's a need to pass project instance to method and import Project class in Utils. Utils should look like this:
import org.gradle.api.Project
public class Utils {
def chopBackgroundImage(Project project, String inPath, String outPath, int scale) {
new File(outPath).mkdirs();
def tree = project.fileTree(dir: inPath, include: '*.png')
}
}
To make Project accessible in buildSrc modify build.gradle by adding the following content:
buildscript {
dependencies {
gradleApi()
}
}
And - of course - because of the fact that groovy is a dynamic language chopBackgroundImage can be defined in the following way:
def chopBackgroundImage(project, inPath, outPath, scale) {
new File(outPath).mkdirs()
def tree = project.fileTree(dir: inPath, include: '*.png')
}
No dependencies needed! ;)

Related

How to use Dagger for Groovy?

How to configure dagger to inject groovy classes, and to inject into groovy classes?
I was initially trying to get dagger to inject a groovy class into my java app, and I found dagger was complaining the groovy class is not found. Looking at the log, it seems that compileGroovy happens after compileJava. And the annotation processing of dagger compiler seems to be in compileJava. I guessed that might be the problem -- no groovy classes are available at this time. But I've yet figured out a way to coerce either of dagger or groovy to work with the other.
It seems I could not upload a .tar.gz. But if anyone needs a minimal demo code for what I meant to achieve, these might help (with gradle 7):
build.gradle:
plugins {
id 'groovy'
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
annotationProcessor 'com.google.dagger:dagger-compiler:2.+'
implementation 'com.google.dagger:dagger:2.+'
implementation 'org.codehaus.groovy:groovy-all:3.+'
}
settings.gradle:
rootProject.name = 'groovy-dagger1'
src/main/groovy/org/example/dagger/MainComponent.groovy:
package org.example.dagger
import dagger.Component
#Component(modules = [
MainModule,
])
interface MainComponent {
String message();
}
src/main/groovy/org/example/dagger/MainModule.groovy:
package org.example.dagger
import dagger.Module
import dagger.Provides
#Module
final class MainModule {
#Provides
static String message() {
return 'Hello Groovy Dagger!'
}
}
src/main/groovy/org/example/main/Main.groovy:
package org.example.main;
class Main {
static void main(String[] args) {
// Dagger component does not exist :/
// println DaggerMainComponent.create().message()
}
}
By default, the groovy compiler will not run the java annotation processors...
You can add this to your build.gradle:
compileGroovy {
groovyOptions.javaAnnotationProcessing = true
}
You will of course need to add an import
import org.example.dagger.DaggerMainComponent
To Main.groovy

Use Gmail API in a keyword in Katalon Studio

I use this tutorial to connect to Gmail API: https://developers.google.com/gmail/api/quickstart/java
I would like to make a keyword in Katalon Studio, which depends on Gmail API.
I modified from sample code that line:
InputStream in = GmailQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
to this:
InputStream ins = new FileInputStream(CREDENTIALS_FILE_PATH);
JAR files are added, project is running and browser window is opened to get token. After successful authorization I got error message:
Caused by: java.lang.NoSuchMethodError:
com.google.api.client.http.HttpRequest.setResponseReturnRawInputStream(Z)Lcom/google/api/client/http/HttpRequest;
UPDATE: List of imported dependencies:
commons-codec-1.15.jar
commons-logging-1.2.jar
google-api-client-1.31.3.jar
google-api-client-extensions-1.6.0-beta.jar
google-api-client-jackson2-1.31.3.jar
google-api-client-java6-1.31.3.jar
google-api-services-gmail-v1-rev110-1.25.0.jar
google-http-client-1.39.1.jar
google-http-client-jackson2-1.39.1.jar
google-oauth-client-java6-1.31.4.jar
google-oauth-client-jetty-1.31.4.jar
guava-30.1.1-jre.jar
httpclient-4.5.13.jar
httpcore-4.4.14.jar
j2objc-annotations-1.3.jar
jackson-core-2.12.2.jar
jsr305-3.0.2.jar
https://docs.katalon.com/katalon-studio/docs/external-libraries.html#exclude-built-in-libraries
With the ability to remove built-in libraries stored in the .classpath file of a project folder, you can replace a built-in library with an external one for flexible libraries usage in a test project.
Requirements
An active Katalon Studio Enterprise license.
Katalon Studio version 7.8.
UPD:
i got katalon 7.9.1 and here how i was able to do it:
add the following class into KS project:
include/scripts/groovy/(default package)/GroovyBox.java
import groovy.lang.*;
import java.util.regex.Pattern;
import java.util.Map;
import java.util.List;
/** run groovy script in isolated classloader*/
public class GroovyBox {
GroovyShell gs;
public GroovyBox(ClassLoader parentCL, Pattern excludeClassPattern ) {
FilteredCL fcl = new FilteredCL(parentCL, excludeClassPattern);
gs = new GroovyShell(fcl);
}
public GroovyBox withClassPath(List<String> classPathList) {
GroovyClassLoader cl = gs.getClassLoader();
for(String cp: classPathList) cl.addClasspath(cp);
return this;
}
public Script parse(String scriptText) {
return gs.parse(scriptText);
}
public static class FilteredCL extends GroovyClassLoader{
Pattern filterOut;
public FilteredCL(ClassLoader parent,Pattern excludeClassPattern){
super(parent);
filterOut = excludeClassPattern;
}
#Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
if(filterOut.matcher(name).matches())throw new ClassNotFoundException("class not found "+ name);
return super.loadClass(name, resolve);
}
}
}
now add a test case - actually you can move code from test case into a class...
import ... /* all katalon imports here*/
assert method1() == 'HELLO WORLD'
def method1() {
def gb = new GroovyBox(this.getClass().getClassLoader().getParent(), ~/^com\.google\..*/)
def script = gb.parse('''
#Grab(group='com.google.api-client', module='google-api-client', version='1.31.3')
import com.google.api.client.http.HttpRequest
def c = HttpRequest.class
println( "methods execute:: "+c.methods.findAll{it.name=='execute'} )
println( "methods setResponseReturnRawInputStream:: "+c.methods.findAll{it.name=='setResponseReturnRawInputStream'} )
println greeting
return greeting.toUpperCase()
''')
script.setBinding([greeting:'hello world'] as Binding)
return script.run()
}
options to define external dependencies:
#Grab(...) as a first line of parsed script - loads all required dependencies from maven central (by default). for example #Grab(group='com.google.api-client', module='google-api-client', version='1.31.3') corresponds to this artifact.
sometimes you need to specify specific maven repository then add #GrabResolver(name='central', root='https://repo1.maven.org/maven2/')
if you want to specify local file dependencies then in the code above:
def gb = new GroovyBox(...).withClassPath([
'/path/to/lib1.jar',
'/path/to/lib2.jar'
])

How can I include a groovy script(with classes, functions) in another groovy file?

My environment: groovy -v
Groovy Version: 3.0.4 JVM: 1.8.0_242 Vendor: Azul Systems, Inc. OS: Linux
I write two files: AStar.groovy and digit.groovy.
file AStar.groovy like:
class State{
int deep = 0;
def pre;
double f = 0;
}
abstract class AStar {
State begin;
State end;
def astar() {
// some astar code
}
abstract def judge(def state);
}
and file digit.groovy like:
class DigitState extends State {
// some fields and methods
}
class Digit extends AStar {
// some fields and methods;
def judge(def state) {
// some codes.
}
}
def digit = new Digit();
def path = digit.astar();
path.each {
println(it);
}
now I want to run digit as: groovy digit.groovy
But it tell me "unable to resolve class State"
I see this url:
Including a groovy script in another groovy
But I can not run my script by that way, and I do not know what is my wrong.
How can I include the file without compile please?
In digit.groovy, use:
import AStar
if they are in the same package.
I tried your code in Eclipse and it ran even without the import statement.

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

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
}

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)
}
}

Resources