Introduction to writing Gradle plugins

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi it's Tom Gregory here and welcome to this video where you'll discover how to encapsulate your own specific Gradle build logic into a plug-in in a simple and reusable way and most Gradle builds use at least a few plugins to add extra functionality so why not make use of this powerful mechanism to share build logic between projects and in this video we'll be building up a step-by-step example of exactly how to create a Gradle plug-in and then you'll be able to apply these steps to your own requirements to create your own plugins but before we go ahead and start writing plugins we need to understand exactly what is a Gradle plugin so let's get right into it the Gradle plug-in is essentially an add-on for your build that adds some additional functionality and it can be applied in your build or Gradle like this so you have what's called the plugins DSL in Gradle and you can apply plugins which have a particular ID or version and it adds some kind of functionality or build and normally it's one of two things and first up plugins can expose tasks so tasks allow users to execute things exposed by the plug-in for example with the maven publish plug-in it exposes a task published to maven local and these tasks may also be configured in the Gradle task graph automatically to run by default and an example of this is the spring boo plug-in which attaches the booty artist to the assemble tasks like this and when you run the assemble it's automatically going to run boot jar and that's going to create you an executable jar file and then plugins can also perform at some action when they're actually applied so as soon as the plug-in is applied it can do anything really and this may be to perform some action or configure the build in some way and the best way to understand this is by an example so if you apply the Java plug-in it automatically adds the main and test source sets so it's configuring your Gradle build in a specific way without you having to run an actual task so how do we configure a plug-in well some plugins allow configuration that can be applied in the build Gradle and this can be something pretty simple by just setting a few parameters or it can get quite complex where actually the plug-in configuration resembles a domain-specific language and for example the Java plug-in allows you to configure tests to be included or excluded by tag like this so this is a configuration that you could put in your build.gradle if you had the java plug-in applied so in terms of today's example plug-in that I mentioned before it's going to be a plug-in that allows you to do a simple diff between two files and I mean really simple it's just gonna compare the file size and it's gonna expose a task which will allow you to do the diff and this will be called file diff and you'll be able to configure this plug-in in the build Gradle like this so you'll define this file diff configuration block and then you'll just specify two files file one and file two when you execute the task it will print the result to standard output as well as write a file and this plug-in will be clever because it will only rerun the task when the input file has changed so far 1 and file to have not changed at all when you rerun the task it won't actually do anything and lastly as we are good developers we will have a test so how do we go about designing a plug-in in terms of what classes are required well the main class with a plug-in is the plug-in class top-level which defines what exactly happens when the plugin gets applied and normally this makes use of a few other types of classes and the first one on the left here is the extension class which is a way of configuring your plug-in and actually the configuration that you saw earlier in and build a cradle that normally maps to an extension class so in our example today the extension class will have a configuration for defining the two input files and then on the right here you've got a task class and the plug-in may define multiple types of tasks class but essentially the plug-in will create an instance of a task class and associate that with a name so in our example today we'll have a task class which will be there to do the diff between these two files the plug-in class will an instance of the task class and associated with the filed if name so that the user can run dot slash grade or W filed if so how do Gradle plugins get published well this happens in much the same way as a normal Java project so normally in a Java project you would create a jar file that would be published to a public or private maven repository and this happens in the same way so today we'll have a separate project for our plugin and this will be a git repository and within that project we will have plug-in classes and a builder cradle and we'll configure that build.gradle to publish to a maven repository and in our case today that's going to be our local maven repository and once that jar file for the plugin gets published then we can head over to another project and apply that plug-in and then it's going to get pulled in and all of the functionality that that plug-in exposes will be available in that other project so now we're ready to go ahead and start writing some code if you want to save your fingers because I'm such a nice guy I provided all this code for you over at github comm /tk Gregory slash file diff plug-in and the link is in the description ok we're gonna start off by just creating a new directory in which will create a new project so I'll create a directory here file diff plug-in let's call it and then I'll CD to that directory and now I'm just gonna do a git init in that directory so it's initialized a git repo in there and then let's do a Gradle init to initialize a Gradle project and we'll just have a basic project for now and it's gonna be groovy and we're gonna cool it file diff plug-in first things first I'm going to open this up in IntelliJ IDEA I mean as much as I like Vai I think ID is going to be the better tool in this case so I'm just going to navigate to the file diff plug-in and select the build up Gradle file and now the project is open in IntelliJ and it's built successfully first things first we're going to go ahead and edit Lee build.gradle file and add all the components that we need in order to be able to go ahead and write a Gradle plugin so I'm gonna delete this comment and then we're gonna need two plugins here and the first one is groovy because Ruby because we're gonna be writing this plug-in in groovy and groovy is my preferred language to use when writing plugins you can use Java but because Gradle supports groovy so well I find it the best option and then we're going to add the Java Gradle plug-in and this adds a whole load of functionality that we need and it does it automatically which is going to set up various things like class paths for example and the write dependencies so that we can very easily do plug-in development and we'll need a group because when we publish this plug-in it's going to be published under this group and I'm gonna put it under comm Tom Gregory feel free to choose your own group obviously and then the version here he is also going to be used when we publish this and it's going to be zero zero one snapshot and then we're going to add some repositories and in this case we actually need to use the j center repository because we're going to add a dependency in the dependencies block here and we're going to actually use insert because it's easier than typing this out so I'll insert and then add maven artifact dependency and I'm going to search for something called spark core and I'm going to choose the version here 1.3 - groovy 2.5 and just hit add and that's added that dependency and I'm going to set it to a test implementation because we only want to use this for our tests this is basically a groovy testing framework that allows us to do given when then start tests really easily and we want to exclude here the module groovy all we don't want to bring in the version of groovy from this dependency we want to use the version that we already have and actually if we want to use exclude here we do need to put this in brackets like this and now if we go to Gradle and refresh we'll see that this is not building currently I'm going to go ahead now and create a new directory where we're going to be putting our code and that is going to be source main groovy comm Tom Gregory plugins file diff so there's the directory and you can see that IntelliJ automatically recognizes it because we have the groovy plug-in and then we can go ahead and first class we're going to create is the extension class so a crate groovy class file diff extension just file diff extension and if you remember from earlier this class is going to essentially contain the configuration that we need so that users can use our plug-in and configure the two input files that are going to be dipped and this class represents those two files so we're going to have two class variables here and we'll define them as final and we're going to have a property of type file file1 and let's just import that here that's going to be the Gradle property and likewise I've just copied that line we're going to have it file two and then we'll have a file diff extension constructor and this needs to create this needs to take in an object factory and then in this constructor we actually need to initialize these two properties as objects dot property file for fire one and then the same again for file 2 and then we also need to annotate this constructor with inject so what's happening here rather than just defining these class variables as type file we're wrapping them with the property type and what this does in Gradle is it allows you to essentially use lazy configuration so these properties do not need to be defined when this code is executed they only need to be defined when the file is accessed and this is really helpful in Gradle because oftentimes you will apply a plug-in before you actually configure it quick interruption here and I've just been editing up this video and later on you'll see that I make a few silly mistakes and then I go and correct them so just for a bit of fun see if you can spot these four mistakes along the way whilst and writing the code the next class we're going to create is a task and this is a groovy class called file diff task and when you create a task in groovy you do need it to extend default task which we'll do here and then just like our extension we also need to define property of type file for file1 file2 and then we'll also need a result file which we're not going to get from a property we're just going to hard code that in this plugin and that will be project build directory slash differs all txt and one other thing we're going to need to annotate these two input files with is the input file annotation and if you remember earlier when I listed out the requirements of this plug-in one of them was that if the input file doesn't change and you rerun the task then it's not going to actually run anything because it's going to detect that there's no change to the inputs and that's what this input file annotation does it makes use of cradles incremental build functionality so when you write a task you need to annotate a method with this past action annotation and that essentially tells Gradle which method to execute when this task is executed so we're just going to have a task here called diff and it's gonna have a differs all we're going to have some very simple diff functionality we're essentially going to diff the size of these two files if they're the same size we're going to print out that they're the same sighs if they're not we're going to detect which one is the largest file and print that out it's not a very elaborate differ guru them but it's going to illustrate how to use plugins quite nicely so I'm just going to have an if block here and I'm going to say if the far one property and we can say dot get to actually get the file and the size if that is equal to file to size then the differs OB files have the same size at whatever the file size is and the else condition here is going to be finding out what the largest file is by saying far one get sighs if that's larger than far too size then the largest file is far one otherwise it's obviously far too and then the result of the DIF here will be something like the largest file name was the largest file at whatever size it was and this diff result string were essentially going to be writing there to our result result file and then we're going to be printing it to standard output and we'll also print the fact that we've written the file - and then we'll include the file location and the last thing we need to do here is create another groovy class for the plug-in which is really going to bring all this together and we're just going to call this file diff plugin and plugins in Gradle need to implement the plug-in interface here and that has a type of project and then we need to implement an apply method so essentially when in the plug-in DSL in the builder cradle you do you specify a plug-in to apply this is what gets called immediately so first up we're going to make use of the file diff extension to enable the user of this plug-in to define the configuration and the way you do this in groovy is to say project dot extensions dot crate and then you pass a string and this string essentially equates to however the user is going to define the configuration in the build Gradle so we want them to define it with file diff and then within that block they'll also supply the file1 and file2 and then we also need to pass the type of the extension so that's the extension done and now we need to actually create a task and assign that task to a particular string which the user will be able to execute on the command line so I'm going to do project tasks great I'm gonna pass a string which I want the user to be able to execute which will be filed if and then I also need to pass the the class which we just created and then I can have a closure here and essentially essentially I'm going to map from the extension to the plug-in class so I'm going to map the the two files that are being configured by the user to the two file properties on the task itself so far 1 equals project dot file diff dot file 1 and likewise the same thing with file 2 last thing we need to apply here is to create a plug-in properties file and this is really a way that your plug-in gets discovered by name and it needs to live in source main resources so I'm just going to create another directory in here and then in that directory we create a file and the name of the file is essentially how we want to be able to refer to our plugin so I can say calm Tom Gregory dot file diff and then it's the dot properties file and in that file we need to have a property which is implementation class equals the main implementation class so that is the plugin class there so in terms of our plugin that's all we really need for the code but of course we haven't actually tested it works and it's more likely than not that I do have some typo somewhere so we're going to go ahead and write a integration test for this so I'm gonna create a new directory here and that's going to be source test groovy com Tom Gregory plugins file dear and then we'll just add a groovy class here for a file diff plug-in integration test and Gradle provides a very easy way for you to write these kind of tests it provides a framework that allows you to essentially write a build up Gradle file in your test and then create another build from that file and execute tasks against it and then verify what comes out in their response first off we're going to declare a rule and it is going to use the J unit temporary folder rule and that's just going to create a throwaway directory in which we can create build artifacts and we'll need a build file and then we'll create a set up method and the SPOC framework that we're using will automatically pick up the setup method and it's equivalent of the J unit before method and in there we're going to initialize the build file with test project our dart new file and we'll create a file build.gradle and then we can use this groovy in tax to append to a file and we can use triple quotes to have a multi-line string here and we'll just define a plugins block and then we're going to refer to this plug-in using the name that we had in the properties file up here come Tom Gregory default if properties or rather just calm don gregorio file death and then I'm going to define a test here in the Spock framework you can just pass a string so we'll have something like can successfully add if two files and we can have this kind of given when then syntax which is kind of nice and we will define the input files so we'll have test file one and then we'll use the rule again to create a new file test1 dot txt and likewise we'll have a test file to here and then we'll once again append to our build file in our test and we're going to use the configuration so remember in our plug-in when we set up the extension we defined a string file diff this is where in the builder cradle we can go ahead and defined far one and we need to pass a file object which we can do by calling this file method in groovy and passing the location which is test file one and dot get name we're going to use a relative location here and then likewise for file two we'll do test file to get name so that's our build Gradle and then what actually happens when we run this is we get a result from going to use this Gradle Runner class ready to run a dot crate we're gonna pass some properties here the project directory which is just test project diet route some argument and the argument here is going to be filed if so just like on the command line you would run Gradle W filed if that's what the argument is and then we also say with plug-in class path which is going to use the correct class path defined via our plug-in and then dot build and that's going to create a result and then in the then section we just want to say result output dot contains and then it should contain the string we had which was files have the same size because both of these files are actually empty so they're both going to have the same size of zero presumably and then the task that we ran which we can reference with this colon and then the name name of the task and we can say the outcome of that must be success and that's our assertion so let's try running this now on the command line and we'll see how many typos I have and we can do this running Gradle W test Wow it said run successfully so let's take a look in build reports tests test and index to HTML and we can open this up in the browser are interesting so even though it said run successfully we don't have any tests run and the reason for this is that I forgot to extend specification here and specification is a Spock class that you need to extend in order for actually to pick up your tests so let's try that again now it's running tests so we do have a failure here could not determine constructor argument missing parameter of type object factory or no service of type object factory so that was in our extension the problem here is that I've imported the wrong object factory type it needs to be the Gradle one that's better and I've also incorrectly named it so let's call this objects and then we're going to say object stock property so let's try that again hopefully third time lucky ok we have one more failure far not found diff result or txt the system cannot find the file specified so the problem here is that I forgot to annotate the result file with output file and this output file annotation means that Gradle is going to understand that this is an output from our build so let's run the test again and now we have an error where it's saying missing property no such property success so we need to say task outcome dot success this is definitely 100% going to work and our test result is successful in order to get our plug-in deployed to maven locals so that we can then use it in another project we need to make a few more tweaks to the build of Gradle here and the first one is to apply the maven publish plugin which make sure that everything's in the right format that maven understands and also provides the publish to maven local task which we can run to push to our local maven repository and then this some additional configuration we need in order that we can then reference this plug-in in the Gradle plugin DSL and that's going to involve this Gradle plug-in configuration here where we define what plugins we want to deploy and we're going to have an element here for the far diff plug-in and we're going to provide an ID and this is the ID that will used to reference our plug-in in the plug-in DSL so I'm going to say I want to reference it as Tom as comm Tom Gregory dot file diff and then we also need to provide the implementation class and that's going to be the plug-in I'm going to copy it cuz I don't trust myself to type this correctly and just to prove that I have a completely empty directory right now in my m2 here we go and now we can run Gradle W publish to maven local and that runs successfully and now we've got some contents here so we've got a directory file diff plug-in which contains the zero zero one snapshot version of the jar file and interestingly we also have another directory and this is essentially the reference that we can use from our plugin DSL and if we go into the pom file here this pom file actually contains a dependency on the other artifact on the jar file so don't worry too much about why we've got two files here but essentially this first one is a reference to the second one now that we've published this Gradle plugin to the local maven repository let's go ahead and try out using it in another project like you would in real life and to illustrate this I'm going to use another project that was used on another video where we did a maven to Gradle migration and in that project we had a requirement to compare two jar files so I'm going to go ahead and I'm going to clone that repo and then we're going to apply the file diff plug-in and make sure that it compares those two jar files so here we are in the maven degrade or migration project and we've actually got two jar files ones here in build lips and maven to Gradle jar and we've got another one here in in the target directory and we've got a requirement here just to compare the jar files built by maven and Gradle so first things first in order to be able to use a plug-in from maven local we need to make a change in the settings dot Gradle file and what we need to do is essentially configure this plug-in management block here and we need to set the list of repositories for plug-in management and we're going to add first up maven local let's go Lucan are local maven repo and also the greg of Gradle plugin portal which is the default if you don't specify this at all so it's going to be looking in those two locations which now means that we can go ahead and in our build Gradle we can add the new plug-in and as we saw before the reference is going to be calm Tom Gregory dot dot file - diff and we do need to provide the version number which will be zero zero one - snapshot and then let's go ahead and maybe down the bottom here we'll put the file diff configuration and you can see IntelliJ even understands and is offering autocomplete for this so far one in this case is going to be well let's go ahead and copy this file location and file2 here is going to be whatever's in the target directory and now we can go ahead and on the command line here we're just going to run Gradle W file diff and we've made a mistake here because if you remember we need to refer to these as files so we need to wrap this in the file directive so that it's of type file let's try that again okay build successful and we've got some output here so this is far written to diff result txt it's written the output file and it's also printed the Maven to Gradle zero zero one snapshot jar was the largest file and it's got the number of bytes here so that was the largest and if we go into build classes here or rather just build in differs alt we've got the same output so this is worked exactly as we would expect on another project as well so hopefully you can see now that writing plug-ins is a fairly straightforward process and it's a great way to encapsulate build logic and also to share that build logic between different projects and if you like this video then please hit the like button and do subscribe to hear about other related topics in the future and I am going to put the resources down below for the github repo used in this project and also some information and documentation so thanks a lot for watching and I'll see you next time on Tom Gregory Tech
Info
Channel: Tom Gregory
Views: 13,756
Rating: undefined out of 5
Keywords: Gradle, plugins, build automation, Groovy
Id: F3DF6bQo6jk
Channel Id: undefined
Length: 31min 37sec (1897 seconds)
Published: Mon Feb 24 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.