Home > Software engineering >  How to force variant selection withResolvedConfiguration.getResolvedArtifacts?
How to force variant selection withResolvedConfiguration.getResolvedArtifacts?

Time:05-31

I'm working with a large, multi-module Android application, and I'm trying to define a Gradle task that collects the jars of all runtime dependencies. I'm trying something like this in app/build.gradle:

task collectDeps {
    doLast {
        configurations.releaseRuntimeClasspath.resolvedConfiguration.resolvedArtifacts.each {
                // do stuff
        }
    }
}

I've used this snippet in the past on other Java projects, so I know that it conceptually works; this is just my first time trying it on a project with multiple build types and/or variants.

When run on the Android project, executing this task throws a variant resolution error:

Execution failed for task ':app:collectDeps'.
> Could not resolve all dependencies for configuration ':app:releaseRuntimeClasspath'.
   > The consumer was configured to find a runtime of a component, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release', attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '7.1.1', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm'. However we cannot choose between the following variants of project :myModule:
       - Configuration ':myModule:releaseRuntimeElements' variant android-aar-metadata declares a runtime of a component, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '7.1.1', attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
           - Unmatched attributes:
               - Provides attribute 'artifactType' with value 'android-aar-metadata' but the consumer didn't ask for it
               - Provides attribute 'com.android.build.gradle.internal.attributes.VariantAttr' with value 'release' but the consumer didn't ask for it
               - Provides a library but the consumer didn't ask for it
       - Configuration ':myModule:releaseRuntimeElements' variant android-art-profile declares a runtime of a component, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '7.1.1', attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
           - Unmatched attributes:
               - Provides attribute 'artifactType' with value 'android-art-profile' but the consumer didn't ask for it
               - Provides attribute 'com.android.build.gradle.internal.attributes.VariantAttr' with value 'release' but the consumer didn't ask for it
               - Provides a library but the consumer didn't ask for it

I've trimmed the error for brevity; there are ~20 variants in total. Note that myModule is a project dependency of the top-level app; if I remove that dependency, the error is the same but comes from a different module.

I should also note here that every other build target works fine; the application is quite mature, and the only change I've made is to add this new task to app/build.gradle. So I assume there's something about the way I'm resolving the dependencies that Gradle doesn't like, but I'm struggling to figure out what, or how to resolve it.

Googling this error is not very helpful; the Gradle documentation is quite vague about exactly how to resolve variants, and the solutions that are provided seem to focus on changing how dependencies are added to the project; but I don't necessarily want to do that, because the build works fine for every other use case.

Ideally, I'd like to be able to force a variant for the resolution specifically within my collectDeps task (in fact, ideally collectDeps would be defined in a plugin). Is this possible to do?

In case it matters, the build is using Gradle 7.2 and v7.1.1 of the Android Gradle Plugin

CodePudding user response:

There may be a better way to handle this, but I ultimately managed to resolve my problem by taking inspiration from Sonatype's open source Nexus scanning plugin. The code looks like (this is in Kotlin, but can be modified to Groovy without much difficulty):

project.allprojects.forEach { project ->
    val cfg = project.configurations.releaseRuntimeClasspath
     try {
        cfg.resolvedConfiguration.resolvedArtifacts.forEach {
            // do stuff
        }
     } catch(e: Exception) {
        when(e) {
            is ResolveException, is AmbiguousVariantSelectionException -> {
                val copyConfiguration = createCopyConfiguration(project)

                cfg.allDependencies.forEach {
                    if(it is ProjectDependency) {
                        project.evaluationDependsOn(it.dependencyProject.path)
                    } else {
                        copyConfiguration.dependencies.add(it)
                    }
                }

                copyConfiguration.resolvedConfiguration.resolvedArtifacts.forEach {
                    // do stuff
                }
            }
            else -> throw(e)
        }
     }
}

private fun createCopyConfiguration(project: Project): Configuration {
    var configurationName = "myCopyConfiguration"

    var i = 0
    while(project.configurations.findByName(configurationName) != null) {
        configurationName  = i
        i  
    }

    val copyConfiguration = project.configurations.create(configurationName)
    copyConfiguration.attributes {
        val factory = project.objects
        this.attribute(Usage.USAGE_ATTRIBUTE, factory.named(Usage::class.java, Usage.JAVA_RUNTIME))
    }

    return copyConfiguration
}

The basic idea is that, if a configuration can't be resolved because of ambiguous variant selection, I create and inject a new parent configuration that specifies the attribute org.gradle.usage='java-runtime'; this is sufficient to disambiguate the variants.

Note that I didn't test this with any other attributes, so it's possible that it could work by setting, for example, the artifactType attribute instead; but my use case is more specifically related to the runtime classpath, so this worked for me

  • Related