blob: 67f534d445b50d77b773b8a1afc884f67732372d [file] [log] [blame]
/*
* Copyright (C) 2018 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.internal.tasks
import com.android.build.gradle.internal.component.ApkCreationConfig
import com.android.build.gradle.internal.component.DynamicFeatureCreationConfig
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.android.tools.build.libraries.metadata.AppDependencies
import com.android.tools.build.libraries.metadata.Library
import com.android.tools.build.libraries.metadata.LibraryDependencies
import com.android.tools.build.libraries.metadata.MavenLibrary
import com.android.tools.build.libraries.metadata.ModuleDependencies
import com.google.protobuf.ByteString
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ComponentSelectionCause
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.io.FileOutputStream
import java.security.MessageDigest
import java.util.Dictionary
import java.util.Hashtable
import java.util.LinkedList
import javax.inject.Inject
/**
* Task that publishes the app dependencies proto for each module.
*/
abstract class PerModuleReportDependenciesTask @Inject constructor(objectFactory: ObjectFactory) :
NonIncrementalTask() {
@get:Internal
abstract val runtimeClasspathName: Property<String>
// Don't use @Classpath here as @Classpath ignores some of the contents whereas the output of
// this task contains the hashes of the entire contents.
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
lateinit var runtimeClasspathArtifacts : FileCollection
private set
@get:OutputFile
val dependenciesList: RegularFileProperty = objectFactory.fileProperty()
@get:Input
abstract val moduleName: Property<String>
private fun convertDependencyToMavenLibrary(
moduleVersion: ModuleVersionIdentifier?,
digest: ByteString?,
librariesToIndexMap: Dictionary<Library, Integer>,
libraries: LinkedList<Library>
): Integer? {
if (moduleVersion != null) {
val libBuilder = Library.newBuilder()
.setMavenLibrary(MavenLibrary.newBuilder()
.setGroupId(moduleVersion.group)
.setArtifactId(moduleVersion.name)
.setVersion(moduleVersion.version)
.build())
// Skip setting digest for libraries without artifact.
if(digest != null) {
libBuilder.setDigests(Library.Digests.newBuilder().setSha256(digest))
}
val lib = libBuilder.build()
var index = librariesToIndexMap.get(lib)
if (index == null) {
index = Integer(libraries.size)
libraries.add(lib)
librariesToIndexMap.put(lib, index)
}
return index
}
return null
}
private fun getFileDigest(file: File): ByteString {
return ByteString.copyFrom(MessageDigest.getInstance("SHA-256").digest(file.readBytes()))
}
override fun doTaskAction() {
val librariesToIndexMap: Dictionary<Library, Integer> = Hashtable()
val libraries = LinkedList<Library>()
val libraryDependencies = LinkedList<LibraryDependencies>()
val directDependenciesIndices: MutableSet<Int> = HashSet()
val runtimeClasspath = project.configurations.getByName(runtimeClasspathName.get())
val artifacts = runtimeClasspath.incoming.artifactView { config ->
config.componentFilter { id -> id !is ProjectComponentIdentifier }
}.artifacts
val componentDigestMap: HashMap<ComponentIdentifier, ByteString> = HashMap()
for (artifact in artifacts) {
componentDigestMap.put(
artifact.id.componentIdentifier,
getFileDigest(artifact.file)
)
}
for (dependency in runtimeClasspath.incoming.resolutionResult.allDependencies) {
// ignore non maven repository dependencies for now.
if (dependency !is ResolvedDependencyResult
|| dependency.requested !is ModuleComponentSelector) {
continue;
}
val resolvedComponent = dependency.selected
val index = convertDependencyToMavenLibrary(
resolvedComponent.moduleVersion,
componentDigestMap.get(resolvedComponent.id),
librariesToIndexMap,
libraries)
if (index != null) {
// add library dependency if we haven't traversed it yet.
if (libraryDependencies.filter { it.libraryIndex == index.toInt() }.isEmpty()) {
val libraryDependency =
LibraryDependencies.newBuilder().setLibraryIndex(index.toInt())
for (libDep in resolvedComponent.dependencies) {
if (libDep !is ResolvedDependencyResult
|| libDep.requested !is ModuleComponentSelector) {
continue;
}
val depIndex = convertDependencyToMavenLibrary(
libDep.selected.moduleVersion,
componentDigestMap.get(libDep.selected.id),
librariesToIndexMap,
libraries
)
if (depIndex != null) {
libraryDependency.addLibraryDepIndex(depIndex.toInt())
}
}
if (libraryDependency.getLibraryDepIndexCount() > 0) {
libraryDependencies.add(libraryDependency.build())
}
}
if (dependency.from.selectionReason.descriptions.filter
{
it.cause == ComponentSelectionCause.ROOT
}.isNotEmpty()) {
// this is a direct module dependency.
directDependenciesIndices.add(index.toInt())
}
}
}
// incoming.ResolutionResult will not return direct file dependencies (i.e. local jars), so
// file dependencies need to be processed using artifact view.
val fileComponentIds = componentDigestMap.keys.filter { !(it is ProjectComponentIdentifier || it is ModuleComponentIdentifier) }
for (fileComponentId in fileComponentIds) {
val library = Library.newBuilder()
.setDigests(Library.Digests.newBuilder().setSha256(componentDigestMap.get(fileComponentId)))
.build()
val index = libraries.size
libraries.add(library)
// File dependency cannot have transitive dependencies.
val libraryDependency =
LibraryDependencies.newBuilder().setLibraryIndex(index)
libraryDependencies.add(libraryDependency.build())
// This is a direct module dependency.
directDependenciesIndices.add(index)
}
val moduleDependency = ModuleDependencies.newBuilder().setModuleName(moduleName.get())
for (index in directDependenciesIndices) {
moduleDependency.addDependencyIndex(index)
}
val appDependencies = AppDependencies.newBuilder()
.addAllLibrary(libraries)
.addAllLibraryDependencies(libraryDependencies)
.addModuleDependencies(moduleDependency.build())
.build()
FileOutputStream(dependenciesList.get().asFile).use {
appDependencies.writeTo(it)
}
}
class CreationAction(
creationConfig: ApkCreationConfig
) : VariantTaskCreationAction<PerModuleReportDependenciesTask, ApkCreationConfig>(
creationConfig
) {
override val name: String = computeTaskName("collect", "Dependencies")
override val type: Class<PerModuleReportDependenciesTask> = PerModuleReportDependenciesTask::class.java
override fun handleProvider(
taskProvider: TaskProvider<PerModuleReportDependenciesTask>
) {
super.handleProvider(taskProvider)
creationConfig.artifacts.setInitialProvider(
taskProvider,
PerModuleReportDependenciesTask::dependenciesList
).withName("dependencies.pb").on(InternalArtifactType.METADATA_LIBRARY_DEPENDENCIES_REPORT)
}
override fun configure(
task: PerModuleReportDependenciesTask
) {
super.configure(task)
task.runtimeClasspathName.set(creationConfig.variantDependencies.runtimeClasspath.name)
task.runtimeClasspathArtifacts = creationConfig.variantDependencies.getArtifactCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.EXTERNAL,
// Normally we would query for PROCESSED_JAR, but JAR is probably sufficient here
// since this task is interested in only the meta data of the jar files.
AndroidArtifacts.ArtifactType.JAR
).artifactFiles
if (creationConfig is DynamicFeatureCreationConfig) {
task.moduleName.setDisallowChanges(creationConfig.featureName)
} else {
task.moduleName.setDisallowChanges("base")
}
}
}
}