After much trial and error, I seem to have a working, custom war task, as shown below. However, I’m having an issue where I can’t configure a property of the class from in the build.gradle of a consumer of my custom war task.
I’m sure that I’ve put the code in the custom war task in the wrong spot, but couldn’t find a working example of extending the gradle war, so advice appreciated. I tried putting the code in doTask or doFirst, but my custom war file was not generated in either of those spots, it was only generated when I put the code into the constructor, as shown below.
The following is not working - directoryName always ends up as the default value assigned in the custom war task (ie. bentallProd), unless I put the RdsWarTask code into doTask, but then the war itself is not generated!
How can I get the code to work properly and still be able to configure the task from the consumer?
FAILURE: Build aborted because of an internal error.
* What went wrong:
Build aborted because of an unexpected internal error. Please file an issue at: http://forums.gradle.org.
* Try:
Run with --debug option to get additional debug info.
* Exception is:
org.gradle.api.UncheckedIOException: Could not normalize path for file 'C:\eclipseProjects\sustainability\env\task ':bentallProdWar''.
I tried various ways to use from{} in various places, but couldn’t resolve this. This is basically the problem in its shortest form:
I would like to extend the war task and have the consumer set properties on the custom task, AND the values have to be available in the constructor (I’m not sure why, but others have noted the same behavior) , but so far values have come through as null.
The below does NOT work.
class RdsWarTask extends War {
private String directoryName
RdsWarTask(){
super()
//alwasy prints null
println "directoryName=" + directoryName
...
RdsWarTask(){
super()
//exclude the local web.xml from the build
exclude '**/web.xml'
//copy properties files to classes
from ({'env/' + directoryName}){
exclude 'web.xml.properties'
include '*.properties'
into '/WEB-INF/classes'
}
(note the {} use in the from statement)
but still no go. If directory name is hardcoded, then the properties files come through in the war no problem.
I’m really dead in the water here - is it possible to extend a War class and reconfigure its properties, or should I just give up and create my own War task?
RdsWarTask(){
super()
from ('${->project.tasks.bentallProd.getDirectoryName()}'){
exclude 'web.xml.properties'
include '*.properties'
into '/WEB-INF/classes'
}
...
In other words, is there any reason why you’d need to drop into Groovy/Java land to solve your problem? Why not do something outlined like the code block below instead of creating your own War class extending from Gradle’s implementation?
task rdsWar(type: War) {
from '...'
into '...'
exclude '...'
include '...'
}
You can then use apply from: ‘…’ to apply this Gradle script in any other build.
Another is utilizing the knowledge of the separation of configuration phase vs. execution phase. I believe that the constructor is always called before the override variables are set, so you want to set your defaults in your class/constructor, and then in the @TaskAction method’s scope you want to just use the variables in your task’s work.
The basic version of your RdsWarTask works fine if you do a few modifications:
For future reference, from my perspective… there’s a lot going on in this thread (which is why I think people have passed up on answering this post). If you’ve got several questions or problems, try breaking them out into individual answerable tidbits instead of tacking on slightly different problem after problem.
‘You just need to write a build script or plugin that adds a few War tasks and configures them to your liking.’
So I tried to create a plugin that would do what I want, seemed like a good idea, but found it to be a lot more difficult than I imagined.
I can try your suggestion about the custom war task and the apply from: ‘…’, that sounds good, and would avoid the issues that I’m running into! I’m going to try it right away, will let you know shortly.
I would love to know the answer to the ‘directoryName’ question since it seems like an issue I could run into again.
The modified example you provided doesn’t work for the war case however - if you don’t make changes in the Groovy constructor that extends War, that a lot of things just don’t work - see that other thread I referenced:
and I really don’t know why the code must be in the constructor. Since other people have already noticed the same I’m hoping to make a nice clean example and put it on the cookbook…
In terms of the thread, you are right, there are several things going on, but all from the same thought. I don’t really know what the protocol is - some places hate starting new threads for things, but I’ll just break it out next time!
In terms of the War task extension, you are right… but it seems like that problem has been solved in another way (via the forum post you referenced). You were having other problems that put you in another problem bucket… which I essentially addressed here.
Well, I have 3 different environments that all need a common set of customizations applied. I could end up with 3 war tasks that all repeat the same customizations, but that doesn’t follow the DRY principle and has its associated pitfalls…
Well, I have over 10 projects, with several targets per project, and my initial build has about 40 lines of script code. If I have a custom task that does all that, if I have to add in a file check, a standard cleanup operation, etc., I won’t have to edit 10 different files in several spots per file, I’ll just edit my custom task, build it to our repo, and then all my builds will have the extra functionality. It sounds great to me, and something that Gradle will handle far better than Ant ever could…It would be much more error prone and inefficient having to manually do that, so I’m unsure as why you would say its typically no less DRY?
Because in Gradle, it’s as easy to configure ten tasks as it is to configure a single task. Typically, configuration isn’t baked into a task but is the responsibility of plugins.
From your explanation, I have a feeling that you want to model each variation in configuration as a separate task class. Typically, you would instead have multiple instances of the same task class which are configured individually by a plugin. In the simplest case, a plugin is a build script applied with ‘apply from:’.
Oh no, that’s not what I’m looking for. I’ve been using Ant across all our projects and each project tend do the same things. I’ve made lots of ant macro’s to do standard stuff and want to have Gradle work at the next level. Standard stuff includes: -checking for required files to build war -copying over required properties files for each environment -creating the web.xml appropriate for each environment etc.
Currently we have a folder called ‘env’ with multiple folders underneath them that hold various artifacts for each environment. ie:
that contain various files, some of which I can just incorporate into the gradle way (with gradle properties) and some of which I can’t - logo’s, login pages etc.
I’m trying to create a war task that will handle all the stuff that has been standardized across ant scripts and put it into one task class that can be shared across projects. There may be some exceptions, but by and large this should be possible in my environment. Is there some resistance to users of gradle customizing built in tasks?
Inheriting from a built-in task class is typically not the right means to customize. Copying over artifacts should be done by a ‘Copy’ task. (Or, if the files should be copied into a War, a ‘War’ task should be configured to include these files.) Creating the ‘web.xml’ should be done by a custom task, potentially implemented as a task class. Checking for required files might be its own task too. All this would be held together by a custom plugin that would be shared across teams. So the general approach is to favor composition over inheritance, and to compose at the plugin rather than the task level.
Thank you for your response. I’m certainly willing to consider the approach you have suggested, although I have some reservations. However, the easiest way to discuss the approaches would probably be against an example. I looked through the java samples but nothing there looked appropriate for what I’m doing:
multiple environment builds with the web.xml configured per environment 2. compilation of java classes into HTML/javascript for deployment (we are using GWT via Vaadin) 3. various other steps that probably aren’t interesting
I’ll provide an example and perhaps you would consider critiquing it so we don’t end up having difficulties in our discussion around semantics.
For anyone reading this, I found out that the code I was interested in running required being in the constructor, and the following examples show 2 ways of deferring the code eval until the execution phase, which helped me greatly in dealing with my issues:
class RdsWarTask extends War {
String directoryName
RdsWarTask(){
super()
//exclude the local web.xml from the build
exclude '**/web.xml'
//note closure to defer until execution phase
from ({directoryName}){
exclude 'web.xml.properties'
include '*.properties'
into '/WEB-INF/classes'
}
project.gradle.projectsEvaluated {
//copy and filter default web.xml file
def Properties webProps = new Properties()
webProps.load(new FileInputStream(directoryName + '/web.xml.properties'))
from ('env'){
include 'defaultWeb.xml'
into '/WEB-INF'
rename { 'web.xml'}
filter(ReplaceTokens, tokens: webProps )
}
}
}
...