How to force variant selection withResolvedConfiguration.getResolvedArtifacts? - gradle

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

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

Classes in bundle 'app' do not match with execution data - Android

Although similar questions are already present on internet, but I am unable to find any solution to this problem with regards to android platform. My project has multiple product flavors, uses kotlin and hilt. I believe byte code transformation while compiling the project is the root cause of disrupture.
I first thought probably Hilt is injecting code inside classes, therefore I made sure to copy classes before Hilt tasks execution into a separate folder, then use those classes as source for jacoco. But it didn't work.
Error
[ant:jacocoReport] Classes in bundle 'app' do not match with execution data. For report generation the same class files must be used as at runtime.
[ant:jacocoReport] Execution data for class com/company/myapp/Data$callApi$1 does not match.
[ant:jacocoReport] Execution data for class com/company/myapp/factory/SomeFactory$SomeGenerator does not match.
and the list continues for whole bunch of classes in the app. Due to these errors, code coverage is always zero although there are bunch of unit test already written in the app.
gradle.projectsEvaluated {
def hiltTaskPattern = ~/\bhilt.*\w+FlavorADebug\b/
def tasksList = getSubprojects()
.collect { it.tasks }
.flatten()
def copyFilesTask = tasksList.find { it.name == "copyClassFilesForJacoco" }
if (copyFilesTask != null) {
tasksList.findAll { hiltTaskPattern.matcher(it.name).matches() }
.each { it.dependsOn copyFilesTask }
}
}
task copyClassFilesForJacoco(dependsOn: "compileDebugJavaWithJavac", type: Copy) {
def javaDebugTree = fileTree(dir: "${buildDir}/intermediates/javac/flavorADebug/classes", excludes: androidFilesExcluded)
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/flavorADebug", excludes: androidFilesExcluded)
from javaDebugTree, kotlinDebugTree
into layout.buildDirectory.dir(classFilesPathForInstrumentation)
doLast {
println("Files copied to ${classFilesPathForInstrumentation}")
}
}
Then in the testCoverage task of type JacocoReport, classDirectories point out to copied files
Also, kotlinx-kover seemed interesting, but it's in pre-mature state and lacks support of multiple product flavors along with outdated documentation which makes its usage unfavourable.
This answer explains very well reason for the problem, and also provide potential solution. But it is old, and not applicable to android project since it uses java plugin and jacoco-ant agent which is not compatible with android.
Can someone guide towards potential solutions for the aforementioned problem? TIA

Gradle distribution plugin: conditionally copy assets

I'm packaging my java application using Gradle's Distribution plugin. I wanted to make 2 distributions, one which doesn't include a JRE and another one that bundles a JRE with the app.
I've set up a copyJre task and wanted to only make Distributions plugin include a folder (jre-8 in the example below) only when copyJre task is in the tasks graph. Here's my attempt which doesn't work.
distributions {
main {
contents {
from('/') {
include 'tools/**'
}
// my attempt to conditionally copy
// jre-8 directory only when tasks graph contains
// a task named 'copyJre'
if (tasks.findByName('copyJre') != null) {
from('../../jre-dist/') {
include 'jre-8/**'
}
}
}
}
}
There probably should be a better approach in general. This looks like kludges.
From a Gradle perspective, you are better expressing what you need the other way around:
Create a different distribution that will include the JRE, possibly extracting the common part of the copy spec.
And if you really only want a single output, make it replace the default distribution after building it.

Can a build template be based on another build template on TeamCity?

I am using TeamCity 9.0.2, and I would like to make a template implement another template, or make a build configuration implement more than one template.
Can this be achieved?
This was not available when you asked the question but since Team City 10 you can now use Kotlin to configure your builds and thus your templates.
From this you can make Templates implement other Templates.
I myself have made Templates inherit from other templates to cut down on reconfiguration time and to not have to repeat myself so many times.
open class TheBaseTemplate(uuidIn: String, extIdIn: String, nameIn: String, additionalSettings: Template.() -> Unit) : Template({
uuid = uuidIn
extId = extIdIn
name = nameIn
/* all the other settings that are the same for the derived templates*/
additionalSettings()
})
object DerivedTemplateA : TheBaseTemplate("myUuidA", "myExtIdA", "myNameA", {
params {
param("set this", "to this")
}
})
object DerivedTemplateB : TheBaseTemplate("myUuidB", "myExtIdB", "myNameB", {
params {
param("set this", "to that")
}
})
object Project : Project({
uuid = "project uuid"
extId = "project extid"
name = "project name"
buildType {
template(DerivedTemplateA)
/* the uuid, extId and name are set here */
}
buildType {
template(DerivedTemplateB)
/* the uuid, extId and name are set here */
}
template(DerivedTemplateA)
template(DerivedTemplateB)
})
The above code might be very hard to understand. It will take some time to familiarise yourself with Kotlin, what it does, and how it interfaces with TeamCity. I should point out that some imports are missing.
Additionally, take the example with a pinch of salt. It is a quick example to demonstrate one way of templates implementing other templates. Do not take this example as the definitive way to do things.
Unfortunately, this is currently not possible but already requested for a long time in TW-12153 (maybe you would like to vote for it).
To share several build steps among several build configurations or build configuration templates, I am using meta runners:
A Meta-Runner allows you to extract build steps, requirements and parameters from a build configuration and create a build runner out of them.
This build runner can then be used as any other build runner in a build step of any other build configuration or template.
Although using meta runners works as a workaround for us, editing meta runners is not as convenient as editing a build configuration template (as it usually requires editing the meta runner definition XML file by hand).
Update 2021
As #zosal points out in his answer TeamCity meanwhile provides another way of sharing common build configuration data or logic by means of the Kotlin DSL. The Kotlin DSL is a very powerful tool but may not always fit in your specific scenario. I would recommend to at least give it a try or watch one of the introductory tutorial videos.

Gradle - how to set up-to-date parameters on a predefined task?

I could really use some help with this!
The gradle docs say that to make the up-to-date logic to function, just do this:
task transform {
ext.srcFile = file('mountains.xml')
ext.destDir = new File(buildDir, 'generated')
inputs.file srcFile
outputs.dir destDir
This is all well and good for tasks you are defining. However, I am using the eclipse plugin to do some modification to the .classpath file. Up-to-date does not work. That is, it runs the task over and over again out of the box (at least for me). Here is what I have:
eclipse {
classpath {
//eclipseClasspath.inputs.file // something like this??? but what to set it to?
//eclipseClasspath.outputs.file // here too
file {
withXml {
def node = it.asNode()
// rest of my stuff here
I tried a couple of things where I have the two commented out lines. Since those didn't work, I realized I didn't really have a clue and could use some help! Thanks in advance!
In my experience, the Eclipse tasks should not rerun every single time. That makes me think that you are doing something to cause either the inputs or outputs to change. If you are modifying your Eclipse project after Gradle generates it or changing dependencies, etc, you would naturally be triggering the upToDate checks.
If you really do need to force it to run every time, you might be able to get it to work with this. I'm not sure if I've ever tried using this when other outputs are already defined.
eclipseClasspath {
outputs.upToDateWhen { true } //there isn't an equivalent for inputs
}
One important note is that what you were using is the Eclipse model that describes your project, not the actual task itself:
eclipse { //this is the eclipse model
classpath {
}
}
eclipseClasspath {
//this is a task
}
eclipseProject {
//this is a task
}

Dynamically configuring a task in a parent build.gradle

I have a multi-project C++ Gradle build, which produces a number of libraries and executables. I'm trying to get the executables (but not the libraries) subprojects to get compiled in with a 'fingerprint' object. This works fine if I sprinkle smth like this in individual subprojects' build.gradle:
compileMain.doFirst {
// code to generate a 'BuildInfo.cpp' from from a template.
// embeds name of executable in so has to be generated anew for each exe
}
Following DRY principles, I'd much rather do this once and for all in a top level build.gradle. This is my attempt, to apply it to just the subprojects that use the cpp-exe plugin, following these instructions:
configure(subprojects.findAll { it.plugins.hasPlugin('cpp-exe') }) {
compileMain.doFirst {
// same code as above
}
}
Alas, this doesn't get triggered. However, if I put smth like this in a less restrictive configure, block, this demonstrates that the idea of querying the plugin should work:
configure(subprojects.findAll { true }) {
task mydebug << {
if ( project.plugins.hasPlugin( 'cpp-exe' ) ) {
println ">>> $project.name has it!"
}
}
}
Could it be that the plugins don't get applied to the subprojects at the time the configure closure is evaluated (in the top-level build.gradle)? There may well be a much simpler way of achieving this altogether?
You probably apply the cpp-exe plugin in the child projects' build scripts. By default, a parent build script gets evaluated before its children, which explains why it's not finding any projects that have cpp-exe applied.
There are several ways to solve this problem. One way is to move all configuration that's specific to a cpp-exe project (like applying the plugin and adding the action) to the same spot. Either you do all such configuration from the parent build script (for example by enumerating the cpp-exe subprojects and configuring them with a single configure(cppExeProjects) { ... }), or you move the cpp-exe specific configuration into its own build script (say gradle/cpp-exe.gradle) and apply it from selected subprojects like so: apply from: "$rootDir/gradle/cpp-exe.gradle".
Another solution is to change the evaluation order of build scripts. But I would only use this as a last resort, and it is certainly not necessary here.
Gradle 1.5 is recently out, I am not sure if this is a new feature but as it looks, you can solve the issue by using afterEvaluate.
Take a look at section 53.6.1 in http://www.gradle.org/docs/current/userguide/build_lifecycle.html
Something like:
subprojects {subProject ->
afterEvaluate {
if ( subProject.plugins.hasPlugin('cpp-exe')){
println "Project $subProject.name has plugin cpp-exe"
}
}
}
would give you a start.

Resources