Can Gradle's conflict manager be extended?

In Ivy, it is almost trivial to extend and inject a custom conflict manager by extending the AbstractConflictManager. We’ve done it in a snap to add a special branch name resolution rule. We’re trying to find a way to do that with Gradle but we’re not sure it’s possible. Any advice?

Can you elaborate a bit on the “a special branch name resolution rule” use-case? What exactly do you try / need to achieve?

Thanks for your question. The special resolution rule is simply to look up a map of preferred branch names when a branch name conflict arises. It was trivial to do in ivy, we just followed the instructions for the ivy typedef feature and it worked. Except that the dependency tree is now outside of Gradle… and ivy itself might be moved into the attic. It’s best if the dependency tree is done by Gradle and known to Gradle for later steps in the build process (and to leverage Gradle’s cache management features).

Ideally, we need first class branch name support for ivy dependency resolution (Add support for Ivy branch attribute for resolving dependencies · Issue #1357 · gradle/gradle · GitHub), just like it exists in ivy. But without first class support of branches, I am not sure where to turn.

We have a really hard time figuring out how to replace or extend the Gradle built-in dependency resolution and the conflict resolver code. We’re finding only internal api classes that are certainly not intended to be extended by users, for example we looked at extending DefaultResolutionStrategy and the ResolutionStrategyFactory but it is all part of an internal api.

Ah, I see.
Unfortunately, I’m not aware of any way to do this, besides maybe a custom distribution that adds the functionality.
I’m curious what happens to the Ivy support in Gradle if it really is sent to the attic.
There are also Ivy classes used in Gradle, not only the format supported.

This was said in that discussion:

Personally I believe we should send IvyDE to the Apache Attic immediately,
and this likely should be the destination for Ivy sooner or later as well.

Sad news since Ivy is the only dependency manager with first class support for branches.

Well, if you are not bound to the branch attribute in the Ivy metadata format, there are other ways, especially when using Gradle for producer and consumer.

You can for example have an attribute on your outgoing variants where the branch is the value.
And then you can have in the consumers (can be provided by some plugin the consumers can apply) compatibility rules and so on.
So with using variants / attributes, you can for sure replace that facility.

Regarding variants, I have three questions. I feel knowing the answers would help me understand better.

It appears to me that the all variants of a component must be published at the same time for a given version of a component. Is this right?

Variants appear to add an arbitrary number of dimensions to dependency resolution, but where do those dimensions start to exist? Do they exist before the version number ivyOrg:ivyModule:variants:revision or after ivyOrg:ivyModule:revision:variants ?

It looks to me like variants of the Gradle component model are very similar to ivy configurations: a way to slice and select a set of artifacts for a given component version, but with extra dimensions. Is this correct?

Thanks in advance.

It appears to me that the all variants of a component must be published at the same time for a given version of a component. Is this right?

I think so, yes.
Especially, if it is for the same version, you have only one module file and there all variants are listed.

Variants appear to add an arbitrary number of dimensions to dependency resolution,

Yes

but where do those dimensions start to exist? Do they exist before the version number ivyOrg:ivyModule:variants:revision or after ivyOrg:ivyModule:revision:variants ?

No. :slight_smile:
You can define them on a dependency or on a configuration using attributes { attribute(...) } see here for more in-depth documentation: Understanding variant selection

It looks to me like variants of the Gradle component model are very similar to ivy configurations: a way to slice and select a set of artifacts for a given component version, but with extra dimensions. Is this correct?

Sounds not too wrong. :slight_smile:

I still don’t understand how I can use variants to support ivy branches.

I have these two “paths” in my ivy repository, each with different sets of artifacts:

  1. org/module/branchOne/1.0.0 (conf1: a few artifacts, conf2: a few artifacts, etc.)
  2. org/module/branchTwo/1.0.0 (conf1: a few artifacts, conf2: a few artifacts, etc.)

They were created months apart. Under each path, artifacts are grouped by ivy configurations, and it’s the same configuration names in both cases.

Constraints: I cannot make the branch name a part of the module name, and dependency declarations do not support branch names…

How do I write my dependency declaration? Is it even possible?

I did not say you can use variants to consume those Ivy artifacts.
I said when using Gradle variants on both, the producer and consumer side, you could maybe knit something that suits your needs, even if Ivy is sent to the attic.

I found an example of variants in Configure build variants  |  Android Studio  |  Android Developers. After reading it, I conclude that all variants known at the time a git commit is made are published together to the same Maven coordinates and that new variants (branches) cannot be added to the same version number later on, at least not easily, and the gradle module metadata would have to be modified to include the new variant, violating the principle that artifacts should be immutable after publishing.

It does not look like variants solve my problem which is to add a dimension to the coordinates, before the version number. Or I have not seen or recognized the right example yet…

That is an Android page, not a Gradle page.
It has nothing to do with Gradle feature variants which you can find at Modeling feature variants and optional dependencies.

But yes also there I already confirmed previously that you cannot add variants under the same coordinates and version later on. But as I also said above, you also seem to have different versions on different branches from what you showed.

Or you could for example encode the branch name in the artifact name, but set a capability without the branch name, so foo:bar-brancha:1.0 and foo:bar-branchb:1.0 both with capability set to foo:bar:1.0 and maybe additionally the default foo:bar-branch?:1.0. If you then depend on both, you get a capability conflict which you can then resolve how you like with an according conflict resolution rule.

I never said you will have a 1:1 translation. But I said, that depending on your concrete needs - which are a mystery - you might be able to come up with a satisfactory solution with only Gradle stuff.

Thank you very much for your answers, help, and explanations. Sorry, I did not mean to make my needs a mystery. I have a working ivy setup that uses the git branch name. I did not want to paste my scenario here because it’s already in this comment but here it is:

                          ,-- 1.0.0-b1
  -- o -- o -- o -- o -- o -- o -- ...
                \                ,-- 1.0.0-b2        ,-- 1.1.0-b2
                  `-- o -- o -- o -- o -- o -- o -- o -- ...
                                           \                ,-- 1.0.0-b3        ,-- 1.0.1-b3
                                             `-- o -- o -- o -- o -- o -- o -- o -- ...

The above diagram shows the git history with tags of the form Major.minor.patch-branchName. Artifacts are published to an Ivy repository where the branchName value goes to the ivy branch attribute, and the Major.minor.patch goes to the ivy revision attribute (details at info | Apache Ivy™ Documentation). Revision and branch can be conflict in the dependency tree. Hence the original question on whether or not the gradle conflict resolver java class can be extended (the Ivy one can so I wanted to learn if the Gradle one could).

I don’t know if I should pursue the variants route, it seems the branch concept is not a very good fit for it. Appending the branch name to any of the GAV coordinates feels a bit like a hack.

There was a pull-request to add native ivy branch support to gradle, but it was closed for inactivity: Ivy dependency branch support by perrinmorrow · Pull Request #10880 · gradle/gradle · GitHub . If I manage to revive it, would it get any attention?

Sorry again for not being clear.

Sorry that I was not clear.
I’ve read that comment previously, but it more shows how you solve your need with Ivy, not what the actual requirements are that should be implemented.
Also, even if you depict them, I would not be able to provide a solution most probably, as it most probably needs playing around to get it working as intended and I don’t have the time to do that for you in my spare time as I did not do something like that before. :slight_smile:

I’m not sure how “hacky” the solution with own artifact-name per branch would be.
Actually, I think Ivy solution is the more hacky approach, as you repeatedly modify an existing artifact which is bad for caching the resolution results and so on.
If you do like suggested, with own artifact name per branch, the artifacts will not change over time and you can request a specific branch, and by assigning the same capability to the different branch releases, you get a capability conflict when depending on multiple of those artifacts with Gradle, and can then use a capability conflict rule that can decide from branchname and version which of the conflicting artifacts to use.

Regarding reviving the PR, or creating a new one based on it, there was no negative statement about the concept in general by the Gradle folks, so it could well be that you get it in if you revive it. Just be aware, that they are pretty short on resources “currently” and PR attention needs quite some time nowadays. But at least the “stale” action is now configured to not auto-close PRs anymore due to inactivity unless the DCO signoff is missing. It might get the “stale” label, but nothing to worry about currently.

No, artifacts are not changed after publishing, never. The first part of the artifact pattern ensures that artifacts of different branches are stored at different coordinates in the binary repository: [org]/[module]/[branch]/[revision]. In Ivy, the branch is also a coordinate, so instead of the Maven “GAV”, Ivy can have “GABV”.

Ah, yes, right.
Actually, it seems when you publish using the ivy-publish plugin, you can even set the branch property: Ivy Publish Plugin.
You just cannot consume it, it seems.
One more reason that a PR should be considered :slight_smile:

1 Like