Statically create dependency

gradle-core

(Thibault Kruse) #1

Hi, in a multi-module project, defining java dependencies is pretty repetitive to write and read. To reduce that effort, we have a groovy file in buildSrc like this:

import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency

interface Versions {
	static jackson = '2.9.2' 
	static cassandra_driver = '3.3.2'
}

interface Libraries {
	static cassandra_driver = "com.datastax.cassandra:cassandra-driver-core:${Versions.cassandra_driver}"
	static jackson_core = "com.fasterxml.jackson.core:jackson-core:${Versions.jackson}"
	static jackson_databind = "com.fasterxml.jackson.core:jackson-databind:${Versions.jackson}"
	static jackson_annotations = "com.fasterxml.jackson.core:jackson-annotations:${Versions.jackson}"
}

interface LibraryGroups {
	static jackson_annotation = [
			Libraries.jackson_core,
			Libraries.jackson_databind,
			Libraries.jackson_annotations
	]
}

/**
 * Allows to create Gradle Dependency instances for complex cases
 */
class DependencyFactory {
	static Collection<Dependency> createCassandraDependencies(Project project) {
		return [
				project.dependencies.create(Libraries.cassandra_driver) { Dependency dep ->
					dep.exclude module: 'io.netty'
				},
				project.dependencies.create(
						"com.datastax.cassandra:cassandra-driver-extras:${Versions.cassandra_driver}"
				)
		]
	}
}

Use in build.gradle like this:

dependencies {
    compile Libraries.jackson_core
    compile LibraryGroups.jackson_annotation
    compile DependencyFactory.createCassandraDependencies(project)
}

As you can see, the Cassandra part is a bit noisy in createCassandraDependencies(), due to having to pass the project instance around. Is there a more elegant way of defining dependencies in buildSrc files without having to go though project.dependencies.create?

It seems Gradle public API is missing some interface DependencyContainer { Object getNotation(), Closure getConfiguration()} that I could use to create a single object that can be passed to the DependencyHandler.

(Other suggestions to improve above are welcome. I could use enums instead of interfaces, but would have to implement constructor() and toString() I guess.)


(Chris Doré) #2

Perhaps you could wrap everything in a small plugin instead. That way, the project reference will be injected when the plugin is applied. More work would be needed if you wanted to make everything in the extension immutable, but here’s a quick example of what I mean.

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency

class DepExtension
{
    Map<String, String> versions = new HashMap<String, String>()
    Map<String, String> libraries = new HashMap<String, String>()
    Map<String, List<Object>> libraryGroups = new HashMap<String, List<Object>>()
}

class DepPlugin implements Plugin<Project>
{
    @Override
    void apply( final Project project )
    {
        DepExtension ext = project.extensions.create( 'deps', DepExtension )

        ext.versions.jackson = '2.9.2'
        ext.versions.cassandra_driver = '3.3.2'

        ext.libraries.cassandra_driver = "com.datastax.cassandra:cassandra-driver-core:${ext.versions.cassandra_driver}"
        ext.libraries.jackson_core = "com.fasterxml.jackson.core:jackson-core:${ext.versions.jackson}"
        ext.libraries.jackson_databind = "com.fasterxml.jackson.core:jackson-databind:${ext.versions.jackson}"
        ext.libraries.jackson_annotations = "com.fasterxml.jackson.core:jackson-annotations:${ext.versions.jackson}"

        ext.libraryGroups.jackson_annotation = [
                ext.libraries.jackson_core,
                ext.libraries.jackson_databind,
                ext.libraries.jackson_annotations
        ]

        ext.libraryGroups.cassandra = [
                project.dependencies.create( ext.libraries.cassandra_driver ) { Dependency dep ->
                    dep.exclude module: 'io.netty'
                },
                project.dependencies.
                        create( "com.datastax.cassandra:cassandra-driver-extras:${ext.versions.cassandra_driver}" )
        ]
    }
}

Then in build.gradle:

apply plugin: DepPlugin
dependencies {
    compile deps.libraryGroups.cassandra
}

(Thibault Kruse) #3

That’s fair enough, though the plugin code itself is even more complex for team members to understand. I was looking for some dead-simple code, and it seemed there really should be a way to create objects containing both a Notation and a configuration.


(Chris Doré) #4

Do you think it would be better if these users didn’t have to interact with buildSrc at all in order to define the dependencies?
I think you might be headed in a similar direction that I was, and I started working on a plugin for dependency reuse but it dropped off the radar. Perhaps I’ll pick it up again if you think it might be useful.
The general idea was that you could use the plugin to declare named dependency definitions in either a global or local scope and then reference those names in dependency declarations. Global scope is available to all projects and the local scope is available to just the project that defines it. All of the dependency definitions would exist in the build files where it should be relatively easy for Gradle beginners to find and understand.


(Thibault Kruse) #5

I believe spring already has a plugin for advanced dependency management, and if I wanted a better plugin, I would try to use that one. I have been using separate includable gradle files in the past, but in projects with very heterogenous submodules this quickly becomes a spaghetti-code mess.

I like using the buildSrc folder for plain java/groovy code because IDEs can directly jump to the definitions (which they cannot well when using apply from: ...). I feel plain Java/Groovy files would not lead to surprising behavior when gradle-non-experts start making changes. However defining a plugin requires a lot more considerations than defining a java interface, so I think that should be avoided for teams like mine.