Skip to content

Commit

Permalink
fix false positive for disableViewBinding when used in debug source…
Browse files Browse the repository at this point in the history
… set of different module
  • Loading branch information
RBusarow authored and kodiakhq[bot] committed Mar 15, 2022
1 parent 6e091d8 commit 0cf0034
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,38 @@

package modulecheck.api.context

import kotlinx.coroutines.flow.toSet
import modulecheck.project.DownstreamDependency
import modulecheck.project.McProject
import modulecheck.project.ProjectContext
import modulecheck.utils.filterAsync
import modulecheck.utils.flatMapToSet

data class DependentProjects(
private val delegate: Set<McProject>
) : Set<McProject> by delegate,
data class DownstreamProjects(
private val delegate: Set<DownstreamDependency>
) : Set<DownstreamDependency> by delegate,
ProjectContext.Element {

override val key: ProjectContext.Key<DependentProjects>
override val key: ProjectContext.Key<DownstreamProjects>
get() = Key

companion object Key : ProjectContext.Key<DependentProjects> {
override suspend operator fun invoke(project: McProject): DependentProjects {
companion object Key : ProjectContext.Key<DownstreamProjects> {
override suspend operator fun invoke(project: McProject): DownstreamProjects {
val others = project.projectCache
.values
.filterAsync { otherProject ->
project.path in otherProject
.flatMapToSet { otherProject ->

otherProject
.classpathDependencies()
.all()
.map { it.contributed.project.path }
.filter { it.contributed.project == project }
.map { transitive ->
DownstreamDependency(
dependentProject = otherProject,
configuredProjectDependency = transitive.withContributedConfiguration().contributed
)
}
}
.toSet()

return DependentProjects(others)
return DownstreamProjects(others)
}
}
}
Expand All @@ -49,4 +55,4 @@ data class DependentProjects(
* All projects which are downstream of the receiver project, including those which only inherit via
* another dependency's `api` configuration without declaring the dependency directly.
*/
suspend fun ProjectContext.dependents(): DependentProjects = get(DependentProjects)
suspend fun ProjectContext.dependents(): DownstreamProjects = get(DownstreamProjects)
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import modulecheck.api.context.layoutFilesForSourceSetName
import modulecheck.api.rule.ModuleCheckRule
import modulecheck.api.settings.ChecksSettings
import modulecheck.core.rule.android.DisableViewBindingGenerationFinding
import modulecheck.parsing.gradle.SourceSetName
import modulecheck.parsing.source.asExplicitReference
import modulecheck.project.AndroidMcProject
import modulecheck.project.McProject
import modulecheck.utils.capitalize
import modulecheck.utils.existsOrNull
import modulecheck.utils.lazyDeferred

class DisableViewBindingRule : ModuleCheckRule<DisableViewBindingGenerationFinding> {

Expand All @@ -44,7 +44,7 @@ class DisableViewBindingRule : ModuleCheckRule<DisableViewBindingGenerationFindi
@Suppress("UnstableApiUsage")
if (!androidProject.viewBindingEnabled) return emptyList()

val dependents = project.dependents()
val dependents = lazyDeferred { project.dependents() }

project.sourceSets.keys
.forEach { sourceSetName ->
Expand Down Expand Up @@ -75,15 +75,26 @@ class DisableViewBindingRule : ModuleCheckRule<DisableViewBindingGenerationFindi

if (usedInProject) return emptyList()

// TODO -- this needs to be changed to respect the source sets of the downstream project
val usedInDependent = dependents
.any { dep ->
.await()
.any { downstream ->

// Get the source set which is exposed to the dependent project through its
// configuration. This will typically be `main`, but it could be another build variant
// if the entire dependency chain is using another source set's configuration.
val exposedSourceSetName = downstream.configuredProjectDependency
.declaringSourceSetName()

val imports = downstream.dependentProject
.importsForSourceSetName(exposedSourceSetName)

val resourceReferences = lazyDeferred {
downstream.dependentProject
.androidResourceReferencesForSourceSetName(exposedSourceSetName)
}

generatedBindings.any { generated ->
dep
.importsForSourceSetName(SourceSetName.MAIN)
.contains(generated) || dep
.androidResourceReferencesForSourceSetName(SourceSetName.MAIN)
imports.contains(generated) || resourceReferences.await()
.contains(generated)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import modulecheck.api.test.TestChecksSettings
import modulecheck.api.test.TestSettings
import modulecheck.parsing.gradle.ConfigurationName
import modulecheck.parsing.gradle.SourceSetName
import modulecheck.parsing.gradle.asConfigurationName
import modulecheck.runtime.test.ProjectFindingReport.disableViewBinding
import modulecheck.runtime.test.RunnerTest
import modulecheck.testing.createSafely
Expand All @@ -30,7 +31,7 @@ class DisableViewBindingTest : RunnerTest() {
override val settings by resets { TestSettings(checks = TestChecksSettings(disableViewBinding = true)) }

@Test
fun `used ViewBinding in dependent module with no changes`() {
fun `used ViewBinding from main sourceset in dependent module with no changes`() {

val lib1 = androidProject(":lib1", "com.modulecheck.lib1") {
buildFile {
Expand Down Expand Up @@ -92,6 +93,71 @@ class DisableViewBindingTest : RunnerTest() {
logger.parsedReport() shouldBe listOf()
}

@Test
fun `used ViewBinding from debug sourceset in dependent module with no changes`() {

val lib1 = androidProject(":lib1", "com.modulecheck.lib1") {
buildFile {
"""
plugins {
id("com.android.library")
kotlin("android")
}
android {
buildFeatures.viewBinding = true
}
"""
}
addLayoutFile(
"fragment_lib1.xml",
"""<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
""",
SourceSetName.DEBUG
)
}

androidProject(":lib2", "com.modulecheck.lib2") {
addDependency("debugImplementation".asConfigurationName(), lib1)
viewBindingEnabled = false

addSource(
"com/modulecheck/lib2/Source.kt",
"""
package com.modulecheck.lib2
import com.modulecheck.lib1.databinding.FragmentLib1Binding
private val binding = FragmentLib1Binding()
""",
SourceSetName.DEBUG
)
}

run(
autoCorrect = false,
).isSuccess shouldBe true

lib1.buildFile shouldHaveText """
plugins {
id("com.android.library")
kotlin("android")
}
android {
buildFeatures.viewBinding = true
}
"""

logger.parsedReport() shouldBe listOf()
}

@Test
fun `used ViewBinding in contributing module`() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ data class TransitiveProjectDependency(
val source: ConfiguredProjectDependency,
val contributed: ConfiguredProjectDependency
) {

fun withContributedConfiguration(
configurationName: ConfigurationName = source.configurationName
): TransitiveProjectDependency {
val newContributed = contributed.copy(configurationName = configurationName)
return copy(contributed = newContributed)
}

override fun toString(): String {
return """TransitiveProjectDependency(
| source=$source
Expand All @@ -64,3 +72,8 @@ data class TransitiveProjectDependency(
""".trimMargin()
}
}

data class DownstreamDependency(
val dependentProject: McProject,
val configuredProjectDependency: ConfiguredProjectDependency
)

0 comments on commit 0cf0034

Please sign in to comment.