Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If a variant cannot be selected because there are no variants, mentio… #28971

Merged
merged 6 commits into from
May 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,8 @@ class SwiftApplicationIntegrationTest extends AbstractSwiftIntegrationTest imple

and:
failure.assertHasCause("Could not resolve project :greeter.")
failure.assertHasCause("A dependency was declared on configuration 'default' which is not declared in the descriptor for project :greeter.")
failure.assertHasCause("No matching variant of project :greeter was found. The consumer was configured to find attribute 'org.gradle.native.architecture' with value 'x86-64', attribute 'org.gradle.native.debuggable' with value 'true', attribute 'org.gradle.native.operatingSystem' with value 'linux', attribute 'org.gradle.native.optimized' with value 'false', attribute 'org.gradle.usage' with value 'native-runtime' but:\n" +
" - No variants exist.")
}

def "can compile and link against a static library"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import org.gradle.test.fixtures.dsl.GradleDsl
import org.gradle.test.fixtures.file.TestFile
import org.gradle.util.GradleVersion
import spock.lang.Ignore

/**
* These tests demonstrate the behavior of the [ResolutionFailureHandler] when a project has various
* variant selection failures.
Expand Down Expand Up @@ -179,6 +178,26 @@ class ResolutionFailureHandlerIntegrationTest extends AbstractIntegrationSpec {
assertSuggestsViewingDocs("Incompatible variant errors are explained in more detail at https://docs.gradle.org/${GradleVersion.current().version}/userguide/variant_model.html#sub:variant-incompatible.")
}

def "demonstrate no variants exist"() {
noGraphVariantsExistForProject.prepare()

expect:
assertResolutionFailsAsExpected(noGraphVariantsExistForProject)

and: "Has error output"
failure.assertHasDescription("Could not determine the dependencies of task ':forceResolution'.")
failure.assertHasCause("Could not resolve all dependencies for configuration ':resolveMe'.")
failure.assertHasCause("Could not resolve project :producer.")
assertFullMessageCorrect(""" Required by:
project :
> No matching variant of project :producer was found. The consumer was configured to find attribute 'color' with value 'green' but:
- No variants exist.""")

and: "Helpful resolutions are provided"
assertSuggestsReviewingAlgorithm()
assertSuggestsViewingDocs("Creating consumable variants is explained in more detail at https://docs.gradle.org/${GradleVersion.current().version}/userguide/declaring_dependencies.html#sec:resolvable-consumable-configs.")
}

// region Configuration requested by name
def "demonstrate configuration not found selection failure"() {
configurationNotFound.prepare()
Expand Down Expand Up @@ -406,6 +425,7 @@ class ResolutionFailureHandlerIntegrationTest extends AbstractIntegrationSpec {
private final Demonstration ambiguousGraphVariantForExternalDep = new Demonstration("Ambiguous graph variant (external)", VariantSelectionException.class, VariantAwareAmbiguousResolutionFailure.class, this.&setupAmbiguousGraphVariantFailureForExternalDep)
private final Demonstration noMatchingGraphVariantsForProject = new Demonstration("No matching graph variants (project dependency)", VariantSelectionException.class, IncompatibleGraphVariantFailure.class, this.&setupNoMatchingGraphVariantsFailureForProject)
private final Demonstration noMatchingGraphVariantsForExternalDep = new Demonstration("No matching graph variants (external dependency)", VariantSelectionException.class, IncompatibleGraphVariantFailure.class, this.&setupNoMatchingGraphVariantsFailureForExternalDep)
private final Demonstration noGraphVariantsExistForProject = new Demonstration("Blah", VariantSelectionException.class, IncompatibleGraphVariantFailure.class, this.&setupNoGraphVariantsExistFailureForProject)

private final Demonstration incompatibleRequestedConfiguration = new Demonstration("Incompatible requested configuration", VariantSelectionException.class, IncompatibleRequestedConfigurationFailure.class, this.&setupIncompatibleRequestedConfigurationFailureForProject)

Expand All @@ -422,6 +442,7 @@ class ResolutionFailureHandlerIntegrationTest extends AbstractIntegrationSpec {
ambiguousGraphVariantForExternalDep,
noMatchingGraphVariantsForProject,
noMatchingGraphVariantsForExternalDep,
noGraphVariantsExistForProject,
incompatibleRequestedConfiguration,
incompatibleArtifactVariants,
noMatchingArtifactVariants,
Expand Down Expand Up @@ -708,6 +729,38 @@ class ResolutionFailureHandlerIntegrationTest extends AbstractIntegrationSpec {
"""
}

private void setupNoGraphVariantsExistFailureForProject() {
settingsKotlinFile << """
rootProject.name = "example"
include("producer")
"""

buildKotlinFile << """
plugins {
id("base")
}

val color = Attribute.of("color", String::class.java)

configurations {
dependencyScope("defaultDependencies")

resolvable("resolveMe") {
extendsFrom(configurations.getByName("defaultDependencies"))
attributes.attribute(color, "green")
}
}

dependencies {
add("defaultDependencies", project(":producer"))
}

${forceConsumerResolution()}
"""

file("producer/build.gradle.kts").touch()
}

private void setupIncompatibleRequestedConfigurationFailureForProject() {
buildKotlinFile << """
plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,38 @@ All of them match the consumer attributes:
failure.assertHasCause """Selected configuration 'default' on 'project :b' but it can't be used as a project dependency because it isn't intended for consumption by other components."""
}

def "mentions that there are no variants when there are none"() {
given:
createDirs("a", "b")
file('settings.gradle') << "include 'a', 'b'"
buildFile << """
$typeDefs

project(':a') {
configurations {
_compileFreeDebug.attributes { $freeDebug }
}
dependencies {
_compileFreeDebug project(':b')
}
task checkDebug(dependsOn: configurations._compileFreeDebug) {
doLast {
assert configurations._compileFreeDebug.collect { it.name } == []
}
}
}
project(':b') {
}
"""

when:
fails ':a:checkDebug'

then:
failure.assertHasCause """No matching variant of project :b was found. The consumer was configured to find attribute 'buildType' with value 'debug', attribute 'flavor' with value 'free' but:
- No variants exist."""
}

def "does not select explicit configuration when it's not consumable"() {
given:
createDirs("a", "b")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ public AbstractResolutionFailureException noMatchingCapabilitiesFailure(Attribut
return describeFailure(schema, failure);
}

public AbstractResolutionFailureException noVariantsExistFailure(AttributesSchemaInternal schema, AttributeContainerInternal requestedAttributes, ComponentIdentifier targetComponent) {
IncompatibleGraphVariantFailure failure = new IncompatibleGraphVariantFailure(targetComponent.getDisplayName(), requestedAttributes, Collections.emptyList());
return describeFailure(schema, failure);
}

// region Configuration by name
// These are cases where a configuration is requested by name in a target component, so there is sometimes no relevant schema to provide to this handler method.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.capabilities.Capability;
Expand Down Expand Up @@ -157,11 +156,10 @@ public VariantGraphResolveState selectByAttributeMatchingLenient(
public VariantGraphResolveState selectLegacyConfiguration(ImmutableAttributes consumerAttributes, ComponentGraphResolveState targetComponentState, AttributesSchemaInternal consumerSchema) {
VariantGraphResolveState conf = targetComponentState.getCandidatesForGraphVariantSelection().getLegacyVariant();
if (conf == null) {
// TODO: We should have a better failure message here.
// We wanted to do variant matching, but there were no variants in the target component.
// So, we fell back to looking for the legacy (`default`) configuration, but it didn't exist.
// We should say that instead of failing with `A dependency was declared on configuration 'default' ...`
throw failureProcessor.configurationNotFoundFailure(targetComponentState.getId(), Dependency.DEFAULT_CONFIGURATION);
// So, there are no variants to select from, and selection fails here.
throw failureProcessor.noVariantsExistFailure(consumerSchema, consumerAttributes, targetComponentState.getId());
}

validateVariantAttributes(conf, consumerAttributes, targetComponentState, consumerSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,37 +37,59 @@
public abstract class IncompatibleGraphVariantsFailureDescriber extends AbstractResolutionFailureDescriber<IncompatibleGraphVariantFailure> {
private static final String NO_MATCHING_VARIANTS_PREFIX = "No matching variant errors are explained in more detail at ";
private static final String NO_MATCHING_VARIANTS_SECTION = "sub:variant-no-match";
private static final String NO_VARIANTS_EXIST_PREFIX = "Creating consumable variants is explained in more detail at ";
private static final String NO_VARIANTS_EXIST_SECTION = "sec:resolvable-consumable-configs";

@Override
public VariantSelectionException describeFailure(IncompatibleGraphVariantFailure failure, Optional<AttributesSchemaInternal> schema) {
AttributeDescriber describer = AttributeDescriberSelector.selectDescriber(failure.getRequestedAttributes(), schema.orElseThrow(IllegalArgumentException::new));
String message = buildNoMatchingGraphVariantSelectionFailureMsg(new StyledAttributeDescriber(describer), failure);
List<String> resolutions = buildResolutions(suggestSpecificDocumentation(NO_MATCHING_VARIANTS_PREFIX, NO_MATCHING_VARIANTS_SECTION), suggestReviewAlgorithm());
FailureSubType failureSubType = FailureSubType.determineFailureSubType(failure);
String message = buildNoMatchingGraphVariantSelectionFailureMsg(new StyledAttributeDescriber(describer), failure, failureSubType);
List<String> resolutions = buildResolutions(failureSubType);
return new VariantSelectionException(message, failure, resolutions);
}

protected String buildNoMatchingGraphVariantSelectionFailureMsg(StyledAttributeDescriber describer, IncompatibleGraphVariantFailure failure) {
private String buildNoMatchingGraphVariantSelectionFailureMsg(StyledAttributeDescriber describer, IncompatibleGraphVariantFailure failure, FailureSubType failureSubType) {
TreeFormatter formatter = new TreeFormatter();
String targetVariantText = style(StyledTextOutput.Style.Info, failure.getRequestedName());
if (failure.getRequestedAttributes().isEmpty()) {
formatter.node("Unable to find a matching variant of " + targetVariantText);
} else {
formatter.node("No matching variant of " + targetVariantText + " was found. The consumer was configured to find " + describer.describeAttributeSet(failure.getRequestedAttributes().asMap()) + " but:");
}

formatter.startChildren();
if (failure.noCandidatesHaveAttributes()) {
formatter.node("None of the variants have attributes.");
} else {
// We're sorting the names of the configurations and later attributes
// to make sure the output is consistently the same between invocations
for (ResolutionCandidateAssessor.AssessedCandidate candidate : failure.getCandidates()) {
formatUnselectableVariant(candidate, formatter, describer);
}
switch (failureSubType) {
case NO_VARIANTS_EXIST:
formatter.node("No variants exist.");
break;
case NO_VARIANTS_HAVE_ATTRIBUTES:
formatter.node("None of the variants have attributes.");
break;
case NO_VARIANT_MATCHES_REQUESTED_ATTRIBUTES:
// We're sorting the names of the configurations and later attributes
// to make sure the output is consistently the same between invocations
for (ResolutionCandidateAssessor.AssessedCandidate candidate : failure.getCandidates()) {
formatUnselectableVariant(candidate, formatter, describer);
}
break;
default:
throw new IllegalStateException("Unknown failure sub type: " + failureSubType);
}
formatter.endChildren();

return formatter.toString();
}

private List<String> buildResolutions(FailureSubType failureSubType) {
if (failureSubType == FailureSubType.NO_VARIANTS_EXIST) {
String suggestReviewCreatingConsumableConfigs = NO_VARIANTS_EXIST_PREFIX + getDocumentationRegistry().getDocumentationFor("declaring_dependencies", NO_VARIANTS_EXIST_SECTION) + ".";
return buildResolutions(suggestReviewCreatingConsumableConfigs, suggestReviewAlgorithm());
} else {
return buildResolutions(suggestSpecificDocumentation(NO_MATCHING_VARIANTS_PREFIX, NO_MATCHING_VARIANTS_SECTION), suggestReviewAlgorithm());
}
}

private void formatUnselectableVariant(
ResolutionCandidateAssessor.AssessedCandidate assessedCandidate,
TreeFormatter formatter,
Expand All @@ -78,4 +100,21 @@ private void formatUnselectableVariant(
formatter.append("'");
formatAttributeMatchesForIncompatibility(assessedCandidate, formatter, describer);
}

private enum FailureSubType {
NO_VARIANTS_EXIST,
NO_VARIANTS_HAVE_ATTRIBUTES,
NO_VARIANT_MATCHES_REQUESTED_ATTRIBUTES;

public static FailureSubType determineFailureSubType(IncompatibleGraphVariantFailure failure) {
if (failure.getCandidates().isEmpty()) {
return FailureSubType.NO_VARIANTS_EXIST;
}
if (failure.noCandidatesHaveAttributes()) {
return FailureSubType.NO_VARIANTS_HAVE_ATTRIBUTES;
} else {
return FailureSubType.NO_VARIANT_MATCHES_REQUESTED_ATTRIBUTES;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package org.gradle.integtests.composite


import org.gradle.api.JavaVersion
import org.gradle.integtests.fixtures.ToBeFixedForConfigurationCache
import org.gradle.integtests.fixtures.build.BuildTestFile
import org.gradle.integtests.fixtures.resolve.ResolveTestFixture
Expand Down Expand Up @@ -696,8 +696,9 @@ class CompositeBuildDependencyGraphIntegrationTest extends AbstractCompositeBuil
when:
checkDependenciesFails()

then:
failure.assertHasCause("A dependency was declared on configuration 'default' which is not declared in the descriptor for project :buildC.")
then: "Build C does not have any configurations defined, and thus no variants exist"
failure.assertHasCause("""No matching variant of project :buildC was found. The consumer was configured to find a library for use during runtime, compatible with Java ${JavaVersion.current().majorVersion}, packaged as a jar, preferably optimized for standard JVMs, and its dependencies declared externally but:
- No variants exist.""")
}

public static final REPOSITORY_HINT = repositoryHint("Maven POM")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.gradle.api.JavaVersion
import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import org.gradle.integtests.fixtures.resolve.ResolveTestFixture
import org.gradle.integtests.resolve.locking.LockfileFixture
import org.gradle.util.GradleVersion
import spock.lang.Issue

import static org.gradle.integtests.fixtures.SuggestionsMessages.repositoryHint
Expand Down Expand Up @@ -1537,7 +1538,9 @@ org:leaf:[1.5,2.0] FAILED
project :A FAILED
Failures:
- Could not resolve project :A.
- A dependency was declared on configuration 'default' which is not declared in the descriptor for project :A.
Creating consumable variants is explained in more detail at https://docs.gradle.org/${GradleVersion.current().version}/userguide/declaring_dependencies.html#sec:resolvable-consumable-configs.
- Unable to find a matching variant of project :A:
- No variants exist.

project :A FAILED
\\--- conf
Expand All @@ -1551,7 +1554,9 @@ project :A FAILED
project :C FAILED
Failures:
- Could not resolve project :C.
- A dependency was declared on configuration 'default' which is not declared in the descriptor for project :C.
Creating consumable variants is explained in more detail at https://docs.gradle.org/${GradleVersion.current().version}/userguide/declaring_dependencies.html#sec:resolvable-consumable-configs.
- Unable to find a matching variant of project :C:
- No variants exist.

project :C FAILED
\\--- project :B
Expand Down