Init script's classpath dependencies are not accessible in the build script


(detelinyordanov) #1

I’m trying to add an init script dependency containing some classes that must be visible by Gradle and all Projects, however it happens that those classes are only visible during the execution of the init script (and within the Gradle instance) but any attempt to import and load them from a Project fails:

//init.gradle
initscript {
    repositories {
        mavenCentral()
    }
          dependencies {
        classpath 'commons-collections:commons-collections:3.2'
    }
}
  gradle.ext.myMap = new org.apache.commons.collections.map.MultiKeyMap()
//build.gradle
gradle.ext.myMap as org.apache.commons.collections.map.MultiKeyMap

Executing the above fails with ‘unable to resolve class org.apache.commons.collections.map.MultiKeyMap’ (during compilation of the build.gradle file). After struggling a bit (a lot) with this, I have found a workaround:

//init.gradle
gradle.projectsLoaded {
    MutableURLClassLoader projectCl = rootProject.buildscript.classLoader as MutableURLClassLoader
    projectCl.addURLs((initscript.getClassLoader() as URLClassLoader).getURLs() as List)
}

However this has the drawback that classes are actually loaded into two separate class loaders so if I start passing objects between them I get a ClassCastException:

//build.gradle
import org.apache.commons.collections.keyvalue.MultiKey
  //fails with java.lang.ClassCastException: Key must be a MultiKey
gradle.ext.myMap.put(new MultiKey('key'), 'value')

So I wonder whether this problem could be considered a real issue or there are some reasons for it to be that way?

I’m using Gradle 1.3

P.S. I’m actually exploring this area in an attempt to Configure different set of repositories based on project status.

Thanks,

Detelin


(Peter Niederwieser) #2

This works as designed. (Not saying I’m entirely happy with the design.) To make a library available to build scripts, you can do something like this (in the init script):

rootProject {
    buildscript {
        dependencies {
            classpath ...
        }
    }
}

For a complete example, see the ‘customDistribution’ sample in the full Gradle distribution. This won’t work for applied scripts though. I’m not aware of a way to make it work for applied scripts, other than declaring the dependencies in the applied script itself.


(detelinyordanov) #3

Thanks, this seems a bit better than the class loader hacks I have been doing but one problem is that I would also need to configure the repositories that the init script uses for the build script as well. I’m not interested in using these dependencies in applied build script (I saw the other topic about this), what I’m trying to do is to initialize some domain objects early before the projects are evaluated, then to set them as extra properties or an extension so that our build plugins can reuse them rather than having to re-create them. I think what I’m trying to do exactly what the basic usage of an init script mentions: > - Set up enterprise-wide configuration, such as where to find custom plugins. > - Set up properties based on the current environment, such as a developer’s machine vs. a continuous integration server.

But with this limitation, I’m not able to do this kind of setup using custom classes, but must use only standard Gradle/Java types :frowning:


(Peter Niederwieser) #4

I don’t understand. What’s the problem with the solution I posted? Did you have a look at the ‘customDistribution’ sample?


(detelinyordanov) #5

The problem is that I want to execute code before the projects are evaluated, so this code cannot be on the buildscript classpath, but has to be on the init script classpath. On the other hand, this code needs to create and store objects somewhere (Gradle/Project instance) where they can be later accessed by build plugins applied to the projects.

If this cannot be achieved by configuring the init script classpath, can I add this piece of code somewhere else so it becomes part of Gradle and:

  • code and objects created by it are visible by init script
  • code and objects created by it are visible by build scripts

(Peter Niederwieser) #6

Unfortunately, I’m not aware of a way to do so.


(detelinyordanov) #7

Ok, while there might be cases where inheriting init script dependencies in the build script classpath might be undesirable (I cannot think of such right now), I think that it is more common to assume that if something is declared as a dependency in the init script, it would be available for the build scripts as well. Also, would not this also solve GRADLE-2407?


(Peter Niederwieser) #8

In larger builds, a global script class path can cause version conflict problems. Nevertheless I consider it necessary to have a way to globally share dependencies between all scripts in a build. I’ll create an issue, let’s see where it goes.