Kotlin / kotlinx-kover

Apache License 2.0
1.28k stars 48 forks source link

How to apply kover in multimodule project #491

Closed massivemadness closed 8 months ago

massivemadness commented 8 months ago

How to apply kover to all modules in the project? I have a project with lots of modules, some of them have android plugin and some of them are pure kotlin modules.

I'm trying to use subprojects{} in top-level build.gradle.kts file like this:

plugins {
    ...
    id("org.jetbrains.kotlinx.kover") version "0.7.4"
}

subprojects {
    apply(plugin = "org.jetbrains.kotlinx.kover")

    koverReport {
        defaults {
            html {
                filters {
                    excludes {
                        ...
                    }
                }
            }
        }
    }
}

And I'm always getting an error when running $ ./gradlew koverHtmlReport

* What went wrong:
Some problems were found with the configuration of task ':benchmark:koverHtmlReport' (type 'KoverHtmlTask').
  - In plugin 'org.jetbrains.kotlinx.kover' type 'kotlinx.kover.gradle.plugin.tasks.reports.KoverHtmlTask' property 'filters' doesn't have a configured value.

    Reason: This property isn't marked as optional and no value has been configured.

    Possible solutions:
      1. Assign a value to 'filters'.
      2. Mark property 'filters' as optional.

    For more information, please refer to https://docs.gradle.org/8.4/userguide/validation_problems.html#value_not_set in the Gradle documentation.

+ same output for title and reportDir variables

After figuring this out I want to merge all generated reports into a single file, is there a better way to do this instead of manually adding kover(project(":dep1")) for every single dependency in every module? 🤔

shanshin commented 8 months ago

Hi, could you clarify, in the end you would like to get one merged report on all modules?

massivemadness commented 8 months ago

Hi @shanshin

in the end you would like to get one merged report on all modules

Exactly. I don't have enough knowledge of gradle api, but I guess something like this might work

subprojects {
    rootProject.dependencies { 
        kover(this@subprojects)
    }
}
shanshin commented 8 months ago

@massivemadness, to generate single report for all modules it is necessary to call the command only for this module, in which all dependencies are declared - in your case, this is a top-level module: ./gradlew :koverHtmlReport.

It is also need to configure the filtering of reports only in the top-level module.

As a result, it should turn out something like this:

subprojects {
    apply(plugin = "org.jetbrains.kotlinx.kover")
    rootProject.dependencies.add("kover", this)
}

koverReport {
    defaults {
        filters {
            excludes {
                classes("foo.bar.*")
                // ...
            }
        }
    }
}
massivemadness commented 8 months ago

@shanshin Thanks for the example, it works but as I already mentioned I have both android modules and pure kotlin modules. Running $ ./gradlew :koverHtmlReport generates a report only for kotlin modules, so the report is not complete. Is it possible to include android modules in the same report?

shanshin commented 8 months ago

Yes, you can also add to this report coverage from tests of one or more android build variants, see also the answer.

massivemadness commented 8 months ago

@shanshin Maybe I do something wrong, but I still see no reports from android modules. Is everything correct?

subprojects {
    apply(plugin = "org.jetbrains.kotlinx.kover")

    if (plugins.hasPlugin("com.android.library")) { // TODO com.android.application
        koverReport {
            defaults {
                mergeWith("fdroidDebug")
            }
        }
    }
    rootProject.dependencies.add("kover", this)
}

koverReport {
    defaults {
        // I can't use mergeWith() here, because it's top-level project without android plugin
    }
}
shanshin commented 8 months ago

@massivemadness, it is necessary to specify in the root project what is specified in this message:

subprojects {
    apply(plugin = "org.jetbrains.kotlinx.kover")
    rootProject.dependencies.add("kover", this)
}

koverReport {
    defaults {
        mergeWith("fdroidDebug")

        filters {
            excludes {
                classes("foo.bar.*")
                // ...
            }
        }
    }
}

In this case, mergeWith("fdroidDebug") means the following: create a merged report with JVM targets and fdroidDebug Android build variant from the current project, and from projects specified in the kover dependency

massivemadness commented 8 months ago

@shanshin I tried this way, it throws an error because root project doesn't have android build variants

Caused by: kotlinx.kover.gradle.plugin.commons.KoverIllegalConfigException: Build variant 'fdroidDebug' not found in project ':' - impossible to merge default reports with its measurements.
Available variations: []
    at kotlinx.kover.gradle.plugin.appliers.ProjectApplier$onApply$listener$1.onFinalize(ProjectApplier.kt:105)
    at kotlinx.kover.gradle.plugin.locators.CompilationsListenerWrapper.finalize(CompilationsListeners.kt:111)
    at kotlinx.kover.gradle.plugin.locators.CompilationsListenerWrapper.finalizeIfNoKotlinPlugin(CompilationsListeners.kt:103)
    at kotlinx.kover.gradle.plugin.locators.NoKotlinPluginLocatorKt$initNoKotlinPluginLocator$1.execute(NoKotlinPluginLocator.kt:12)
    at kotlinx.kover.gradle.plugin.locators.NoKotlinPluginLocatorKt$initNoKotlinPluginLocator$1.execute(NoKotlinPluginLocator.kt:11)
shanshin commented 8 months ago

root project doesn't have android build variants

Sorry, missed it.

Try these way:

subprojects {
    apply(plugin = "org.jetbrains.kotlinx.kover")
    pluginManager.withPlugin("com.android.library") {
        koverMerge("fdroidDebug")
    }
    pluginManager.withPlugin("com.android.application") {
        koverMerge("fdroidDebug")
    }

    rootProject.dependencies.add("kover", this)
}

fun Project.koverMerge(buildVariant: String) {
    koverReport {
        koverReport {
            defaults {
                mergeWith(buildVariant)
            }
        }
    }
}

and run ./gradlew :koverHtmlReport

massivemadness commented 8 months ago

@shanshin thank you so much! 🎉

0neel commented 3 months ago

For 0.7.6 I came up with a similar though a bit different solution:

import com.android.build.gradle.api.AndroidBasePlugin

subprojects {
  apply plugin: "org.jetbrains.kotlinx.kover"

  plugins.withType(AndroidBasePlugin) {
    configurations.implementation {
      dependencies.withType(ProjectDependency) {
        project.dependencies.kover dependencyProject
      }
    }
  }
}

It configures Kover for every Gradle module and makes them include coverage from all of their dependencies.

rbrauwers commented 2 months ago

One thing to be considered in the solution above is that the project can have api dependencies.

subprojects {
  apply plugin: "org.jetbrains.kotlinx.kover"

  plugins.withType(AndroidBasePlugin) {
    configurations.implementation {
      dependencies.withType(ProjectDependency) {
        project.dependencies.kover dependencyProject
      }
    }

    configurations.api {
      dependencies.withType(ProjectDependency) {
        project.dependencies.kover dependencyProject
      }
    }
  }
}

cc @0neel