com.grab.grazel.gradle.dependencies.Dependencies.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grazel-gradle-plugin Show documentation
Show all versions of grazel-gradle-plugin Show documentation
A Gradle plugin to automate Bazel migration for Android projects
The newest version!
/*
* Copyright 2022 Grabtaxi Holdings PTE LTD (GRAB)
*
* 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 com.grab.grazel.gradle.dependencies
import com.grab.grazel.GrazelExtension
import com.grab.grazel.bazel.rules.ANDROIDX_GROUP
import com.grab.grazel.bazel.rules.ANNOTATION_ARTIFACT
import com.grab.grazel.bazel.rules.DAGGER_GROUP
import com.grab.grazel.bazel.rules.DATABINDING_GROUP
import com.grab.grazel.bazel.starlark.BazelDependency
import com.grab.grazel.bazel.starlark.BazelDependency.MavenDependency
import com.grab.grazel.bazel.starlark.BazelDependency.StringDependency
import com.grab.grazel.gradle.ConfigurationDataSource
import com.grab.grazel.gradle.ConfigurationScope
import com.grab.grazel.gradle.ConfigurationScope.TEST
import com.grab.grazel.gradle.configurationScopes
import com.grab.grazel.gradle.hasDatabinding
import com.grab.grazel.gradle.variant.AndroidVariant
import com.grab.grazel.gradle.variant.AndroidVariantsExtractor
import com.grab.grazel.gradle.variant.DEFAULT_VARIANT
import com.grab.grazel.gradle.variant.TEST_VARIANT
import com.grab.grazel.gradle.variant.Variant
import com.grab.grazel.gradle.variant.VariantBuilder
import com.grab.grazel.gradle.variant.isConfigScope
import com.grab.grazel.gradle.variant.isTest
import com.grab.grazel.gradle.variant.migratableConfigurations
import com.grab.grazel.util.GradleProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.internal.artifacts.DefaultResolvedDependency
import java.io.File
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
/**
* Maven group names for artifacts that should be excluded from dependencies calculation everywhere.
*/
internal val IGNORED_ARTIFACT_GROUPS = listOf(
"com.android.tools.build",
"org.jetbrains.kotlin"
)
/**
* Simple data holder for a Maven artifact containing its group, name and version.
*/
internal data class MavenArtifact(
val group: String?,
val name: String?,
val version: String? = null,
) : Comparable {
val id get() = "$group:$name"
override fun compareTo(other: MavenArtifact): Int {
return toString().compareTo(other.toString())
}
override fun toString() = "$group:$name:$version"
}
internal data class ArtifactsConfig(
val excludedList: List = emptyList(),
val ignoredList: List = emptyList()
)
internal interface DependenciesDataSource {
/**
* Return the project's project (module) dependencies before the resolution strategy and any other custom
* substitutions by Gradle
*/
fun projectDependencies(
project: Project,
vararg scopes: ConfigurationScope
): Sequence>
/**
* @return true if the project has any private dependencies in any configuration
*/
@Deprecated("No longer supported")
fun hasDepsFromUnsupportedRepositories(project: Project): Boolean
/**
* Verify if the project has any dependencies that are meant to be ignored. For example, if the [Project] uses any
* dependency that was excluded via [GrazelExtension] then this method will return `true`.
*
* @param project the project to check against.
*/
@Deprecated("No longer supported")
fun hasIgnoredArtifacts(project: Project): Boolean
/**
* Returns map of [MavenArtifact] and the corresponding artifact file (aar or jar). Guarantees the
* returned file is downloaded and available on disk
*
* @param rootProject The root project instance
* @param fileExtension The file extension to look for. Use this to reduce the overall number of
* values returned
*/
fun dependencyArtifactMap(
rootProject: Project,
fileExtension: String? = null
): Map
/**
* Non project dependencies for the given [buildGraphType]
*/
fun collectMavenDeps(
project: Project,
buildGraphType: BuildGraphType
): List
/**
* Collects all transitive maven dependencies for the given [buildGraphType]. Similar to
* [collectMavenDeps]
*/
fun collectTransitiveMavenDeps(
project: Project,
buildGraphType: BuildGraphType
): Set
}
@Singleton
internal class DefaultDependenciesDataSource @Inject constructor(
private val grazelExtension: GrazelExtension,
private val configurationDataSource: ConfigurationDataSource,
private val artifactsConfig: ArtifactsConfig,
private val dependencyResolutionService: GradleProvider,
private val androidVariantsExtractor: AndroidVariantsExtractor,
private val variantBuilder: VariantBuilder,
) : DependenciesDataSource {
private val configurationScopes by lazy { grazelExtension.configurationScopes() }
private fun Project.buildGraphTypes() =
configurationScopes.flatMap { configurationScope ->
androidVariantsExtractor.getVariants(this).map { variant ->
BuildGraphType(configurationScope, variant)
}
}
/**
* @return `true` when the `MavenArtifact` is present is ignored by user.
*/
private val MavenArtifact.isIgnored get() = artifactsConfig.ignoredList.contains(id)
/**
* @return `true` when the `MavenArtifact` is present is excluded by user.
*/
private val MavenArtifact.isExcluded get() = artifactsConfig.excludedList.contains(id)
override fun hasDepsFromUnsupportedRepositories(project: Project): Boolean {
return false
}
override fun hasIgnoredArtifacts(project: Project): Boolean {
return project.firstLevelModuleDependencies()
.flatMap { (listOf(it) + it.children).asSequence() }
.filter { it.moduleGroup !in IGNORED_ARTIFACT_GROUPS }
.any { MavenArtifact(it.moduleGroup, it.moduleName).isIgnored }
}
override fun projectDependencies(
project: Project, vararg scopes: ConfigurationScope
) = declaredDependencies(project, *scopes)
.filter { it.second is ProjectDependency }
.map { it.first to it.second as ProjectDependency }
override fun dependencyArtifactMap(
rootProject: Project,
fileExtension: String?
): Map {
val results = mutableMapOf()
rootProject.subprojects
.asSequence()
.flatMap { project ->
variantBuilder.build(project)
.asSequence()
.filterIsInstance()
.filter { !it.variantType.isTest }
}.flatMap { it.compileConfiguration }
.flatMapTo(TreeSet(compareBy { it.id.toString() })) { configuration ->
configuration
.incoming
.artifactView {
isLenient = true
componentFilter { identifier -> identifier is ModuleComponentIdentifier }
}.artifacts
}.asSequence()
.filter { it.file.extension == fileExtension }
.forEach { artifactResult ->
val artifact = artifactResult.id.componentIdentifier as ModuleComponentIdentifier
results.getOrPut(
MavenArtifact(
group = artifact.group,
name = artifact.module,
version = artifact.version,
)
) { artifactResult.file }
}
return results
}
override fun collectMavenDeps(
project: Project,
buildGraphType: BuildGraphType
): List {
val grazelVariant: Variant<*> = findGrazelVariant(project, buildGraphType)
return grazelVariant.migratableConfigurations
.asSequence()
.flatMap { it.allDependencies.filterIsInstance() }
.filter { it.group !in IGNORED_ARTIFACT_GROUPS }
.filter {
val artifact = MavenArtifact(it.group, it.name)
!artifact.isExcluded && !artifact.isIgnored
}.filter {
if (project.hasDatabinding) {
it.group != DATABINDING_GROUP && (it.group != ANDROIDX_GROUP && it.name != ANNOTATION_ARTIFACT)
} else true
}.map { dependency ->
val variantHierarchy = buildSet {
add(grazelVariant.name)
addAll(grazelVariant.extendsFrom.reversed())
}
when (dependency.group) {
DAGGER_GROUP -> StringDependency("//:dagger")
else -> dependencyResolutionService.get().get(
variants = variantHierarchy,
group = dependency.group!!,
name = dependency.name!!
) ?: run {
error("$dependency cant be found for migrating ${project.name}")
}
}
}.distinct()
.toList()
}
override fun collectTransitiveMavenDeps(
project: Project,
buildGraphType: BuildGraphType
): Set {
val grazelVariant: Variant<*> = findGrazelVariant(project, buildGraphType)
return grazelVariant.migratableConfigurations
.asSequence()
.map { it.incoming.resolutionResult.root }
.flatMapTo(TreeSet()) { rootNode ->
// Using ResolvedComponentsVisitor is redundant here since the work would
// already be done by the time we reach here by resolveVariantDependenciesTask.
// But for simplicity we visit components again.
// TODO(arun) Optimize this and read resolved components as Task input.
ResolvedComponentsVisitor().visit(rootNode) { (component, _, _, _) ->
val version = component.moduleVersion!!
MavenArtifact(
group = version.group,
name = version.name,
)
}
}.map { MavenDependency(group = it.group!!, name = it.name!!) }
.toSortedSet()
}
private fun findGrazelVariant(
project: Project,
buildGraphType: BuildGraphType
): Variant<*> {
val variants = variantBuilder.build(project).groupBy(Variant<*>::name)
val inputVariant = buildGraphType.variant
// From input variant map to Grazel variant
val grazelVariant: Variant<*> = when {
inputVariant != null -> variants[inputVariant.name]!!.first {
it.variantType.isConfigScope(project, buildGraphType.configurationScope)
}
// Input variant is null, probably legacy code path assumes non android project has no
// variants. To compensate, map it to `default` or `test` based on
// BuildGraphType.configurationScope
// This will no longer needed after migrating legacy code path to use variants
else -> when (buildGraphType.configurationScope) {
TEST -> variants[TEST_VARIANT]!!.first()
else -> variants[DEFAULT_VARIANT]!!.first()
}
}
return grazelVariant
}
/**
* Collects first level module dependencies from their resolved configuration. Additionally, excludes any artifacts
* that are not meant to be used in Bazel as defined by [IGNORED_ARTIFACT_GROUPS]
*
* @return Sequence of [DefaultResolvedDependency] in the first level
*/
private fun Project.firstLevelModuleDependencies(
buildGraphTypes: List = buildGraphTypes()
): Sequence {
return configurationDataSource
.resolvedConfigurations(
project = this,
buildGraphTypes = buildGraphTypes.toTypedArray()
).map { it.resolvedConfiguration.lenientConfiguration }
.flatMap {
try {
it.firstLevelModuleDependencies.asSequence()
} catch (e: Exception) {
sequenceOf()
}
}.filterIsInstance()
.filter { it.moduleGroup !in IGNORED_ARTIFACT_GROUPS }
}
internal fun firstLevelModuleDependencies(project: Project) =
project.firstLevelModuleDependencies()
/**
* Collects dependencies from all available configuration in the pre-resolution state i.e without dependency resolutions.
* These dependencies would be ideally used for sub targets instead of `WORKSPACE` file since they closely mirror what
* was defined in `build.gradle` file.
*
* @return Sequence of `Configuration` and `Dependency`
*/
private fun declaredDependencies(
project: Project,
vararg scopes: ConfigurationScope
): Sequence> {
return configurationDataSource.configurations(project, *scopes)
.flatMap { configuration ->
configuration
.dependencies
.asSequence()
.map { dependency -> configuration to dependency }
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy