Attribute disambiguation I have Gradle plugin in ...
# plugin-development
a
Attribute disambiguation I have Gradle plugin in which I create a new component variant
myVariant
with the
runtimeElements
as its base variant and add
myAttribute
with
myAttributeValue
to it. Then I create a configuration where I request
myAttribute
with
myAttributeValue
as the selection preference. However, I'm getting this error.
Copy code
Caused by: org.gradle.internal.component.resolution.failure.exception.VariantSelectionByAttributesException: The consumer was configured to find attribute 'myAttribute' with value 'myAttributeValue'. However we cannot choose between the following variants of org.acme:lib-a:1.0-SNAPSHOT:
  - myVariant
  - runtimeElements
All of them match the consumer attributes:
  - Variant 'myVariant' capability 'org.acme:lib-a:1.0-SNAPSHOT' declares attribute 'myAttribute' with value 'myAttributeValue':
      - Unmatched attributes:
          - Provides org.gradle.category 'library' but the consumer didn't ask for it
          - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
          - Provides org.gradle.jvm.version '17' but the consumer didn't ask for it
          - Provides org.gradle.libraryelements 'jar' but the consumer didn't ask for it
          - Provides org.gradle.status 'integration' but the consumer didn't ask for it
          - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
  - Variant 'runtimeElements' capability 'org.acme:lib-a:1.0-SNAPSHOT':
      - Unmatched attributes:
          - Doesn't say anything about myAttribute (required 'myAttributeValue')
          - Provides org.gradle.category 'library' but the consumer didn't ask for it
          - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
          - Provides org.gradle.jvm.version '17' but the consumer didn't ask for it
          - Provides org.gradle.libraryelements 'jar' but the consumer didn't ask for it
          - Provides org.gradle.status 'integration' but the consumer didn't ask for it
          - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
	at org.gradle.internal.component.resolution.failure.describer.AmbiguousVariantsFailureDescriber.describeFailure(AmbiguousVariantsFailureDescriber.java:56)
	at org.gradle.internal.component.resolution.failure.describer.AmbiguousVariantsFailureDescriber.describeFailure(AmbiguousVariantsFailureDescriber.java:39)
	at org.gradle.internal.component.resolution.failure.ResolutionFailureHandler.lambda$describeFailure$3(ResolutionFailureHandler.java:275)
	at org.gradle.internal.component.resolution.failure.ResolutionFailureHandler.describeFailure(ResolutionFailureHandler.java:275)
	at org.gradle.internal.component.resolution.failure.ResolutionFailureHandler.ambiguousVariantsFailure(ResolutionFailureHandler.java:149)
	at org.gradle.internal.component.model.GraphVariantSelector.selectByAttributeMatchingLenient(GraphVariantSelector.java:155)
	at org.gradle.internal.component.model.GraphVariantSelector.selectByAttributeMatching(GraphVariantSelector.java:82)
	at org.gradle.internal.component.model.LocalComponentDependencyMetadata.selectVariants(LocalComponentDependencyMetadata.java:110)
	at org.gradle.internal.component.model.DelegatingDependencyMetadata.selectVariants(DelegatingDependencyMetadata.java:46)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.EdgeState.calculateTargetNodes(EdgeState.java:257)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.EdgeState.attachToTargetNodes(EdgeState.java:148)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.attachToTargetRevisionsSerially(DependencyGraphBuilder.java:390)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.resolveEdges(DependencyGraphBuilder.java:280)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.traverseGraph(DependencyGraphBuilder.java:205)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.resolve(DependencyGraphBuilder.java:164)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DependencyGraphResolver.resolve(DependencyGraphResolver.java:120)
	at org.gradle.api.internal.artifacts.ivyservice.ResolutionExecutor.doResolve(ResolutionExecutor.java:482)
	at org.gradle.api.internal.artifacts.ivyservice.ResolutionExecutor.resolveGraph(ResolutionExecutor.java:355)
	at org.gradle.api.internal.artifacts.ivyservice.ShortCircuitingResolutionExecutor.resolveGraph(ShortCircuitingResolutionExecutor.java:92)
	at org.gradle.api.internal.artifacts.ivyservice.DefaultConfigurationResolver.resolveGraph(DefaultConfigurationResolver.java:129)
I expected
myVariant
to be the candidate with the "longest" match. I must be missing something. Why is this not the case? Thanks.
m
There used to be a helpful page about this but I think it's been removed in recent versions of the doc but you can still find it, e.g: https://docs.gradle.org/8.4/userguide/variant_attributes.html#sec:abm_algorithm
Copy code
A candidate is considered compatible if its value matches the consumer's value exactly, passes the attribute's compatibility rule or is not provided.
I think this is what's happening there
So "longest" match isn't what you would expect
Which TBH should be improved in the error message
a
that's fine, that's point 1 there. Then point 3 says
Copy code
If several candidates are compatible, but one of the candidates matches all of the same attributes as the other candidates, Gradle chooses that candidate. This is the candidate with the "longest" match.
ah, they appear to be compatible on all the attributes. Hm...
So I guess I need to provide a disambiguation rule then?
i think phrasing could be improved there. It uses matches in point 3
Copy code
If several candidates are compatible, but one of the candidates matches all of the same attributes as the other candidates, Gradle chooses that candidate.
In my case, the attributes of the
runtimeElements
variant do not match the attributes of
myVariant
. But they appear to be compatible.
m
there is also https://docs.gradle.org/current/userguide/variant_model.html
Ah yes, I missed that it got improved, thanks!
a
m
Yea, this is all very confusing to me
Copy code
IF the candidate attribute is missing, mark the candidate as missing.
That should probably be
Copy code
IF the candidate attribute is missing, mark the candidate attribute as missing.
?
Still doesn't explain why it wouldn't work in your case. Since there is missing
myAttributeValue
🤔
a
right, that's what i did not expect
I have spent some time stepping through that code and at some point I think I understood it but it was a long time ago
Might be worth setting a breakpoint in
disambiguateCompatibleCandidates
to try to see what's happening?
a
Before digging into the impl, I'd hope we could clarify whether what is observed is expected or not.
I have a bit of an embarrassing question, Where could I set https://docs.gradle.org/current/javadoc/org/gradle/api/attributes/AttributeDisambiguationRule.html to use it? I see an example
Copy code
configurations.all {
    attributes {
        // Define the attribute matching strategy
        attribute(javaLanguageVersion, "1.8") {
            // Set up disambiguation logic
            disambiguationStrategy {
                // Example disambiguation: Prefer newer versions
                preferNewer()
            }
        }
    }
}
but i'm not sure how that would translate to Java code.
t
Fwiw, my recollection was that you should make sure an attribute is present in all variants, possibly using a component metadata rule to add it with a default value.
a
I like the fact that if there is no variant with the requested attribute, an otherwise “default” variant is still a candidate
But, from my perspective, I’d prefer a variant that includes the attribute with the requested value to be selected even if there is a variant with the other attributes matching that doesn’t include the requested attribute
a
I think this is the issue to +1 https://github.com/gradle/gradle/issues/30661 I agree that sometimes I want to strictly select a dependency with a specific attribute, and ignore the other variants.
a
it looks related @Adam i'm not sure it's exactly the same though. For example, if a consumer requested
myAttribute
with
myAttributeValue
and there was no variant matching that, would you expect an error?
a
yes. What would you expect?
a
i would expect what otherwise would be selected if i didn't ask for that attribute
but if there was a variant with that attribute and the matching value, i'd expect it to be selected
a
ah I see, I misunderstood then, sorry
a
afaiu, what you are asking for would force me add the attribute to all the components
this is not necessary at the moment and I like that
v
Is your new attribute added to the attribute schema of the consuming build?
a
no, i didn't touch the attribute schema
v
Then that's probably your problem
a
@Vampire i just tried
project.getDependencies().getAttributesSchema().attribute(myAttr);
but it didn't help. Perhaps I'm doing it too late though
v
Can you maybe knit an MCVE to look at?
a
sure, but before i do that, does what i observe look like an issue to fix?
v
Depends on what the reason is. 😄 From a cursory look I'd say it should work, if you request a specific value of an attribute and there is one variant that matches and one that does not have it, the one with the match should be picked and afair it used to work like that.
👍 1
But I can overlook something right now, or something in your setup is not proper. That's why I asked for an MCVE. Attribute / variant logic is often not easy to grasp and easy to do wrong.
a
makes sense, i'll look into creating a simple reproducer
👌 1
v
From a quick test even without adding to attribute schema it seems to work fine. Might have been that the attribute schema is only necessary if you need disambiguation or compatibility rules. But I learned that you should always add an attribute the schema.
👍 1