Programmatically adding dependencies


(Pranav K Varshney) #1

I have a requirement that all the teams / developers in the organization should use same version of a module for example 1.2.15 version of log4j, 3.0.0.RELEASE version of org.springframework.core etc. So, I want developer to mention something like the following in their build file

thirdPartyDependencies {
 compile id: 'log4j'
 compile id: 'springframework.core'
 testCompile id: 'junit'
        runtime id: 'xx'
}

I maintain another properties file that maps id to the corresponding group / name / version.

I will write a plugin that will read thirdPartyDependencies from build file and the properties file and would add the required dependencies programmatically.

  1. I am not able to read the dependencies from the build file

  2. I looked into DependencyHandler but did not get much clue.of adding the external dependency 3. How do I set transitive as false?

Regards, Pranav


(Leonard Brünings) #2

Have a look at the snippets in the comments of http://issues.gradle.org/browse/GRADLE-197 this should get you started on how to add dependencies by code (including transitive and exclude…).


(Luke Daley) #3

If your plugin is in Groovy, you’d use exactly the same DSL.

So something like…

class ThirdPartyDependenciesExtension {
    final Project project
    def compile(Map dependency) {
    add("compile", dependency)
  }
    def add(String scope, Map dependency) {
    def id = dependency.id
    def group = /* get group */
    def version = /* get version */
      project.dependencies {
      delegate."$scope"("$id:$group:$version")
    }
  }
   }
  project.extensions.add("thirdPartyDependencies", new ThirdPartyDependenciesExtension(project: project))

That’s untested, but should get you started.

In general, it’s a good strategy to stick as close to the DSL layer as possible as it’s more understanable to regular Gradle users making it easier to maintain. It’s also less likely to change in future Gradle versions.


(Pranav K Varshney) #4

Hi Luke,

I am getting the following error -

Caused by: groovy.lang.MissingPropertyException: Could not find property ‘extensions’ on root project ‘pgutils’.

This is my custom plugin -

package com.pg.gradle.plugins
  import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.MavenPlugin
  class PGPlugin implements Plugin<Project> {
 Project project
 public void apply(Project project) {
  project.getPlugins().apply(JavaPlugin.class)
  project.getPlugins().apply(MavenPlugin.class)
  project.getPlugins().apply(BundlorPlugin.class) // Custom bundlor plugin
  project.getPlugins().apply(RmicPlugin.class) // Custom rmic plugin
  // reading repository details from pg.properties
    Properties props = new Properties ()
  props.load(this.class.classLoader.getResourceAsStream("pg.properties"))
    def config = new ConfigSlurper().parse(props)
    this.project = project
  project.group = config.project.group
  project.repositories {
   mavenRepo name: config.project.repositories.name, urls: config.project.repositories.url
  }
    project.sourceSets {
   main { java { srcDir 'src/main'
    } }
   test { java { srcDir 'src/test'
    } }
  }
    project.uploadArchives {
   repositories.mavenDeployer {
    repository(url: config.project.uploadArchives.repo.url) {
     authentication(userName: config.project.repositories.login, password: config.project.repositories.password)
    }
   }
  }
    project.extensions.add("thirdPartyDependencies", new ThirdPartyDependenciesExtension(project: project))
 }
}

Regards, Pranav


(Pranav K Varshney) #5

And my build file is -

apply plugin: 'pg' // mapped to PGPlugin
  version = "36.0"
  thirdPartyDependencies {
 compile artifactId: 'log4j'
 compile artifactId: 'springframework.core'
 testCompile artifactId: 'junit'
}

(Luke Daley) #6

Looks like you are using an older version of Gradle that doesn’t have the extensions feature. It was introduced in 1.0-milestone-4.


(Pranav K Varshney) #7

Thanks. I was using the 1.0-milestone-3. I have upgraded to 1.0-milestone-4. I have issue now.

I wrote custom plugin and placed that jar (pgplugin.jar) under D:\gradle-1.0-milestone-4\lib\plugins. It started throwing the following error -

Caused by: org.gradle.api.plugins.PluginInstantiationException: Could not find implementation class'null' for plugin 'pg' specified in jar:file:/D:/gradle-1.0-milestone-4/lib/plugins/pgplugin.jar!/META-INF/gradle-plugins/pg.properties.
        at org.gradle.api.internal.plugins.DefaultPluginRegistry.getTypeForId(DefaultPluginRegistry.java:102)
        at org.gradle.api.internal.plugins.DefaultPluginRegistry.getTypeForId(DefaultPluginRegistry.java:76)
        at org.gradle.api.internal.plugins.DefaultProjectsPluginContainer.getTypeForId(DefaultProjectsPluginContainer.java:102)
        at org.gradle.api.internal.plugins.DefaultProjectsPluginContainer.apply(DefaultProjectsPluginContainer.java:37)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.applyPlugin(DefaultObjectConfigurationAction.java:101)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.access$200(DefaultObjectConfigurationAction.java:32)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction$3.run(DefaultObjectConfigurationAction.java:72)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.execute(DefaultObjectConfigurationAction.java:114)
        at org.gradle.api.internal.project.AbstractProject.apply(AbstractProject.java:845)
        at org.gradle.api.Project$apply.call(Unknown Source)
        at org.gradle.api.internal.project.ProjectScript.apply(ProjectScript.groovy:35)
        at org.gradle.api.Script$apply.callCurrent(Unknown Source)
        at build_7gslcdpg1pe0grk29isjrlf8sc.run(D:\B2D_PROJECTS-R36\commontech\pgutils\build.gradle:1)
        at org.gradle.groovy.scripts.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:49)
        ... 26 more
Caused by: java.lang.ClassNotFoundException: com.pg.gradle.plugins.PGPlugin not found.
        at org.gradle.util.MultiParentClassLoader.loadClass(MultiParentClassLoader.java:51)
        at org.gradle.api.internal.plugins.DefaultPluginRegistry.getTypeForId(DefaultPluginRegistry.java:100)
        ... 39 more

The same plugin jar used to work under 1.0-milestone-3. Is there any change in classpath etc in milestone-4?

I see another file gradle-plugins-classpath.properties under your gradle-plugins-1.0-milestone-4.jar. Do I need to add similar file and what is the purpose of this?

Regards, Pranav


(Luke Daley) #8

It’s due to this: http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-5+Migration+Guide#Gradle1.0-milestone-5MigrationGuide-Buildscriptclasspath

Adding jars to GRADLE_HOME is a bad idea because it’s not portable. It only ever worked previously by accident.


(Pranav K Varshney) #9

Thanks Luke for quick reply.

The issue which I see with this is that every team / developer has to write these lines of code in every build files.

buildscript {
    repositories { ... add some repositories ... }
    dependencies {
        classpath 'group:module:version'
    }
}

If build script path can load custom plugin jar from $gradleHome/lib or $gradleHome/lib/plugins then the developer has to just give the following line -

apply plugin: 'pg'

I can put my custom jar under $gradleHome/lib or $gradleHome/lib/plugins and could create custom Gradle installer for my organization.

Is there any clean solution where each and every team / developer do not have to add that few lines of glue code for loading custom plugin in build scripts?

Regards, Pranav


(Luke Daley) #10

This has now become another topic, please open a new thread so it’s easier for a future searcher to find the discussion.


(Pranav K Varshney) #11

Done.

http://forums.gradle.org/gradle/topics/loading_custom_plugin_from_gradlehome_lib_and_gradlehome_lib_plugins


(Peter Niederwieser) #12

You don’t necessarily need to invent your own thirdPartyDependencies block for this. Your plugin can just provide a map of values which is used from the regular dependencies block as follows:

dependencies {
  compile libraries.log4j
  compile libraries['springframework.core']

(Pranav K Varshney) #13

Hi Peter,

Sorry, I could not understand your suggestion completely. How do I read libraries […] from plugin and set proper version etc?

Regards, Pranav


(Peter Niederwieser) #14

The plugin doesn’t read the “libraries” property, it provides it (I’m using Groovy here):

project.libraries = [
  log4j: "log4j:log4j:1.2.6",
  spring_core: "org.springframework:spring-core:3.0.3.RELEASE"
 ]

For advanced needs you can use “dependencies.create()”, which gives you everything you can do in a build script’s dependencies {} block:

project.libraries = [
  ...
  commons_io: project.dependencies.create("commons-io:commons-io:1.3") {
    transitive = false
  }
  ...
}

It’s good enough to set “project.libraries” for the root project; subprojects will see it automatically.

I like this solution because it’s simple and stays close to vanilla Gradle. On the other hand, a “thirdPartyDependencies” block gives you the power to implement a custom DSL.

To externalize the dependency information, you’d use standard programming techniques.


(Pranav K Varshney) #15

Great. I came to know two different ways to make it work. :slight_smile:

When I am writing ThirdPartyDependenciesExtension as mentioned by Luke, how do I set transitive (true or false) for the dependency ?

This is the sample code

class ThirdPartyDependenciesExtension {
  Project project
  def compile(Map dependency) {
    add("compile", dependency)
  }
  def add(String scope, Map dependency) {
    def id = dependency.id
    def group = /* get group */
    def version = /* get version */
    project.dependencies {
      delegate."$scope"("$id:$group:$version")
    }
  }
}

(Luke Daley) #16

The exact same way you would if you were using the DSL normally.


(Pranav K Varshney) #17

Hi Peter,

Based on your suggestion I wrote a plugin that looks like

class ProjectLibraries implements Plugin<Project> {
 public void apply(Project project) {
  project.libraries = [
     log4j: project.dependencies.create("log4j:log4j:1.2.15") { transitive = false },
     spring_core: project.dependencies.create("org.springframework:org.springframework.core:3.0.0.RELEASE") { transitive = false },
     spring_beans: project.dependencies.create("org.springframework:org.springframework.beans:3.0.0.RELEASE") { transitive = false }
    ]
 }
}

Adding any new third party dependency is bit difficult and error-prone as it has groovy / gradle code. I want to simplify and create a configuration file that holds third party dependencies and a plugin that reads configuration and creates project.libraries.

Configuration file - TPD.groovy

log4j="log4j:log4j:1.2.15"
spring_core="org.springframework:org.springframework.core:3.0.0.RELEASE"
spring_beans="org.springframework:org.springframework.beans:3.0.0.RELEASE"

A plugin that read this configuration file and create project.libraries

class ThirdPartyLibraries implements Plugin<Project> {
 public void apply(Project project) {
  def config = new ConfigSlurper().parse(Tpd)
  println config
      project.libraries = [:]
  config.flatten().each { key, value ->
   project.libraries.put(key, value)
   println "$key : $value"
  }
 }
}

I am getting the following error (below lines repeats several time) -

Caused by: java.lang.StackOverflowError
        at org.gradle.util.FilteringClassLoader.allowed(FilteringClassLoader.java:142)
        at org.gradle.util.FilteringClassLoader.loadClass(FilteringClassLoader.java:68)
        at org.gradle.util.MultiParentClassLoader.loadClass(MultiParentClassLoader.java:46)
        at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:112)
        at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:61)
        at org.gradle.api.internal.project.AbstractProject.dependencies(AbstractProject.java:880)
        at org.gradle.api.Project$dependencies.call(Unknown Source)
        at com.pg.gradle.plugins.ThirdPartyDependenciesExtension.compile(ThirdPartyDependenciesExtension.groovy:12)
        at com.pg.gradle.plugins.ThirdPartyDependenciesExtension$_compile_closure1.doCall(ThirdPartyDependenciesExtension.groovy:13)

Am I doing something wrong?


(Pranav K Varshney) #18

Just realized that I don’t have to iterate and put it to other map. I can do something like this

class ThirdPartyLibraries implements Plugin<Project> {
 public void apply(Project project) {
  def config = new ConfigSlurper().parse(Tpd)
  project.libraries = config.flatten()
 }
}

Still could not get the reason for “java.lang.StackOverflowError” in above example.


(Peter Niederwieser) #19

The stack trace indicates that ThirdPartyDependenciesExtension is used, so its seems you are somehow mixing both approaches. The way you add dependencies is unnecessarily complicated. “dependencies.create()” is just needed for advanced configuration. In the regular case you can do:

project.libraries = [
   log4j: "log4j:log4j:1.2.15",
   ...
]

I don’t see much value in externalizing this information.


(Peter Niederwieser) #20

I’m closing this topic. Please open a new topic as you see fit.