Saturday, March 5, 2016

Gradle plugin development and TDD


Gradle plugin development and TDD

I Getting started with environment
A) Setting up an environment
  1. install gradle 2.10+
  2. install gradle plugin for eclipse (  Gradle IDE    3.7.2.201511260851-RELEASE    org.springsource.ide.eclipse.gradle.feature.feature.group    Pivotal Software, Inc.)
  3. install groovy 2.4 support for eclipse from here (update site: http://dist.springsource.org/snapshot/GRECLIPSE/e4.4/)

B) Configure plugin project (code snippet for project)


// Write the plugin's classpath to a file to share with the tests
task createClasspathManifest {
        def outputDir = file("$buildDir/$name" )

       inputs.files sourceSets.main .runtimeClasspath
       outputs.dir outputDir

       doLast {
              outputDir.mkdirs()
              file( "$outputDir/plugin-classpath.txt" ).text = sourceSets.main .runtimeClasspath.join("\n")
       }
}

dependencies {
       compile 'org.codehaus.groovy:groovy:2.4.4'
       compile gradleApi()
       testCompile gradleTestKit()
       testCompile( 'com.netflix.nebula:nebula-test:4.0.0')
       testRuntime files(tasks.createClasspathManifest)
}

To create plugin in project create class that implements Plugin class

package my.great.pkg

import org.gradle.api.Plugin
import org.gradle.api.Project

class MyGreatPlugin  implements Plugin<Project> {
       
        @Override
        void apply(Project prj) {

Then register this class as a plugin by adding property file i.e. 'my-super-plugin.properties' (see below) to resources/META-INF/gradle-plugins
implementation-class=my.great.pkg.MyGreatPlugin 

After that you may use your plugin by declaring
                     plugins {
                           id 'my-super-plugin'
                     }
C) Write BaseSpec for your plugin
BaseSpec prepares settings file build file and plugin classpath

public class BaseSpec extends Specification{
       
        @Rule final TemporaryFolder testProjectDir = new TemporaryFolder()
       File buildFile
       File settingsFile
       List<File> pluginClasspath
       
        def setup() {
               settingsFile = testProjectDir .newFile('settings.gradle')
              
               buildFile = testProjectDir .newFile('build.gradle')
              
               pluginClasspath = preparePluginClassPath()
       }

        private List<String> preparePluginClassPath() {
               def pluginClasspathResource = getClass().classLoader.findResource("plugin-classpath.txt" )
               if (pluginClasspathResource == null) {
                      throw new IllegalStateException("Did not find plugin classpath resource, run `testClasses` build task.")
              }

              pluginClasspathResource. readLines().collect { new File(it ) }
       }


}

D) Write a speck for plugin (Only new notation works fine in tests)
Write whatewer you like instead of foo-bar

class MySuperPluginSpec extends BaseSpec {
       
        def setup() {
               buildFile << """
                     plugins {
                           id 'java'
                           id 'my-super-plugin'
                     }

                     version = '0.1.0'
                     group = 'foo.bar'
              """ .stripIndent ()

               settingsFile << """rootProject.name = 'foo-bar'""" .stripIndent ()
       }
Build file should contains everything you need in real build file with this project
Also you may need some sample source files and resources. Here is the sample below
        private void createHelloWorld() {
               def src = new File(testProjectDir .folder , 'src/main/java/example')
              src.mkdirs()
              
               new File(src, 'HelloWorld.java').text = '''\
              package example;
              
              /**
              * HelloWorld class for test
              *
              * @copyright.placeholder@
              */
              public class HelloWorld {
              }
              ''' .stripIndent ()
              
               def resources = new File(testProjectDir. folder, 'src/main/resources' )
              resources.mkdirs()
              
               new File(resources, 'foobar.properties').text = '''\
              # some comments
              * @copyright.placeholder@
              useful text
              ''' .stripIndent ()
       }

Let's do the test itself.
Given section: We created sample files for project.
When section: We launched gradle build and got result in variable result. !If you expect build to fail, please 
Than section: We evaluate result and task outputs itself

        def 'sources jar is created'() {

              given:
                     createHelloWorld();

              when:
                      def result
                     result = GradleRunner. create()
                                                .withProjectDir( testProjectDir.root)
                                                .withArguments( 'sourcesJar')
                                                .withPluginClasspath( pluginClasspath)
                                                .forwardOutput()
                                                .build() //

              then:
                     result.task( ":sourcesJar").outcome == SUCCESS //import static org.gradle.testkit.runner.TaskOutcome.*
                     File resultJar= new File(testProjectDir .root, 'build/libs/test-0.1.0-sources.jar' )
                     resultJar.exists()

                     AntBuilder ant = new AntBuilder();
                     ant. unzip src:"$resultJar.canonicalPath",
                                          dest:"$ testProjectDir.root/unpacked",
                                          overwrite:"true" )

                     File helloWorldHtml= new File(testProjectDir .root, "unpacked/example/HelloWorld.java" );
                      assertThat(helloWorldHtml, containsString('Some copyright'));
       }

If you would like to check up what's going on in project folder (temp folder is erased after test run), you may do like this:

                     AntBuilder ant = new AntBuilder();
                     
                     ant.zip(destfile: 'C:/temp_2/test.zip',
                            basedir: "$testProjectDir.root" )

II Plugin interaction with your build script
A) Things to keep in mind
Plugin take information from your build script using extensions (it's BEANS!!!) and add custom tasks to your build script.
So in case your plugin doesn't need own configuration => you don't need 

Plugins add their own tasks. So your plugin will do the same. Even if it's internal task. It's visible. (They need to have group 'build')

Tasks may not (and will not) be executed in order you declared them in apply block. Use dependsOn and executeAfter allways.

Keep in mind that gradle "understands" that task shall or shall not be executed by evaluating task outcomes (???)

B) Prepare task for launch
In apply section you create new task object with corresponding name. Than you may want to configure task with some specific settings. There are two cases B.1 - You use your own configuration B.2 - You tries to reuse existing one

B.1) Your own shiny configuration
Here is how it looks like in build script
                           myconfig {
                                  user="ADMIN"
                                  password="secret"
                           }
Let's get it in the plugin.
     1] Define your own extension (package doesn't mater and keep in mind it's still groovy, not java)
class MyPluginExtension {
       String user;
       String password;
}
     2] Get this configuration in plugin code
              project.extensions.create( 'myconfig', MyPluginExtension)
              
               MyPluginExtension myConfigFromBuildScript = project.extensions.myconfig
              
               //create your task
               MyTask task = new MyTask();
              
              project.afterEvaluate {
                     task.user = myConfigFromBuildScript.user
                     task.password = myConfigFromBuildScript.password
              }
B.1) You tries to reuse existing one. 
Let's work with jar.manifest configuration. I would like to define there some fields in case they are not defined inside build script.
              project.jar {
                      manifest {
                            attributes  'Implementation-Vendor': 'Me, Serhii Belei',
                           'Build-date' : new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
                     }
              }
C) How to create new task
Usually you have to do it in apply section
              project.tasks.create( "yourTaskName", Exec) {//or Jar or Copy
                      dependsOn project.tasks.getByName('otherTaskName')
                      group 'build'//or documentation or whatever
                      description "Human readable desription of the task"
                         ...
              }
D) How to override existing task
Please pay attention to another syntax of task creation
              project.tasks.create( name: 'processResources', type : Copy, overwrite : true) {
                      into (project.sourceSets.main.output.resourcesDir)
                      from (project.sourceSets.main.resources) {
                            filter(...)
                     }
              }

E) How to rework snippet from build script into plugin

  1. add project. variable before file; dependencies; configurations; etc
            @Override
            public void apply(Project prj) {
                  prj.apply plugin: WarPlugin
                  prj.dependencies {
                          compile prj.fileTree(
  2. Use project.beforeEvaluate and project.afterEvaluate
                  prj.afterEvaluate {
                         prj.eclipse {
                                classpath {
  3. Local pathes like "my/path" replace with "${project.buildDir}/my/path"
    into "${project.buildDir}/tmp/someFolder"

E) Read source code in gradle folder and source code of other plugins
%GRADLE_HOME%/src/plugins (the best way is to open it with Intellij IDEA community edition)

Saturday, January 25, 2014

What Ukrainians are fighting for

You may notice avatars like this. Why?

Our Ukrainian parliament in illegal way accepted set of laws that kills democracy in Ukraine. These laws forbid us to make peaceful protests, use internet without censorship and so on. Russian parliament accepted the same laws beforehand.  Parliament did it in illegal way, so it's outside the law now and it has to be changed.

According to our laws our President guarantees that none of the laws change constitution. Our President signed laws that are wrong according to our constitution and Human Rights. He didn't do his job well and he should quit.

Our police used force against peaceful demonstration. Now police use tortures against people who was arrested. Responsible persons has to be found and judged.

Ukrainians love democracy and freedom. Pylyp Orlyk was a Ukrainian (http://en.wikipedia.org/wiki/Pylyp_Orlyk). So freedom and democracy what we are fighting for. We don't get it from Russia because Russia doesn't have one. We need our democracy.

Europe and US. Please understand that "All that is necessary for the triumph of evil is that good men do nothing".
The only thing necessary for the triumph of evil is for good men to do nothing.

Read more at http://www.brainyquote.com/quotes/quotes/e/edmundburk377528.html#rEWB2R8GcDrscfc7.99
The only thing necessary for the triumph of evil is for good men to do nothing.

Read more at http://www.brainyquote.com/quotes/quotes/e/edmundburk377528.html#rEWB2R8GcDrscfc7.99
The only thing necessary for the triumph of evil is for good men to do nothing.

Read more at http://www.brainyquote.com/quotes/quotes/e/edmundburk377528.html#rEWB2R8GcDrscfc7.99
The only thing necessary for the triumph of evil is for good men to do nothing.

Read more at http://www.brainyquote.com/quotes/quotes/e/edmundburk377528.html#rEWB2R8GcDrscfc7.99
The only thing necessary for the triumph of evil is for good men to do nothing.

Read more at http://www.brainyquote.com/quotes/quotes/e/edmundburk377528.html#rEWB2R8GcDrscfc7.99

Monday, October 7, 2013

GitDetails plugin for Total Commander

During two evenings (Saturday and Monday) I made plugin for Total Commander. This plugin is based on java plugin interface (by Ken Händel) located here http://www.totalcmd.net/plugring/tc_java.html.

All source code you may find at my github https://github.com/crc83/tc-git-plugin
Binary distribution you may create by running mvn assembly, or download from here: https://github.com/crc83/tc-git-plugin/blob/master/binary/GitDetails-1.0.zip

Please contact me if you would like to see more features in this plugin. 

Thursday, September 12, 2013

Intellij Idea task repository plugin creation

Key notes
1) Reuse existing infrastructure
@Tag("Rally") // This is name of part in xml configuration file where your settings will be stored
public class RallyRepository extends BaseRepositoryImpl {

public class RallyRepositoryType extends BaseRepositoryType<RallyRepository> {
// UI things are located here (repository name, icon and stuff)

public class RallyTask extends Task {
// It's all about tasks. But nothing special here. Just implement all methods carefully

META-INF/plugin.xml
// In this file you may find some plugin configuration. But there is also nothing special

Making all the stuff works
All configuration goes through repository deitor which is responsible for reading changing and storing configuration. So extend existing one:

public class RallyRepositoryEditor extends BaseRepositoryEditor<RallyRepository> {

What is necesary to understand before implementation is that editor is tightly coupled with repository itself. Main items:
1) Editor check if something changed via equals method of repository (In our case RallyRepository)
2) Editor uses copying constructor of repository to create new changed instance of it
3) Editor serializes and deserializes repository according to bean conventions by default, so don't forget about appropriate getters and setters
4) Also don't use getXXX and setXXX methods for fields you don't whant to serialize. (There is @Transient annotation and you can use it)
5) Don't invoke logger in constructior of repository

You may refer my own plugin at github for additional implementation details (https://github.com/crc83/rallydev). I changed a lot from one that I forked (https://github.com/RallySoftware/intellij-plugin).

Download ready to use jar you can from here (http://dl.bintray.com/crc83/generic/rallydev.zip).

Monday, April 1, 2013

BuildHive + Github, Precommit builds for free

In previous post I wrote about integration your github repository with continious integration server buildhive from cloudbees.
Now I'd like to write about one incredible feature I discovered recently.
Here is it. BuildHive CI server tracs all pul requests to your repository. So what it do when it discover new pull request? It build your master with changes from pullrequest. Then CI server makes comment to your pull request in case build was succesfull or not (see screenshot above).

And last but not least. You CAN make pull requests to your own repo in case you need this feature and you owner of the repo.



Thursday, January 3, 2013

Opensource development ecosystem


 My aim was to have compleatly free development ecosystem for one project. Since this is java project I whanted to have java ecosystem. As a source control system was choosen git and that's why project was hosted at github (you know where it is so I don't put link here).
the JenktocatI start looking for free CI service. First I found Travis-CI but to maintain build there I have to make additional file in my repository (but at that time I was a bit lazy to do so). Fortunally I found another service. It's BuildHive from Cloudbees (and here is the link https://buildhive.cloudbees.com/). This service is based on Jenkins-CI and it was first plus. The second plus was the way to configure build. Actually you don't need to configure build, just type shell comands to build project ant that's it. 


Monday, December 17, 2012

How to change your jenkins theme

Why? 

Because I have two environments: development and production. They look very similar and I don't whant to broke production environment during testing

How?

1) Install theme plugin
 
 2) Add your css and js (and img) files under %JEKINNS_INSTALLATION_PATH%/userContent folder

 3) Add links to these files in configuration
 

4)* You may left blank field with JS file if you don't have one

 

What I've got?

With css below 
 #top-panel > * {
    background-color : #B80000 !important;
 }
I've got this result

Wednesday, October 10, 2012

EnvMan4J

Short story 

Recently I evaluated couple of code generation Java frameworks and I faced with one interesting issue. Every framework or application server wants to have XXX_HOME variable and %XXX_HOME%/bin element in a path variable.
I dreamed about tool that do such thing for a long time. And now I decided to write it. I choose Lazarus to write this tool because it must be native application (in case I need to set JAVA_HOME ;) ). Now this program works fine with Windows (I have only one open issue). Hope I will also make Linux version.

Where to get

You may get this application from my github https://github.com/crc83/EnvMan4J

Case study

I made short screen cast because it's better to see once than to hear many times. You may find it here

Monday, August 20, 2012

Eclipse plugins that I like and dislike

EclEmma - code coverage
Checkstyle - check style
Subverse - svn client (remove) TortoiseSVN plugin instead
LogViewer - for Log viewing
JInto -messages (deletes comments in message files)
Jautodoc - javadocs generator
InstaSearch - quick search in all files
GoToFile - fast go to file http://www.muermann.org/gotofile/update
Eclipse2ant - creates ant build script easily (ugly ant script)
Subclipse - svn client  http://subclipse.tigris.org/update_1.8.x
QuickImage - for picture viewer (useful in pair with bug tracing system)
AnyEdit - easily replace tabs by spaces and vice versa http://andrei.gmxhome.de/eclipse/
ContextMenu - invokes Explorer context menu http://timealias.bplaced.net/ContextMenuPlugin/update/
EasyShell - Opens system shell in eclipse
GrepConsole - Allows you colorify console output

Wednesday, July 25, 2012

Vim in four days

Vim is the one of the most powerful and widely used text editor. But how can you learn it staying at home with children. The answer is simple and straightforward. You can learn vim by rough memorizing. 
For inspiration you may use any youtube screencast
As a step by step guide I use this book. I read one chapter in 10-20 minutes. Then I try to recollect shortcuts while playing with children. At the evening or on the next morning I try to use them. And here is result of past three days.

1-st day

  • Navigation keys "hjkl".
  • Insert {"i"} and normal {Esc} mode. 
  • page up and page down {CTRL-U CTRL-D}
  • undo {"u"} and block undo {"U"}
  • Home {"^"} and end {"$"}

2-nd day

  • Word by word navigation {"b" "w"}
  • Delete word {"dw"}or couple of words {"d5w"}
  • Append characters to the end {"A"}
  • Delete line {"dd"}
  • append line {"o" "O"}

3-rd day

  • Join lines {"J"} or join five lines at once {"5J"}
  • Find character "s" in line {"fs"}
  • Repeat last modifcation {"."}
  • Record macro for key "n" {"qn" commands... then "q"}
  • Invoke recorded macro {"@n"}

4-th day

I skipped some parts since I don't need it in near future (and they are boring :) ). These parts are:
  • Filtering 
  • Working with windows
Then I stucked with visual block mode. I even can't turn it on (with CTRL-V hotkey). I googled a little bit and found the solution. Use CTRL-Q instead of CTRL-V on Windows.
Visual block mode I'm going to learn with tecnique read and try because I can't imagine behavior of VIM.
BTW: Vrapper fo Eclipse still doesn't support visual block mode.