blob: c0ba396ea8b01c19f1ccd3d8e274e871460cd7ba [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.SdkConstants
import com.android.build.api.transform.QualifiedContent.DefaultContentType.RESOURCES
import com.android.build.api.transform.QualifiedContent.Scope.EXTERNAL_LIBRARIES
import com.android.build.api.transform.QualifiedContent.Scope.PROJECT
import com.android.build.api.transform.QualifiedContent.Scope.SUB_PROJECTS
import com.android.build.api.transform.QualifiedContent.ScopeType
import com.android.build.gradle.internal.InternalScope.FEATURES
import com.android.build.gradle.internal.InternalScope.LOCAL_DEPS
import com.android.build.gradle.internal.packaging.SerializablePackagingOptions
import com.android.build.gradle.internal.pipeline.StreamFilter.PROJECT_RESOURCES
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.BuildArtifactsHolder
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.scope.InternalArtifactType.MERGED_JAVA_RES
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.ide.common.resources.FileStatus
import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
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 org.gradle.workers.WorkerExecutor
import java.io.File
import java.util.function.Predicate
import javax.inject.Inject
/**
* Task to merge java resources from multiple modules
*/
@CacheableTask
open class MergeJavaResourceTask
@Inject constructor(workerExecutor: WorkerExecutor, objects: ObjectFactory) : IncrementalTask() {
// PathSensitivity.ABSOLUTE necessary here to support changing java resource file relative
// paths. A better solution will be custom snapshots from gradle:
// https://github.com/gradle/gradle/issues/8503
@get:InputFiles
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:Optional
var projectJavaRes: FileCollection? = null
private set
@get:Classpath
@get:Optional
var projectJavaResAsJars: FileCollection? = null
private set
@get:Classpath
@get:Optional
var subProjectJavaRes: FileCollection? = null
private set
@get:Classpath
@get:Optional
var externalLibJavaRes: FileCollection? = null
private set
@get:Classpath
@get:Optional
var featureJavaRes: FileCollection? = null
private set
@get:Input
lateinit var mergeScopes: Collection<ScopeType>
private set
@get:Nested
lateinit var packagingOptions: SerializablePackagingOptions
private set
private lateinit var intermediateDir: File
@get:OutputDirectory
lateinit var cacheDir: File
private set
private lateinit var incrementalStateFile: File
@get:OutputFile
val outputFile: RegularFileProperty = objects.fileProperty()
private val workers = Workers.preferWorkers(project.name, path, workerExecutor)
override val incremental: Boolean
get() = true
// The runnable implementing the processing is not able to deal with fine-grained file but
// instead is expecting directories of files. Use the unfiltered collection (since the filtering
// changes the FileCollection of directories into a FileTree of files) to process, but don't
// use it as a jar input, it's covered by the [projectJavaRes] and [projectJavaResAsJars] above.
private lateinit var unfilteredProjectJavaRes: FileCollection
override fun doFullTaskAction() {
workers.use {
it.submit(
MergeJavaResRunnable::class.java,
MergeJavaResRunnable.Params(
unfilteredProjectJavaRes.files,
subProjectJavaRes?.files,
externalLibJavaRes?.files,
featureJavaRes?.files,
outputFile.get().asFile,
packagingOptions,
incrementalStateFile,
false,
cacheDir,
null,
RESOURCES
)
)
}
}
override fun doIncrementalTaskAction(changedInputs: Map<File, FileStatus>) {
if (!incrementalStateFile.isFile) {
doFullTaskAction()
return
}
workers.use {
it.submit(
MergeJavaResRunnable::class.java,
MergeJavaResRunnable.Params(
unfilteredProjectJavaRes.files,
subProjectJavaRes?.files,
externalLibJavaRes?.files,
featureJavaRes?.files,
outputFile.get().asFile,
packagingOptions,
incrementalStateFile,
true,
cacheDir,
changedInputs,
RESOURCES
)
)
}
}
class CreationAction(
private val mergeScopes: Collection<ScopeType>,
variantScope: VariantScope
) : VariantTaskCreationAction<MergeJavaResourceTask>(variantScope) {
private val projectJavaResFromStreams: FileCollection?
override val name: String
get() = variantScope.getTaskName("merge", "JavaResource")
override val type: Class<MergeJavaResourceTask>
get() = MergeJavaResourceTask::class.java
init {
if (variantScope.needsJavaResStreams) {
// Because ordering matters for Transform pipeline, we need to fetch the java res
// as soon as this creation action is instantiated, if needed.
projectJavaResFromStreams =
variantScope.transformManager
.getPipelineOutputAsFileCollection(PROJECT_RESOURCES)
// We must also consume corresponding streams to avoid duplicates; any downstream
// transforms will use the merged-java-res stream instead.
variantScope.transformManager
.consumeStreams(mutableSetOf(PROJECT), setOf(RESOURCES))
} else {
projectJavaResFromStreams = null
}
}
override fun handleProvider(taskProvider: TaskProvider<out MergeJavaResourceTask>) {
super.handleProvider(taskProvider)
variantScope.artifacts.producesFile(
MERGED_JAVA_RES,
BuildArtifactsHolder.OperationType.APPEND,
taskProvider,
MergeJavaResourceTask::outputFile,
fileName = "out.jar"
)
}
override fun configure(task: MergeJavaResourceTask) {
super.configure(task)
if (projectJavaResFromStreams != null) {
task.projectJavaResAsJars = projectJavaResFromStreams
task.unfilteredProjectJavaRes = projectJavaResFromStreams
} else {
val projectJavaRes = getProjectJavaRes(variantScope)
task.unfilteredProjectJavaRes = projectJavaRes
task.projectJavaRes = projectJavaRes.asFileTree.filter(spec)
}
if (mergeScopes.contains(SUB_PROJECTS)) {
task.subProjectJavaRes =
variantScope.getArtifactFileCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.JAVA_RES
)
}
if (mergeScopes.contains(EXTERNAL_LIBRARIES) || mergeScopes.contains(LOCAL_DEPS)) {
// Local jars are treated the same as external libraries
task.externalLibJavaRes = getExternalLibJavaRes(variantScope, mergeScopes)
}
if (mergeScopes.contains(FEATURES)) {
task.featureJavaRes =
variantScope.getArtifactFileCollection(
AndroidArtifacts.ConsumedConfigType.METADATA_VALUES,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.METADATA_JAVA_RES
)
}
task.mergeScopes = mergeScopes
task.packagingOptions =
SerializablePackagingOptions(
variantScope.globalScope.extension.packagingOptions
)
task.intermediateDir =
variantScope.getIncrementalDir("${variantScope.fullVariantName}-mergeJavaRes")
task.cacheDir = File(task.intermediateDir, "zip-cache")
task.incrementalStateFile = File(task.intermediateDir, "merge-state")
}
}
companion object {
val predicate= Predicate<String> { t ->
!t.endsWith(SdkConstants.DOT_CLASS) &&
!t.endsWith(SdkConstants.DOT_NATIVE_LIBS)
}
val spec: (file: File) -> Boolean = {
predicate.test(it.absolutePath)
}
}
}
fun getProjectJavaRes(scope: VariantScope): FileCollection {
val javaRes = scope.globalScope.project.files()
javaRes.from(scope.artifacts.getFinalArtifactFiles(InternalArtifactType.JAVA_RES))
javaRes.from(scope.artifacts.getFinalProduct<Directory>(InternalArtifactType.JAVAC))
javaRes.from(scope.variantData.allPreJavacGeneratedBytecode)
javaRes.from(scope.variantData.allPostJavacGeneratedBytecode)
javaRes.from(
scope.artifacts.getFinalArtifactFiles(InternalArtifactType.RUNTIME_R_CLASS_CLASSES)
)
return javaRes
}
private fun getExternalLibJavaRes(scope: VariantScope, mergeScopes: Collection<ScopeType>): FileCollection {
val externalLibJavaRes = scope.globalScope.project.files()
if (mergeScopes.contains(EXTERNAL_LIBRARIES)) {
externalLibJavaRes.from(
scope.getArtifactFileCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.EXTERNAL,
AndroidArtifacts.ArtifactType.JAVA_RES
)
)
}
if (mergeScopes.contains(LOCAL_DEPS)) {
externalLibJavaRes.from(scope.localPackagedJars)
}
return externalLibJavaRes
}