Skip to content
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

support multiple android base packages #411

Merged
merged 1 commit into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021-2022 Rick Busarow
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package modulecheck.api.context

import modulecheck.parsing.gradle.SourceSetName
import modulecheck.project.AndroidMcProject
import modulecheck.project.McProject
import modulecheck.project.ProjectContext
import modulecheck.utils.SafeCache

data class AndroidBasePackages(
private val delegate: SafeCache<SourceSetName, String?>,
private val project: McProject
) : ProjectContext.Element {

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

suspend fun get(sourceSetName: SourceSetName): String? {
if (project !is AndroidMcProject) return null

return delegate.getOrPut(sourceSetName) {

// Note that this isn't just looking for a manifest file. It's looking for a manifest which
// has a defined base package. It's possible for a manifest to exist, but just add an
// Activity or something, if the package is already defined in an withUpstream source set.
sourceSetName
.withUpstream(project)
.firstNotNullOfOrNull { sourceSetOrUpstream ->

project.manifestFileForSourceSetName(sourceSetOrUpstream)?.basePackage
}
}
}

companion object Key : ProjectContext.Key<AndroidBasePackages> {
override suspend operator fun invoke(project: McProject): AndroidBasePackages {

return AndroidBasePackages(SafeCache(), project)
}
}
}

suspend fun ProjectContext.androidBasePackages(): AndroidBasePackages =
get(AndroidBasePackages)

suspend fun ProjectContext.androidBasePackagesForSourceSetName(
sourceSetName: SourceSetName
): String? = androidBasePackages().get(sourceSetName)
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ data class AndroidDataBindingDeclarations(

if (project !is AndroidMcProject) return emptyLazySet()

val basePackage = project.androidPackageOrNull ?: return emptyLazySet()

return delegate.getOrPut(sourceSetName) {

val basePackage = project.androidBasePackagesForSourceSetName(sourceSetName)
?: return@getOrPut emptyLazySet()

lazySet(
lazyDataSource {
project.layoutFiles()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2021-2022 Rick Busarow
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package modulecheck.api.context

import modulecheck.parsing.gradle.SourceSetName
import modulecheck.project.McProject
import modulecheck.project.ProjectContext
import modulecheck.utils.SafeCache

data class AndroidRFqNames(
private val delegate: SafeCache<SourceSetName, String?>,
private val project: McProject
) : ProjectContext.Element {

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

suspend fun get(sourceSetName: SourceSetName): String? {

return delegate.getOrPut(sourceSetName) {

project.androidBasePackagesForSourceSetName(sourceSetName)?.let { "$it.R" }
}
}

companion object Key : ProjectContext.Key<AndroidRFqNames> {
override suspend operator fun invoke(project: McProject): AndroidRFqNames {

return AndroidRFqNames(SafeCache(), project)
}
}
}

suspend fun ProjectContext.androidRFqNames(): AndroidRFqNames =
get(AndroidRFqNames)

suspend fun ProjectContext.androidRFqNameForSourceSetName(
sourceSetName: SourceSetName
): String? = androidRFqNames().get(sourceSetName)
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ data class AndroidResourceDeclarations(
val android = project as? AndroidMcProject
?: return@getOrPut emptyLazySet()

val rName = android.androidRFqNameOrNull ?: return@getOrPut emptyLazySet()
val rName = android.androidRFqNameForSourceSetName(sourceSetName)
?: return@getOrPut emptyLazySet()

val resourceParser = AndroidResourceParser()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ data class AndroidResourceReferences(

private suspend fun fetchNewReferences(sourceSetName: SourceSetName): LazySet<Reference> {

val androidRFqNameOrNull = (project as? AndroidMcProject)?.androidRFqNameOrNull
val androidRFqNameOrNull = (project as? AndroidMcProject)
?.androidRFqNameForSourceSetName(sourceSetName)
?: return emptyLazySet()

val packagePrefix = (project as? AndroidMcProject)
?.androidPackageOrNull
?.androidBasePackagesForSourceSetName(sourceSetName)
?.let { "$it." }

if (androidRFqNameOrNull == null || packagePrefix == null) {
return emptyLazySet()
}
?: return emptyLazySet()

val jvm = project.jvmFilesForSourceSetName(sourceSetName)
.map { jvmFile ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import kotlinx.coroutines.flow.toList
import modulecheck.parsing.gradle.SourceSetName
import modulecheck.parsing.source.DeclarationName
import modulecheck.parsing.source.asDeclarationName
import modulecheck.project.AndroidMcProject
import modulecheck.project.ConfiguredProjectDependency
import modulecheck.project.McProject
import modulecheck.project.ProjectContext
import modulecheck.project.isAndroid
import modulecheck.utils.LazySet
import modulecheck.utils.LazySet.DataSource
import modulecheck.utils.LazySet.DataSource.Priority.HIGH
import modulecheck.utils.SafeCache
import modulecheck.utils.dataSource
Expand All @@ -41,32 +41,30 @@ data class Declarations(
suspend fun get(sourceSetName: SourceSetName): LazySet<DeclarationName> {
return delegate.getOrPut(sourceSetName) {

val inheritedSourceSetsNames = sourceSetName.inheritedSourceSetNames(
project,
includeSelf = true
)
val sets = mutableListOf<LazySet<DeclarationName>>()
val sources = mutableListOf<DataSource<DeclarationName>>()

val rNameOrNull = (project as? AndroidMcProject)?.androidRFqNameOrNull
sourceSetName
.withUpstream(project)
.forEach { sourceSetOrUpstream ->

val sets = mutableListOf<LazySet<DeclarationName>>()
val rNameOrNull = project.androidRFqNameForSourceSetName(sourceSetOrUpstream)

val sources = inheritedSourceSetsNames
.flatMap { inherited ->
project.jvmFilesForSourceSetName(inherited)
project.jvmFilesForSourceSetName(sourceSetOrUpstream)
.toList()
.map { dataSource(HIGH) { it.declarations } }
}
.toMutableList()
.let { sources.addAll(it) }

if (rNameOrNull != null) {
sources.add(dataSource { setOf(rNameOrNull.asDeclarationName()) })
}
if (rNameOrNull != null) {
sources.add(dataSource { setOf(rNameOrNull.asDeclarationName()) })
}

if (project.isAndroid()) {
sets.add(project.androidResourceDeclarationsForSourceSetName(sourceSetName))
if (project.isAndroid()) {
sets.add(project.androidResourceDeclarationsForSourceSetName(sourceSetOrUpstream))

sets.add(project.androidDataBindingDeclarationsForSourceSetName(sourceSetName))
}
sets.add(project.androidDataBindingDeclarationsForSourceSetName(sourceSetOrUpstream))
}
}

lazySet(sets, sources)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ data class ManifestFiles(
if (project !is AndroidMcProject) return null

return delegate.getOrPut(sourceSetName) {
val file = project.manifests[sourceSetName]
?.existsOrNull()
?: return@getOrPut null

XmlFile.ManifestFile(file)
sourceSetName
.withUpstream(project)
.firstNotNullOfOrNull { sourceSetOrUpstream ->
project.manifests[sourceSetOrUpstream]
?.existsOrNull()
}
?.let { file -> XmlFile.ManifestFile(file) }
}
}

Expand All @@ -55,8 +57,4 @@ suspend fun ProjectContext.manifestFiles(): ManifestFiles = get(ManifestFiles)

suspend fun ProjectContext.manifestFileForSourceSetName(
sourceSetName: SourceSetName
): XmlFile.ManifestFile? = manifestFiles()
.get(sourceSetName)
?.takeIf { it.file.exists() }
?: manifestFiles().get(SourceSetName.MAIN)
?.takeIf { it.file.exists() }
): XmlFile.ManifestFile? = manifestFiles().get(sourceSetName)
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@

package modulecheck.core.rule

import modulecheck.api.context.androidBasePackagesForSourceSetName
import modulecheck.api.context.androidResourceReferencesForSourceSetName
import modulecheck.api.context.dependents
import modulecheck.api.context.importsForSourceSetName
import modulecheck.api.context.layoutFiles
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.gradle.all
import modulecheck.parsing.source.asExplicitReference
import modulecheck.project.AndroidMcProject
import modulecheck.project.McProject
import modulecheck.utils.capitalize
import modulecheck.utils.existsOrNull

class DisableViewBindingRule : ModuleCheckRule<DisableViewBindingGenerationFinding> {

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

val layouts = androidProject
.layoutFiles()
.all()
.all()

val dependents = project.dependents()

val basePackage = project.androidPackageOrNull
?: return listOf(
DisableViewBindingGenerationFinding(
dependentProject = project, dependentPath = project.path, buildFile = project.buildFile
)
)
project.sourceSets.keys
.forEach { sourceSetName ->

val usedLayouts = layouts
.filter { it.file.exists() }
.filter { layoutFile ->
val basePackage = project.androidBasePackagesForSourceSetName(sourceSetName)
?: return@forEach

val generated = layoutFile.file
.nameWithoutExtension
.split("_")
.joinToString("") { fragment -> fragment.capitalize() } + "Binding"
val generatedBindings = project.layoutFilesForSourceSetName(sourceSetName)
.mapNotNull { it.file.existsOrNull() }
.map { layoutFile ->
val simpleBindingName = layoutFile.nameWithoutExtension
.split("_")
.joinToString("") { fragment -> fragment.capitalize() } + "Binding"

val reference = "$basePackage.databinding.$generated".asExplicitReference()
// fully qualified
"$basePackage.databinding.$simpleBindingName".asExplicitReference()
}

val usedInProject = project
.importsForSourceSetName(SourceSetName.MAIN)
.contains(reference)
val usedInProject = sourceSetName.withDownStream(project)
.any { sourceSetNameOrDownstream ->

usedInProject || dependents
generatedBindings.any { generated ->

project.importsForSourceSetName(sourceSetNameOrDownstream)
.contains(generated)
}
}

if (usedInProject) return emptyList()

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

dep
.importsForSourceSetName(SourceSetName.MAIN)
.contains(reference) || dep
.androidResourceReferencesForSourceSetName(SourceSetName.MAIN)
.contains(reference)
generatedBindings.any { generated ->
dep
.importsForSourceSetName(SourceSetName.MAIN)
.contains(generated) || dep
.androidResourceReferencesForSourceSetName(SourceSetName.MAIN)
.contains(generated)
}
}

if (usedInDependent) return emptyList()
}
.toList()

return if (usedLayouts.isNotEmpty()) {
emptyList()
} else {
listOf(
DisableViewBindingGenerationFinding(
dependentProject = project, project.path, buildFile = project.buildFile
)

return listOf(
DisableViewBindingGenerationFinding(
dependentProject = project, dependentPath = project.path, buildFile = project.buildFile
)
}
)
}

override fun shouldApply(checksSettings: ChecksSettings): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class InheritedDependencyRule : ModuleCheckRule<InheritedDependencyFinding> {
return configurationName.toSourceSetName()
// Check the receiver's configuration first, but if the dependency isn't used there, also
// check the upstream configurations.
.inheritedSourceSetNames(project, includeSelf = true)
.withUpstream(project)
.any { sourceSet ->
dependencyPathsForSourceSet(sourceSet)
.contains(this.project.path to isTestFixture)
Expand Down
Loading