War task related question

Hi,

We need to build multiple distributions of our products and we’ve decided to have each project build multiple .war files. The .war files are quite similar; mostly, we need to apply some distribution-specific changes to some files before adding them to the .war file. So we’ve defined our own custom War task that looks like this:

public class OurWar extends War {
   String cssPath = "/some/default/css/path"
   String jsPath = "/some/default/js/path"
     OurWar() {
      super()
        copyAction.rootSpec.into('css') {
         from cssPath
      }
        copyAction.rootSpec.into('js') {
         from jsPath
      }
        // Other stuff
   }
}

and in our build.gradle we have something like this:

task platformXWar(type: OurWar) {
   // Use default paths
}
  task platformYWar(type: OurWar) {
   cssPath = tasks.transformCssForPlatformY.destDir
}

Unfortunately, this doesn’t work, because the copy actions for platformYWar are configured before we have a chance to set ‘cssPath’ to something else. We tried moving the copy actions out of the OurWar constructor into a doFirst() block. It resulted in those files not being included in the war files at all. So finally we had to change OurWar to look like this:

public class OurWar extends War {
   String cssPath = /some/default/css/path
   String jsPath = /some/default/js/path
     OurWar() {
      super()
        // Other stuff
   }
     void setupStandardCopyActions() {
      copyAction.rootSpec.into('css') {
         from cssPath
      }
        copyAction.rootSpec.into('js') {
         from jsPath
      }
   }
}

and we had to change our build.gradle to:

task platformXWar(type: OurWar) {
   // Use default paths
   setupStandardCopyActions()
}
  task platformYWar(type: OurWar) {
   cssPath = tasks.transformCssForPlatformY.destDir
   setupStandardCopyActions()
}

Now everything works. However, it seems very ugly/hacky to have to call setupStandardCopyAction() at the end of every war task. Basically, it seems like we could really use a post-config (or rather “end-of-config”) hook on the Task class. Does anything like this exist? What’s the best way to do this?

Thanks!

A straightforward solution is to use ‘from { cssPath }’ and ‘from { jsPath }’ in the constructor. This will defer the evaluation of ‘cssPath’ and ‘jsPath’ until execution time.

Thanks Peter!

I tried

from {
   cssPath
   into 'css'
}

and Gradle complained about a cyclic dependency (it tells me that my war task depends on itself) – not sure why. However, this worked:

into('css') {
   from {
      cssPath
   }
}

Thanks!

can you post the whole war task definition. This might help us to find a reason for the cyclic dependency.

regards, René

Our set up looks pretty much like this:

class OurWar extends War {
   OurWar() {
      super()
        // Copied from WarPlugin
      description = "Description for our War task"
      group = BasePlugin.BUILD_GROUP
      project.extensions.getByType(DefaultArtifactPublicationSet.class).addCandidate(new ArchivePublishArtifact(this))
        appName = "ourAppName"
      into('WEB-INF/classes') {
         from {
            "/path/to/our/standard/templates/commons-logging.properties"
         }
         expand(appName: appName)
      }
   }
     @Override
   FileCollection getClasspath() {
      def ourClasspath = project.files(project.tasks.jar.archivePath, project.configurations.runtime)
      return ourClasspath - project.configurations.providedRuntime - project.configurations.providedCompile
   }
}
  class OurExtendedWar extends OurWar {
   String propDir = 'WEB-INF/classes'
     String commonCssDir = '/default/path/to/our/common/css/dir'
     String productCssDir = '/default/path/to/our/product/specific/css/dir'
     String generatedCssDir = '/default/path/to/our/generated/css/dir'
     // Some similar paths to img files, js files and other resources
     OurExtendedWar() {
      super()
        archiveName = "${project.someProperty}.war"
        destinationDir = "${project.buildDir}/war"
        into(propDir) {
         from {
            "/path/to/our/standard/templates/autogen.properties.template"
         }
         expand(prop1: project.prop1, prop2: project.prop2)
         rename { it.replace('.template', '') }
      }
        into('css/common') {
         from {
            from commonCssDir
         }
      }
        into('css/product') {
         from {
            from productCssDir
         }
      }
        into('css/include') {
         from {
            from generatedCssDir
         }
      }
        // Similar blocks for other resource files.
        doFirst {
         destinationDir.mkdirs()
      }
        doFirst {
         // Make sure that all critical files are in the right location.
               }
   }
}

And then we have a war.gradle plugin that looks like this:

apply from: "/path/to/our/plugins/plugin1"
apply from: "/path/to/our/plugins/plugin2"
  buildscript {
   dependencies {
      classpath files("/path/to/jar/file/with/out/custom/war/tasks/custom-tasks.jar")
   }
}
  task war(type: OurExtendedWar) {
   dependsOn taskFromPlugin1
   dependsOn taskFromPlugin2
     from (webAppDirName) {
      into ''
   }
}

And then the project build.gradle file looks like this:

apply from: "/path/to/our/plugins/war.gradle"
  // Non-war related stuff
  tasks.withType(War.class).all { warTask ->
   warTask.configure {
      from('/path/specific/to/this/project') {
         into 'project/specific/path'
      }
   }
}

And for some reason, if I replace the “into { from { } }” blocks in the task OurExtendedWar constructor with

from {
   'path'
   into 'warPath'
}

I get the cyclic dependency.

I have a typo: the blocks that I posted as “into(path) { from { from path } }” are really “into(path) { from { path } }”

The latter declaration is invalid. It effectively passes ‘{ into ‘warPath’ }’ as the ‘from’ (whatever that means), with ‘path’ getting lost. Instead, it would have to be ‘from(‘path’) { into ‘warPath’ }’. Or, to get lazyness for the ‘from’ part, ‘from({ ‘path’ }) { into ‘warPath’ }’.