How to write custom substitution rules for ResolutionStrategy

I started playing around with 2.5 rc-1 yesterday and combed through the documentation, and for the life of me, I couldn’t figure out how given the options of ResolutionStrategy, using “substitute project with project” or “substitute project with module” to make this work for our case, which is one of using a pre-built binary from a local directory hierarchy in place of a project dependency. First of all, “module” is not well-documented anywhere that I looked. Second, it appears only to handle group,name, version. I’m guessing that what we want is closer to substitute project with library which isn’t in the API.

I had originally thought that using an artifacts closure with an associated configuration in each project on which we would have a dependency and then using something like this:

      substitute project(':core:utilities:utils') with project(path:':core:utilties:utils', configuration: 'archive'),

would be what we might have to do, but given the docs and examples, I’m completely stumped. I understand that I may have to put some custom ResolutionStrategy code in settings.gradle, but I don’t know how to morph a specific binary file to a substitution rule.

I’d appreciate some help here. Thanks.

A ‘module’ in dependency substitution roughly corresponds to an ExternalModuleDependency, and ‘project’ corresponds to a ProjectDependency. More precisely they correspond to subtypes of ComponentSelector.

If you have a pre-built Jar file that you want to use instead of building a project dependency, then you need to treat that pre-built Jar file as an external module dependency. So you’d

  1. Define a file-backed repository containing the jar file and include that in the set of repositories for resolving: using a Flat Directory repository is probably the easiest way, but an standard file-backed Ivy repository will work too.
  2. Place your prebuilt-artifact in the flat-dir repository, making sure that your artifacts include a version number in their name.
  3. Use a substitution rule to replace the project dependency with the module dependency resolved from the local repository.

Hope that helps,
Daz

Thanks, but that won’t directly work for our case. We just got rid off all versions on jars (for reasons I won’t go into here). Also, although all jars exist under a common root dir (corresponding to our product installation layout, and directly corresponding to the ultimate target build locations of each module), they are not in a flat directory repo. Perhaps we could somehow map from an Ivy repo to what we have. Does that sound like the best approach?

Yep, you can configure a file-backed Ivy repository with pretty much any pattern you like: using the Pattern layout.

I’m making good progress with this, simply using a flatDir repo with multiple dirs for now just to try to flesh out other issues. I’ve hit one snag which I can’t figure out.

I have a number of places in the build, where I do something like this:

mod/a/b/c/build.gradle
configurations { aSpecialJar }
task buildSomeJar(type: Jar) {…}

artifacts {
aSpecialJar buildSomeJar
}

Then, in another module I make a reference to this configuration:

mod/d/e/f/build.gradle
configurations { mySpecialRefConfig }
dependencies {
mySpecialRefConfig project(path: ‘:a:b:c’, configuration: aSpecialJar)
}

Now, most of the time, when I have a dependency on project(‘:a:b:c’) is simply to the default configuration and not specified, as in compile(project(‘:a:b:c’)) as is typical. For dependencies like this, my DependencySubsitution rules work. However, for the case where the dependencies are of the mySpecialRefConfig project(path: ‘:a:b:c’, configuration: aSpecialJar) form, they fail with a message like:

Could not resolve all dependencies for configuration ‘:mySpecialRefConfg’.
Module version :mod:unspecified, configuration ‘mySpecialRefConfg’ declares a dependency on configuration ‘aSpecialJar’ which is not declared in the module descriptor for group-ignored:symName:1.0

My dependencySubsitution code in mod/build.gradle looks like this:

  configurations.all {
    resolutionStrategy {
      dependencySubstitution {
        all { DependencySubstitution dependency ->
            if (dependency.requested instanceof ProjectComponentSelector) {
              def symName = project(dependency.requested.getProjectPath()).symbolicName
              /*
                If we're running with -a, then substitute the pre-built jar, if one exists, for the
                requested dependency.  Otherwise, don't do a substitution
              */
              if (!project.gradle.startParameter.isBuildProjectDependencies()) { // i.e. "gradle -a"
                dependency.useTarget(module("group-ignored:${symName}:1.0")) // ignores group-ignored and version 1.0
              }
            }
          }
        }
      }
    }

The only thing I’ve been able to figure out so far is to not do the dependency substitution rule for modules which are referenced this way, but this is not a good solution at all for a number of reasons, the main one being that it will defeat the potential performance gains of substituting a pre-built jar for those modules that have lots of references.
How can I either (a) construct the substitution rules to account for this, or (2) override the rules when an alternative configuration is specified on the dependency?

Thanks

By using a flatDir repository you are losing the ability to describe multiple configurations for a module, and there’s no way to replace the configuration value using a dependency substitution rule. So the only way forward that I can see is to use a local ivy repository, which will allow you to more accurately represent the project artifacts.

I’ve switched to an ivy repository, but the same question remains: How can I define a different way of handling dependencies on a project when those dependencies reference named external configurations on the project. Ideally, what I would like to do is define the dependency substitution only for the default configuration, but not otherwise or if. To use the example above:

For normal dependencies such as:
compile(project(’:a:b:c’)) I want my substitution to be applied/used,

however if the dependency is of this form:
special(project(path: ‘:a:b:c’, configuration: aSpecialJar), I do not want to my substitution to be applied/used

Even using ivy, I am no closer to figuring out how at resolution time, I can circumvent the substitution so that the original/unaltered dependency is used in this special case.

It seems to me that the substitution rules would be more powerful if they would allow substitution on a designated set of configurations and not simply apply to all configurations.

Thanks again,
Jim