Binary plugin used in custom script - project.tasks.withType not working

Hi,

I have a problem that is probably related to my question here: http://forums.gradle.org/gradle/topics/inherit_inject_buildscript_dependencies_into_custom_script_within_subproject

My binary plugin ‘nsis’ (dependency name: ‘gradle-nsis-plugin’) contains a task class 'NsisTask’and a plugin class ‘NsisPlugin’. The plugin does not define any task instances, but only tries to configure the tasks of type ‘NsisTask’, specifically some convention mappings.

My build consists of multiple projects. The relevant subproject ‘client’ applies a custom script ‘gradle/distribution.gradle’, which defines an ‘NsisTask’. Problem is, on task execution the convention mapping properties are not set.

Here’s the code: ### NsisPlugin.groovy

class NsisPlugin implements Plugin<Project> {
 void apply(Project project) {
  // ...
  project.extensions.create("nsis", NsisExtension )
  project.tasks.withType(NsisTask) { task ->
   // can't find that output
   println "found nsis task $task"
   configureTaskDefaults(task, project)
  }
 }
    private def configureTaskDefaults(NsisTask task, Project project) {
  // can't find that output
  println "configure task $task"
  task.conventionMapping.with {
   nsisHome = {project.nsis.nsisHome}
   verbosity = {project.nsis.verbosity}
   defines = {project.nsis.defines}
   nsisClasspath = {project.configurations.nsis}
  }
 }
}

NsisTask.groovy

class NsisTask extends DefaultTask
{
 @InputFiles
 FileCollection nsisClasspath
   @InputFile
 File script
    @OutputFile
 File outputFile
// ...
    @TaskAction
 void run() {
  ant.taskdef(name:"nsis",classname:"com.danielreese.nsisant.Task", classpath:getNsisClasspath().asPath)
  ant.nsis(script:getScript().absolutePath, verbosity: getVerbosity()) {
   define(name: "NSIS_HOME", value: getNsisHome().toString())
   define(name: "outputFile", value: getOutputFile().absolutePath)
   // ...
  }
 }
}

client/build.gradle

apply from: "gradle/distribution.gradle"

client/gradle/distribution.gradle

import com.inxmail.gradle.plugins.*
apply plugin: "nsis"
  nsis {
 nsisHome = "/usr/share/nsis"
 verbosity = 1
 define "version", project.majorVersion
 define "product_version", project.version
}
  task prepareNsis(type:Copy) {
 into {file("$buildDir/tmp/nsis/$installer.name")}
 from {zipTree(installerTask.call().archivePath)}
 into("inst") {
  from("../install/client/install/windows/inst")
  from("../install/client/install/windows/icons")
 }
}
  task windowsExe(type:NsisTask) {
 inputs.dir prepareNsis.destinationDir
 dependsOn prepareNsis
 outputFile = file({"$buildDir/distributions/${distBaseName}-${version}.exe"})
 script = file({"$prepareNsis.destinationDir/inst/install.nsi"})
}

Result:

FAILURE: Build failed with an exception.
  * What went wrong:
A problem was found with the configuration of task ':client:windowsExe'.
> No value has been specified for property 'nsisClasspath'.

As noted in the code, I don’t find the outputs from the ‘tasks.withType(NsisTask)’ closure, so it looks like it never finds such a task.

The confusing thing is: if I move the declaration of the task

task windowsExe(type:NsisTask) {...

into ‘client/build.gradle’, after the line ‘apply from: “gradle/distribution.gradle”’, it does work correctly.

Any idea?

A slight supection: could this be a class loader problem? Does the custom script plugin ‘client/gradle/distribution.gradle’ maybe have a different class loader, and the class NsisTask in the type declaration there is a different instance of the class object than the one in the plugin’s ‘tasks.withType()’ call?

Yes, it could be a class loader problem. It could be that the only safe way is to have ‘buildscript’ blocks in all build scripts that require them (potentially combined in a single ‘buildscript’ block in the root project’s build script), not in any script plugins. That’s how I always do it, and it has served me well.

I’m not sure I get that. Would that mean I can’t use classes from binary plugins in script plugins?

I had forgotten that those classes aren’t visible to script plugins then. Our current class loading approach surely needs some improvements. I’ll open an issue for this. For the time being, you can try to use something like ‘tasks.matching { it.getClass().name == … }’ instead of ‘tasks.withType’.

I’ve filed GRADLE-2363 for this.