Multiple 'apply from's don't seem to import all methods


(Henry Clout) #1

For example:

a.gradle:

def aMethod () {
 print "Running method A"
}

b.gradle:

def bMethod () {
 print "Running method B"
}

build.gradle:

apply from: 'a.gradle'
apply from: 'b.gradle'
  defaultTasks 'testApplies'
  task testApplies() {
 doLast {
  aMethod ()
  bMethod ()
 }
}

Running this gives: > Could not find method aMethod() for arguments [] on task ‘:testApplies’.

If you swap around the applies, i.e.

apply from: 'b.gradle'
apply from: 'a.gradle'

The error becomes: > Could not find method bMethod() for arguments [] on task ‘:testApplies’.

Is this expected behaviour (i.e. am I doing something wrong) or is this a bug?

Output of gradle -v is:

------------------------------------------------------------
Gradle 1.12
------------------------------------------------------------
  Build time:
 2014-04-29 09:24:31 UTC
Build number: none
Revision:
   a831fa866d46cbee94e61a09af15f9dd95987421
  Groovy:
     1.8.6
Ant:
        Apache Ant(TM) version 1.9.3 compiled on December 23 2013
Ivy:
        2.2.0
JVM:
        1.7.0_51 (Oracle Corporation 24.51-b03)
OS:
         Mac OS X 10.9.2 x86_64

Thanks,

Henry


(Peter Niederwieser) #2

‘apply from:’ doesn’t import methods or classes. Depending on what exactly your bigger goal is, there are likely other ways to achieve it.


(Henry Clout) #3

Thanks for the response Peter.

We’ve defined some utility methods in the referenced gradle files to allow ssh-ing and loading of properties files, e.g.

def ssh(command, user, host) {
 ant.ssh(
  user: user,
  host: host,
  keyfile: "${System.getProperty("user.home")}/.ssh/id_dsa",
  command: command
 )
}
def loadProperties(String sourceFileName) {
    def config = new Properties()
    def propFile = new File(sourceFileName)
    if (propFile.canRead()) {
        config.load(new FileInputStream(propFile))
        for (Map.Entry property in config) {
            ext[property.key] = property.value;
        }
    }
}

We’d like to call the ssh command in particular multiple times for a ‘deployment’ task, and in the limit I’d imagined we’d re-use these utilities across multiple projects.

I’m new to gradle (and groovy for that matter) so I’m probably not seeing the wood for the trees. Is there an obvious other way to do this? I’d thought defining methods for this functionality is the ‘right’ thing to do.

Thanks,

Henry


(Gary Hale) #4

A quick and dirty way to approach this is to turn the functions into closures and add them to the project extended properties:

ext.ssh = { command, user, host ->
...
}

Then they’ll be available via the project object in your main build script and you can just use them as if they were defined as methods.

A cleaner (and testable) approach would be to define the methods in classes in buildSrc and then import those classes. And if you wanted to make them more re-usable outside of your project, inject them via a plugin.


(Peter Niederwieser) #5

The ‘ssh’ method will need to be called from a task in any case, so it would be good to share it as a task class to begin with. The easiest way to do so is to put the class into ‘buildSrc/src/main/groovy’. Alternatively, there is a great third-party SSH plugin: http://gradle-ssh-plugin.github.io/


(Henry Clout) #6

Thanks for the advice guys. The SSH plugin is very nice, that solved that problem. Regarding the property loading, I created the following groovy file under the buildSrc directory:

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
  /**
 * Task for loading a properties file into ext properties.
 */
class LoadProperties extends DefaultTask {
    String sourceFileName = null
      @TaskAction
    def loadProperties() {
  if (sourceFileName != null) {
   println "Loading properties from: $sourceFileName"
         def config = new Properties()
      def propFile = new File(sourceFileName)
      if (propFile.canRead()) {
          config.load(new FileInputStream(propFile))
          for (Map.Entry property in config) {
           println " - Setting $property.key:$property.value"
              project.ext[property.key] = property.value;
          }
      }
  } else {
   throw new IllegalArgumentException("sourceFileName not specified on LoadProperties task")
  }
    }
}

In my build.gradle I then create tasks of the form:

task loadSystemProperties(type: LoadProperties) {
 sourceFileName = "$rootProject.projectDir/conf/system.properties"
}

This works nicely except for the case where I want to access these properties in the configuration phase of another task, e.g.

task prepareAppServerDist(type: Copy, dependsOn: loadSystemProperties) {
 from "deploy/appserver"
 into "build/appserver"
 exclude "server.xml"
 from ("deploy/appserver") {
  into "build/appserver"
  include "server.xml"
  filter (ReplaceTokens, tokens: ['${PUBLIC_IP}', project.ext.publicIp])
 }
}

In this case, as the loadSystemProperties task has yet to be executed the project.ext.publicIp property isn’t yet available.

Any thoughts on a neat solution around this? Or is loading properties as a task not workable in this situation?

Many thanks,

Henry


(Peter Niederwieser) #7

If the properties are used to configure the build, they should be loaded right in the build script (which will load them in the configuration phase), not by a task (which would load them in the execution phase).


(Henry Clout) #8

Ok thanks, that makes sense – I refactored my build to work in this way. I guess this is pretty basic stuff, but in case it helps any other beginners, this is what I got set up :

Deployment profiles live under : /conf//buildProperties.groovy

A typical buildProperties.groovy file is:

ext {
 localWarDeployPath = "/..."
 publicIp = "..."
 // etc
}

In my build.gradle, I load the properties using an apply:

apply from: "$rootProject.projectDir/conf/$project.ext.conf/buildProperties.groovy"

And I create application config in the WAR from templates and these properties thus:

war {
    webInf{
        from("src/main/template-resources") {
        expand(project.ext.getProperties())
            into('classes/')
        }
    }
}

Where a templated file contains entries like:

publicIp = ${publicIp}

The build is launched specifying a config profile to use :

gradle -Pconf=dev war

Thanks again for the help :slight_smile: