blob: 0a1b9860fcaa2d1049673cbe7130abe7dec79f45 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.android.build.gradle.tasks
import com.android.SdkConstants
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.AnchorOutputType
import com.android.build.gradle.internal.scope.BuildArtifactsHolder
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.tasks.NonIncrementalTask
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.builder.core.VariantTypeImpl
import com.google.common.annotations.VisibleForTesting
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import java.io.File
// TODO: Make incremental
abstract class AnalyzeDependenciesTask : NonIncrementalTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
lateinit var variantArtifact: Provider<FileCollection>
private set
private lateinit var externalArtifactCollection: ArtifactCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val externalArtifacts: FileCollection by lazy { externalArtifactCollection.artifactFiles }
@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty
// Don't need to be marked as input as they are represented in externalArtifacts
private var apiDirectDependenciesConfiguration: Configuration? = null
private lateinit var allDirectDependencies: Collection<Dependency>
private var isVariantLibrary: Boolean? = null
override fun doTaskAction() {
val variantDepsHolder = VariantDependenciesHolder(
allDirectDependencies,
apiDirectDependenciesConfiguration?.allDependencies)
val variantClassHolder = VariantClassesHolder(variantArtifact.get())
val classFinder = ClassFinder(externalArtifactCollection)
val depsUsageFinder =
DependencyUsageFinder(classFinder, variantClassHolder, variantDepsHolder)
val compileClasspathConfig = project.configurations.getAt("${variantName}CompileClasspath")
if (compileClasspathConfig.isCanBeResolved) {
val graphAnalyzer = DependencyGraphAnalyzer(
compileClasspathConfig,
depsUsageFinder)
val reporter = DependencyUsageReporter(
variantClassHolder,
variantDepsHolder,
classFinder,
depsUsageFinder,
graphAnalyzer)
reporter.writeUnusedDependencies(
File(
outputDirectory.asFile.get(),
"dependenciesReport.json"))
// Report misconfigured dependencies only for library modules
if (isVariantLibrary == true) {
reporter.writeMisconfiguredDependencies(
File(outputDirectory.asFile.get(), "apiToImplementation.json")
)
}
}
}
class VariantDependenciesHolder(
_directAllDependencies: Collection<Dependency>,
_directApiDependencies: Collection<Dependency>?) {
val all = getDependenciesIds(_directAllDependencies)
val api = getDependenciesIds(_directApiDependencies)
private fun getDependenciesIds(dependencies: Collection<Dependency>?) =
dependencies?.mapNotNull { buildDependencyId(it) }?.toSet() ?: emptySet()
@VisibleForTesting
internal fun buildDependencyId(dependency: Dependency): String? {
if (dependency.group == null) {
return null
}
var id = "${dependency.group}:${dependency.name}"
if (dependency.version != null) {
id += ":${dependency.version}"
}
return id
}
}
class VariantClassesHolder(private val variantArtifact: FileCollection) {
private enum class CLASS_TYPE { ALL, PUBLIC }
private val analyzer = DependenciesAnalyzer()
private val classesByType: Map<CLASS_TYPE, Set<String>> by lazy {
val classesUsedInVariant = mutableSetOf<String>()
val classesExposedByPublicApis = mutableSetOf<String>()
variantArtifact.files.forEach { file ->
file.walk().forEach { classFile ->
val name = classFile.name
if (classFile.isFile && name.endsWith(SdkConstants.DOT_CLASS)) {
classesUsedInVariant.addAll(
analyzer.findAllDependencies(classFile.inputStream()))
classesExposedByPublicApis.addAll(
analyzer.findPublicDependencies(classFile.inputStream()))
}
}
}
mapOf(
CLASS_TYPE.ALL to classesUsedInVariant,
CLASS_TYPE.PUBLIC to classesUsedInVariant.minus(classesExposedByPublicApis))
}
/** Returns classes used inside our variant code. */
fun getUsedClasses() = classesByType[CLASS_TYPE.ALL] ?: emptySet()
/** Returns classes not exposed in any public method/fields/etc in our variant code. */
fun getPublicClasses() = classesByType[CLASS_TYPE.PUBLIC] ?: emptySet()
}
class CreationAction(val scope: VariantScope) :
VariantTaskCreationAction<AnalyzeDependenciesTask>(scope) {
override val name: String
get() = scope.getTaskName("analyze", "Dependencies")
override val type: Class<AnalyzeDependenciesTask>
get() = AnalyzeDependenciesTask::class.java
override fun configure(task: AnalyzeDependenciesTask) {
super.configure(task)
task.variantArtifact = scope.artifacts
.getFinalProductAsFileCollection(AnchorOutputType.ALL_CLASSES)
task.externalArtifactCollection = scope
.getArtifactCollection(
AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
AndroidArtifacts.ArtifactScope.ALL,
AndroidArtifacts.ArtifactType.CLASSES)
task.apiDirectDependenciesConfiguration = scope
.variantDependencies
.getElements(AndroidArtifacts.PublishedConfigType.API_ELEMENTS)
task.allDirectDependencies = scope
.variantDependencies
.incomingRuntimeDependencies
task.isVariantLibrary = (scope.type == VariantTypeImpl.LIBRARY)
}
override fun handleProvider(taskProvider: TaskProvider<out AnalyzeDependenciesTask>) {
super.handleProvider(taskProvider)
variantScope.artifacts.producesDir(
artifactType = InternalArtifactType.ANALYZE_DEPENDENCIES_REPORT,
operationType = BuildArtifactsHolder.OperationType.INITIAL,
taskProvider = taskProvider,
productProvider = AnalyzeDependenciesTask::outputDirectory,
fileName = "analyzeDependencies")
}
}
}