Custom dependency resolver


(dumi_daniliuc) #1

Hi,

I was wondering if there’s a way to specify a custom dependency resolver in the stable release ( http://issues.gradle.org/browse/GRADLE-1805 ). I understand that there might not be a standard plugin to do this, but I was wondering if there’s a way to do this without hacking Gradle’s source code.

We need this functionality and we would really like not to have to patch and rebuild Gradle every time a new version comes out.

Thanks!


(Luke Daley) #2

To answer your question: no, not at the moment.

Do you really need a dependency resolver though? Couldn’t you implement your custom logic behind a DSL and only use a dependency if you decide not to build locally?


(dumi_daniliuc) #3

Thanks for your reply, Luke.

I’m not sure what you mean by “implement my custom logic behind a DSL”. Could you elaborate a bit please?

Ideally, I’d like to be able to declare something like this in my build.gradle:

dependencies {
    compile <some external dependency>
    compile project: 'my_project', module: 'my_module', version: 'some_version'
}

and then have Gradle figure out if that project/module is checked out (in some predefined directories), and build it and add a DefaultProjectDependency if it is (along with a task dependency from :compileJava to :my_project-my_module:build), or add a DefaultExternalModuleDependency if it’s not.

I haven’t looked much at the Gradle source code since milestone-3, so I might be a bit off here, but it seems to me that to make this work, I’d need to define a custom MapNotationParser (which should be doable without hacking the Gradle source code), and a way to add it to the list of parsers in DefaultDependencyManagementServices.createDependencyFactory() (which I’m not sure how to do without patching Gradle).

Is there a different way to go about this? If not, is it possible for me to submit a patch for review? I don’t have it yet, but it would be a generic patch that allows Gradle users to define custom dependency resolvers. What would be the best way to submit such a patch for review?

Thanks!


(thoward) #4

We’re doing something similar to what it sounds like you are trying to do.

import org.gradle.api.Project
  public class ModuleManager
{
   static ModuleResolver createModuleResolver(Project project)
   {
      return new ModuleResolver(project)
   }
}
  public class ModuleResolver
{
   private Project project
     public ModuleResolver(Project project)
   {
      this.project = project
   }
     def getProperty(String name)
   {
      this.@project.project(name)
   }
     def invokeMethod(String name, args)
   {
      args.collect
       {
         if (this.@project.project(name).sourceSets.findByName(it) == null)
            throw new Exception("Module '$name' does not have '$it' under the src/ directory")
           this.@project.dependencies.project(path: name, configuration: it)
      }
   }
}

Usage:

modules = ModuleManager.createModuleResolver(project)
  dependencies
{
   compile modules.my_module
}

Hope this helps.


(Adam Murdoch) #5

@Dumitru, the use case you have (use a project dependency if the project is checked out, otherwise use an external dependency) is something we want to add to Gradle. However, we probably wouldn’t implement it how you’ve described.

Conceptually, there are a few steps involved in going from a dependency declaration to some actual files to compile against:

  1. Convert the dependency notation to a Dependency implementation, at configuration time. This is what the notation parser does. 2. Convert the Dependency to a module selector, at resolve time. 3. Apply the module selector to each repositories, and choose the best match.

I would do the substitution in step 2, rather than step 1. There are a couple of reasons for this. Firstly, we don’t necessarily know which projects are involved at configuration time, and secondly, it means that the original declaration is retained. This is important for later generating the meta-data descriptor when the project is published.

Another thing I would do differently is to avoid mixing both the project and module identifiers in the dependency declaration. Instead, I would separate the declaration of the dependency from the substitution. So, you would do this:

dependencies {
    compile project: 'other'
}

And have some other mechanism to tell Gradle: when you resolve a dependency on project ‘other’, replace it with a dependency on group: ‘org.myorg’, module: ‘other’, version: ‘1.2’.

Implementation-wise, I would generalise the force stuff on ResolutionStrategy (as forcing is a special case of substitution), perhaps with a callback that, given a dependency, can add/change/remove the dependency.


(gMale Another) #6

Great answer! Could you give sample code for a simple ResolutionStrategy that would allow for substituting an external dependency for a project dependency? In other words, for each dependency, if its artifactName exists as a directory, then replace it with a project dependency. It’s the ‘replace this dependency’ part that I’m struggling with. It’s unclear how to convert a dependency to a module selector at resolve time.


(Peter Niederwieser) #7

I don’t think it can currently be done with ‘ResolutionStrategy’ (I think the latter only covers external dependencies), but see https://github.com/pniederw/elastic-deps for a proof-of-concept with a different implementation.


(gMale Another) #8

That would explain why this has been so tough to figure out. It seems like this should be pretty easy: at the right moment, ignore the external dependency we were about to load and point to a local one instead. That proof of concept is great but it edits the existing subprojects. Often and in my case in particular, this is not an option.


(Peter Niederwieser) #9

I’m not aware of another solution at this time.