Where to put something that can be retrieved from pluginManager and configuration

I have made a plugin which will be working in two “modes”:

For the normal mode resolutionStrategy will be whatever is specified as the version i.e. if the version for a dependency is set to latest.integration then the latest integration is retrieved for the particular dependency, so essentially it will behave as gradle will normally do.

When we have to make a release version of an artifact a file is generated containing the versions that each dependency needs to be locked to. It is done in order to avoid to modify build gradle file.

I achieved this by having a custom resolutionStrategy in pluginManager in settings and configurations.

In order to not have duplicated code I have created a groovy script which is applied to the pluginManager, the buildScript and the project, and it works as expected. The groovy script gets downloaded from a repository via a hardcoded url.

My challenge is now that after it has been working for a long time more and more projects is using this logic. and some enhancements needs to be done, and given that all projects will always take the latest it means that whatever change I made will have an effect on all the projects using this plugin, which means that one has to be extremely careful not to break any of the project, which potentially can be quite difficult.

So I figured that I would like to be able to select certain version of a dependency while on others they should just use the latest integration.

So in order to achieve this I would like to add some logic that would be able to select a version for each project e.g. for project abc then the version of a dependency A should be e.g. 1.20.0 and but for project def the version of a dependency A should be e.g. 1.25.0 and for any other the version of A should be latest.integration. This behaviour would also help if I would like to upgrade from gradle 4.x to 5.x and I do not want to update all projects at the same time.

What I would like to avoid is to go through all 100 projects and change the hardcoded url every time a new strategy needs to be implemented so I thought about using the built in dependency mechanism to get the latest version selection logic down.

I found out that the resolutionStrategy can only be changed until an item has been resolved, so I thought about making a hack, where the resolutionStrategy used act as proxy for some logic that will actually do the version selection.

So I imagined something along these line:

resolutionStrategy {
eachPlugin { it ->
delegate.resolve(it)
}
}

and then in the build gradle script I would have something like

plugins {
id(‘version-selector’) version “LATEST”
id(‘my-plugin1’) version “LATEST”
id(‘my-plugin2’) version “LATEST”
}

LATEST is used as gradle 4.9 does not support dynamic version so I made a hack to support this, but it is irrelevant for the discussion. But what should happen is that the version-selector should retrieve the latest version of the version-selector which contains the delegate needed for the resolution strategy.
And then the delegate should be instantiated and injected somehow into the resolutionStrategy.

Using the above example it would not be possible inject the value into the resolutionStrategy so I thought about having some container object and do some lazy initialisation so that resolutionStrategy I would look for the delegate and when applying the version-selector the delegate needed to be stored somewhere where it can be retrieved by the resolutionStrategy.

What I can’t seem to figure where it will be possible to store the delegate that be retrieved from the resolutionStrategy.

I tried to see if it would be possible to store the delegate into the ext, extensions, conventions, however they do not seems to be the same when running in the resolutionStrategy and in the build script and therefore this cannot be easily transferred.

So I would like to ask if there is a better way to do what I am intended to do, which is to be able to store something somewhere in gradle and can easily be retrieved by the resolutionStrategy.

Alternatively the resolutionStrategy needs to be implemented in another way so if any has some suggestion it would be highly appreciated.

I’m not entirely sure I understand. What I understand is:

  1. There’s a Groovy script which selects between (1) normal resolution and (2) some custom resolutionStrategy.
  2. This script is downloaded from some URL, which must be updated when changing the script.
  3. You want a way to select which script is used on each project, while avoiding breaking functionality.
  4. Your suggested solution is:

So I figured that I would like to be able to select certain version of a dependency while on others they should just use the latest integration.

At this point I think I lost you a bit. Are you trying to solve the problem of distributing the script? Or do you want to find a different solution, which will replace the script?

Hehe maybe it was too confusing and to internal so I’ll explain in a more simple terms but equivalent use case.

Supposedly I have created a plugin, which sets up some defaults like: what java version the source and target compatibility should be, what the encoding should be used and other things that may be standard for all projects, and that plugin also contains some logic to be build in a specific way. This plugin is used for 250 projects, so the each project use the latest.integration of that plugin(I know latest.integration is not possible for plugins, however this is solved and is another discussion), so all the project enjoys to have the latest configuration whenever we made some project wide changes.

Now I want to update to let us gradle 4.x to 5.x (but it could be something else), and the plugin is not backwards compatible and I do not want to wait until I have tested my changes on all projects but I rather upgrade in a pace that our team can cope with.

So now what was an advantage has now becoming a disadvantage because I will be forced to ensure that all the projects will have to be changed at the same time. So what I would like is a mechanism that can set a version for a plugin or dependency for a specific project.

To be concrete:

Let us say the most recent version of the plugin is 1.25.0, and I have project a, b and c.

I would like project a and c to be locked to plugin version 1.25, and for project b it should be latest.integration. Then I can evolve the plugin and ensure that it will work for b and then a, c can be upgraded when it is ready. Since the version to be used for the projects may change over time I would like to avoid fixing all projects every time there is a change.

There are several options for solving this;

I could use dynamic ranges. I am not in favor of this as because we have a mechanism which automatically bumps the version. But one still will need to fix all projects but just as often.

I know that gradle 4.9+ supports resolutionStrategy and we have been using this for other purposes.

So I think I need to expand it so it override the behavior in resolutionStrategy in pluginManager, the configurations in buildscript and the projects to behave as described above.

My challenge is the knowledge of locking which version to which project without changing all projects I imagined can be achieved by having an additional plugin that can modify the resolutionStrategy to lock versions to specific projects.

So what I though the solution for this is to create a resolutionStrategy object, which could delegate the strategy to the plugin once the plugin has been loaded.

So I made some logic similar to the below:

I have a groovy script (because I would not like to duplicate the logic for all projects), which will set up a resolutionStrategy for pluginManager, and the configuation. The resolutionStrategy is more less something like this:

class DependencyResolverDelegator implements Action {
public Action delegate

@Override
void execute(DependencyResolveDetails dependencyResolveDetails) {
    if(delegate != null) {
        delegate.execute(dependencyResolveDetails)
    }
}

}

and then have some logic:

configurations.all { configuration ->
    logger.quiet("Applying release resolution strategy for configuration {}", configuration.name)
    configuration.resolutionStrategy.eachDependency(new DependencyResolverDelegator())
}   

Now the problem is how do I get the instance so I can inject my plugins resolutionStrategy into the proxy from a plugin?

Especially when the proxy object is created in a script and the instances are not accessible outside the script
(I tried to reflect on the pluginManager afterwards but could not figure out how to get the instance I have created in the script).

And to make things more chalenging it is so early in build process that most things are not available when working with pluginManager in the settings. So I have challenges to find a place to store the proxy object (once retrieved), so the plugin can inject its resolution strategy later on. I though about putting it in properties of the rootProject but it is not available at this time.

So the way I have figured out is by making a crazy hack like taking advantage of that the gradle property is available at that point in time and project property can be replaced, so what I did was to extend a hashmap with additional fields which could contain my proxy object instances, and this can be retrieved from my plugin.

I know it is a very hackish way but I am very open for better solution that make me appear as a more sane person :stuck_out_tongue:

My answer got long but I hope it was more clearer.

I know that the use case is nothing that gradle has thought of (and they may not want to support this use case) and therefore I am stepping out of the boundaries of what gradle is capable is able to do (I do not know about the more recent versions) . But I would prefer this rather than having to change all the 250 projects everytime or ensure that it works for all 250 projects before releasing a new version of the plugin.

I forgot to mention we use Gradle 4.9 (and want to upgrade to 5.x) and we use Kotlin where ever possible (which is not possible when you apply a script)

I just found out that gradle will first resolve all plugins and then apply them afterwards so that was not part of my expectation, however it seems reasonable. So the concept will work for dependencies but not for plugins.

So I have to go back to the drawing board :confused: and figure what alternatives I have.