-
Notifications
You must be signed in to change notification settings - Fork 52
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
Gradle Convention Plugin to automatically add kover(...)
dependency and to automatically merge feature-module-filters
#715
Comments
Hi @OneFiveFour, first of all, thanks for this! I was stuck trying to create a plugin with the same purpose as yours, and your post really helped clarify some things. I tried to improve the code a bit, and I also added some stuff I needed for my project: class KoverPlugin : Plugin<Project> {
private val koverPluginId = "org.jetbrains.kotlinx.kover"
private val applicationModuleName = "application"
private val excludedModules = listOf("moduleA") // Modules you want to exclude manually
override fun apply(target: Project) {
if (target != target.rootProject) {
target.logger.info("KoverPlugin should be applied to the root project only. Current project: ${target.name}")
return
}
target.subprojects {
excludedModules.forEach {
if (name == getModuleByName(it)?.name) {
return@subprojects // Skip configuration if excluded module
}
}
if (buildFile.exists().not()) {
return@subprojects // Skip configuration if no build file, this to avoid dependencies with modules/directories that have no gradle config
}
target.logger.info("KoverPlugin applied to project: $name")
plugins.apply(koverPluginId) // Apply plugin to all modules with this we avoid to add the plugin manually and we can assure is being used in the whole project
}
// This was being executed inside allProjects, is not needed, because it should be executed a single time, not per each module
target.configureForKoverPlugin()
}
private fun Project.configureForKoverPlugin() {
val appModule = getModuleByName(applicationModuleName)
if (appModule == null) {
logger.warn("Application module '$applicationModuleName' was not found. Kover configuration skipped.")
return
}
val koverModules = getKoverModules(koverPluginId)
if (koverModules.isEmpty()) {
logger.warn("No Kover feature modules found.")
return
}
val configuration = appModule.getKoverConfiguration()
// Add each Kover module as a dependency and configure custom variant
koverModules.forEach { koverModule ->
// this was a special config for my project because i needed to use debug but application did not have a debug variant :)
koverModule.asKoverExtension()?.currentProject {
createVariant("custom") {
add("debug", optional = true)
}
}
appModule.addKoverDependency(configuration, koverModule)
}
// Apply filters once after all dependencies are added
appModule.asKoverExtension()?.collectAndApplyFilters()
}
private fun Project.getModuleByName(name: String): Project? {
return allprojects.find { it.name == name }
}
private fun Project.getKoverModules(koverPluginId: String): List<Project> {
return allprojects.filter { it.plugins.hasPlugin(koverPluginId) && it.name != rootProject.name && it.name != "application" }
}
private fun Project.getKoverConfiguration(): Configuration {
return configurations.getByName(KoverNames.configurationName)
}
private fun Project.asKoverExtension() = extensions.findByType(KoverProjectExtension::class.java)
private fun Project.addKoverDependency(configuration: Configuration, koverModule: Project) {
configuration.dependencies.add(dependencies.create(koverModule))
}
// Here I added the filters because I had them centralized in application instead of having them in each module or in application, so all Kover config is centralized here
private fun KoverProjectExtension.collectAndApplyFilters() {
reports {
filters {
includes {
...
}
excludes {
...
}
}
}
}
}
Hope this helps :) and thanks again! |
Hi, It is enough to use a special // in the root project
kover {
merge {
allProjects {
it.name !in excludedModules && it.buildFile.exists()
}
createVariant("custom") {
add("debug", optional = true)
}
}
reports {
filters {
// report filters
}
}
} |
@OneFiveFour, thank you for your efforts! It helps to understand what problems beginners face when working with Gradle and configuring plugins in it. In many ways, the functionality you specified duplicates the Also, let me give you a little feedback on your code, which may help you understand Gradle more. I would not recommend writing custom plugins based on the recommended method is precompiled convention plugins. Yes, it's not very convenient due to the lack of a single point of configuration. target.allprojects {
project.plugins.withId(koverPluginId) {
...
// add kover dependencies in app module
koverModules.forEach { koverModule ->
addKoverDependency(configuration, koverModule)
collectAndApplyFilters(
appModule.koverExtension(),
koverModule.koverExtension()
)
}
...
} this code will be processed as many times as there are projects in the build, this may have non-obvious consequences. The configuration must be performed once. project.plugins.withId(koverPluginId) {
...
val koverModules = project.rootProject.allprojects
.filter { it.plugins.hasPlugin(koverPluginId) } this is one of the non-obvious problems of cross-project calls, this approach is very sensitive to the configuration order of the projects in the build - first, the root project is configured, then the child subprojects are recursively configured. For example, at the time when the root project is configured, its subprojects have not yet been configured, respectively, plug-ins are not applied there, and As far as I understand, it is precisely because of this problem that in the previous example you use
I recommend not doing this, because this mixes two configuration methods: configuration in one place and configuration distributed across projects. There are no individual filters for each project in Kover, and the ability to specify a filter in a specific project may give the false impression that the classes of this project are filtered only by the filters specified in it. Also, if you make all the basic Kover settings in one place, then you should specify the filters in the same place. |
I have written a gradle convention plugin in Kotlin to be used in an mutlimodule Android project that does the following:
kover(project(":my:feature"))
dependency for each of these modules to the app.build.gradle to enable the merged/total report via./gradlew koverHtmlReportDebug
I am no expert in gradle, but it works. If anyone knows how to improve this script, feel free to comment. @shanshin Feel free to add this piece of code to the wiki or even add it to Kover.
So far only the classes filters can be read as
SetProperty
. Packages and sourceset are for some reason only writeable. Should that change, then they can be easily added incollectAndApplyFilters
.Add this to your build-logic module that contains Gradle Convention Plugins
Register the plugin in your /build-logic/build.gradle.kts
Apply this plugin in your root build.gradle
The text was updated successfully, but these errors were encountered: