Skip to content

Commit

Permalink
If a variant cannot be selected because there are no variants, mentio… (
Browse files Browse the repository at this point in the history
  • Loading branch information
bot-gradle committed May 9, 2024
2 parents 9eea8e5 + 4b0e271 commit 7f8b155
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 22 deletions.
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

0 comments on commit 7f8b155

Please sign in to comment.