Subsitution Rules


(Jordan Carlyon) #1

According to the Resolution Strategy docs I should be able to target a specific module that I can substitute. How can I target a specific module in a specific included library, where both the included library and my app require a 3 third module, and then target the included library in my apps.

For example my app and the included module named myLib both need use of notMyLib

dependencySubstitution {
substitute module(’:myLib:com.google.some:thing:4.21.2’) with module(‘notMyLib:com.google.some:thing:4.21.2’’)
}

This doesn’t work with failure

Could not find method dependencySubstitution() for arguments [build_dws8f2a3fpczygr77uxbgk9g7$_run_closure3$_closure35@47987206] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler


(James Justinic) #2

Where is the code snippet located in your build script?

The error indicates you did this:

dependencies {
    resolutionStrategy {
        dependencySubstitution {
            substitute module(':myLib:com.google.some:thing:4.21.2') with module('notMyLib:com.google.some:thing:4.21.2')
        }
    }
}

instead of something like this:

configurations.all {
    resolutionStrategy {
        dependencySubstitution {
            substitute module(':myLib:com.google.some:thing:4.21.2') with module('notMyLib:com.google.some:thing:4.21.2')
        }
    }
}

The code dependencySubstitution { ... } is invoking a method called dependencySubstitution with a closure as the argument. It’s just likely on the wrong place (dependencies vs. a configuration).


(Jordan Carlyon) #3

I don’t see anything on that in the docs on that page, regardless I get this same error with configurations and configurations.all

So doing

configurations.all {
resolutionStrategy {
dependencySubstitution {

gives the error

Error:(213, 0) Cannot convert the provided notation to an object of type ComponentSelector:

This is all in my dependencies block


(James Justinic) #4

If you’re getting Cannot convert the provided notation to an object of type ComponentSelector:, your code is at least calling the right methods on the API now. The error is due to the strings inside of the module(...) declarations. I’m expecting that you’re not actually using the values as written, but these values will cause the same error. The correct format should be module('group:name') or module('group:name:version').

When resolving a configuration, you either want a certain library replaced or you don’t. There’s no concept of different dependencies using different transitive dependencies. In the application, it’s all one classpath, so you’ve either have the substitution or you don’t, and if there’s somehow duplicate classes, normal classpath ordering fun applies.

Although configurations are used for dependency resolution, the configuration of the resolutionStrategy of the configurations is unrelated to the dependencies block. It would be typical to have something like this

apply plugin: 'java'
  
configurations.all {
    resolutionStrategy {
        dependencySubstitution {
            substitute module('org.apache.logging.log4j:log4j-core') with module('org.slf4j:log4j-over-slf4j:1.7.25')
        }
    }
}

dependencies {
    compile 'org.apache.logging.log4j:log4j-core'
}

repositories {
    jcenter()
}

(Jordan Carlyon) #5

Thanks for the help, I think the issue is now that I am trying to replace an included projects dependency with my app’s dependency.

substitute module(’:includedProject:log4j-core’) with module(‘log4j-core’)

I think my issue might be that gradle doesn’t recognize that project path, reading their example it looks like they either target the entire project or nothing at all

substitute project(’:util’) with module(‘org.gradle:util:3.0’)

Where I want to substitute ‘:util:org.gradle:util:3.0’ with org.gradle:util:3.0’


(James Justinic) #6

This doesn’t really make sense. In this example, org.gradle:util:3.0 is org.gradle:util:3.0 regardless of being a direct dependency or a dependency of project(':util').

The project path isn’t relevant at all for dependency resolution. If you had :includedProject and your app both depending on log4j-core, one version of log4j-core is resolved. You can substitute it with something else, if that’s what you want to do, but there’s only one despite the various levels of the dependencies.

It almost seems like you’re trying to describe a situation that could arise with how node_modules are handled with NPM, but applying classpath principles to it.

To get the answer you’re looking for, I think you would at minimum need to provide a hierarchal dependency tree with the projects (at least dummy names) and modules you have (something public that can actually be resolved) and how they relate to each other, along with what you want to have after the substitution. A working example project is even better.


(Jordan Carlyon) #7

I found the answer from here

compile (‘com.facebook.fresco:fresco:0.12.0+’){
exclude group: ‘com.parse.bolts’, module: ‘bolts-applinks’;
exclude module: ‘bolts-android’;
exclude group: ‘com.parse.bolts’, module: ‘bolts-tasks’;
}

I wasn’t able to find this type of exclude group module in the docs anywhere. I’m sure it’s there somewhere though.


(James Justinic) #8

So, you didn’t want dependency substitution at all. You just wanted to exclude a certain transitive dependency. That’s here: Excluding transitive module dependencies


(Jordan Carlyon) #9

I wasn’t familiar with the specific language used by gradle to describe these two different but similar situations. So substitution rules are used to manage entire libraries or specific dependencies but not both, and transitive dependency is used to manage specific dependencies in a module.


(James Justinic) #10

These are all about managing transitive dependencies. It’s really about intent. Do you want to exclude a dependency from the app no matter what? Do you want to exclude it because it’s optional for your current dependency, but it might be needed laster? Do you want to substitute an implementation with something better?

Using the fresco example above, consider these points:

  • if bolts-tasks is optional for fresco, but some other library could require it, exclude it from fresco as shown
  • if bolts-tasks is evil and you never should have it in your application, no matter what, you should exclude it from all configurations, not just this module
  • if bolts-tasks is unwanted because you have super-bolts-tasks that is a way better implementation of the same interfaces, you want to substitute bolts-tasks with super-bolts-tasks.

(Jordan Carlyon) #11

Actually the situation is that I am including a single dependency twice, once in my app and once in the included library which is causing a conflict so using that exclude group module let’s me remove the dependency from the library.