blob: d08f15528946a4a750a7014ab80ff8b823c81374 [file] [log] [blame]
/*
* Copyright (C) 2012 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.build.api.artifact.ArtifactType
import com.android.build.gradle.internal.LoggerWrapper
import com.android.build.gradle.internal.errors.MessageReceiverImpl
import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL
import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.ASSETS
import com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH
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.IncrementalTask
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.build.gradle.options.SyncOptions
import com.android.builder.core.BuilderConstants
import com.android.builder.model.SourceProvider
import com.android.ide.common.resources.AssetMerger
import com.android.ide.common.resources.AssetSet
import com.android.ide.common.resources.FileStatus
import com.android.ide.common.resources.FileValidity
import com.android.ide.common.resources.MergedAssetWriter
import com.android.ide.common.resources.MergingException
import com.android.utils.FileUtils
import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.Lists
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
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
import java.io.IOException
import java.util.function.Function
import java.util.function.Supplier
@CacheableTask
abstract class MergeSourceSetFolders : IncrementalTask() {
// ----- PUBLIC TASK API -----
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
// ----- PRIVATE TASK API -----
// file inputs as raw files, lazy behind a memoized/bypassed supplier
private lateinit var sourceFolderInputs: Supplier<Collection<File>>
// supplier of the assets set, for execution only.
@get:Internal("for testing")
internal lateinit var assetSetSupplier: Supplier<List<AssetSet>>
// for the dependencies
@get:Internal("for testing")
internal var libraryCollection: ArtifactCollection? = null
@get:InputFiles
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val shadersOutputDir: DirectoryProperty
@get:Input
@get:Optional
var ignoreAssets: String? = null
private set
private lateinit var errorFormatMode: SyncOptions.ErrorFormatMode
private val fileValidity = FileValidity<AssetSet>()
override val incremental: Boolean
@Internal
get() = true
@Throws(IOException::class)
override fun doFullTaskAction() {
val incFolder = incrementalFolder!!
// this is full run, clean the previous output
val destinationDir = outputDir.get().asFile
FileUtils.cleanOutputDir(destinationDir)
val assetSets = computeAssetSetList()
// create a new merger and populate it with the sets.
val merger = AssetMerger()
val logger = LoggerWrapper(logger)
try {
getWorkerFacadeWithWorkers().use { workerExecutor ->
for (assetSet in assetSets) {
// set needs to be loaded.
assetSet.loadFromFiles(logger)
merger.addDataSet(assetSet)
}
// get the merged set and write it down.
val writer = MergedAssetWriter(destinationDir, workerExecutor)
merger.mergeData(writer, false /*doCleanUp*/)
// No exception? Write the known state.
merger.writeBlobTo(incFolder, writer, false)
}
} catch (e: Exception) {
MergingException.findAndReportMergingException(
e, MessageReceiverImpl(errorFormatMode, getLogger())
)
try {
throw e
} catch (mergingException: MergingException) {
merger.cleanBlob(incFolder)
throw ResourceException(mergingException.message, mergingException)
}
}
}
@Throws(IOException::class)
override fun doIncrementalTaskAction(changedInputs: Map<File, FileStatus>) {
// create a merger and load the known state.
val merger = AssetMerger()
try {
getWorkerFacadeWithWorkers().use { workerExecutor ->
if (!/*incrementalState*/merger.loadFromBlob(incrementalFolder!!, true)) {
doFullTaskAction()
return
}
// compare the known state to the current sets to detect incompatibility.
// This is in case there's a change that's too hard to do incrementally. In this case
// we'll simply revert to full build.
val assetSets = computeAssetSetList()
if (!merger.checkValidUpdate(assetSets)) {
logger.info("Changed Asset sets: full task run!")
doFullTaskAction()
return
}
val logger = LoggerWrapper(logger)
// The incremental process is the following:
// Loop on all the changed files, find which ResourceSet it belongs to, then ask
// the resource set to update itself with the new file.
for ((changedFile, value) in changedInputs) {
// Ignore directories.
if (changedFile.isDirectory) {
continue
}
merger.findDataSetContaining(changedFile, fileValidity)
if (fileValidity.status == FileValidity.FileStatus.UNKNOWN_FILE) {
doFullTaskAction()
return
} else if (fileValidity.status == FileValidity.FileStatus.VALID_FILE) {
if (!fileValidity
.dataSet
.updateWith(
fileValidity.sourceFile,
changedFile,
value,
logger
)
) {
getLogger().info(
"Failed to process {} event! Full task run", value
)
doFullTaskAction()
return
}
}
}
val writer = MergedAssetWriter(outputDir.get().asFile, workerExecutor)
merger.mergeData(writer, false /*doCleanUp*/)
// No exception? Write the known state.
merger.writeBlobTo(incrementalFolder!!, writer, false)
}
} catch (e: Exception) {
MergingException.findAndReportMergingException(
e, MessageReceiverImpl(errorFormatMode, logger)
)
try {
throw e
} catch (mergingException: MergingException) {
merger.cleanBlob(incrementalFolder!!)
throw ResourceException(mergingException.message, mergingException)
}
} finally {
// some clean up after the task to help multi variant/module builds.
fileValidity.clear()
}
}
@Optional
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
fun getLibraries(): FileCollection? = libraryCollection?.artifactFiles
// input list for the source folder based asset folders.
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
fun getSourceFolderInputs(): Collection<File> = sourceFolderInputs.get()
/**
* Compute the list of Asset set to be used during execution based all the inputs.
*/
@VisibleForTesting
internal fun computeAssetSetList(): List<AssetSet> {
val assetSetList: List<AssetSet>
val assetSets = assetSetSupplier.get()
if (!shadersOutputDir.isPresent
&& ignoreAssets == null
&& libraryCollection == null
) {
assetSetList = assetSets
} else {
var size = assetSets.size + 3
libraryCollection?.let {
size += it.artifacts.size
}
assetSetList = Lists.newArrayListWithExpectedSize(size)
// get the dependency base assets sets.
// add at the beginning since the libraries are less important than the folder based
// asset sets.
libraryCollection?.let {
// the order of the artifact is descending order, so we need to reverse it.
val libArtifacts = it.artifacts
for (artifact in libArtifacts) {
val assetSet = AssetSet(ProcessApplicationManifest.getArtifactName(artifact))
assetSet.addSource(artifact.file)
// add to 0 always, since we need to reverse the order.
assetSetList.add(0, assetSet)
}
}
// add the generated folders to the first set of the folder-based sets.
val generatedAssetFolders = Lists.newArrayList<File>()
if (shadersOutputDir.isPresent) {
generatedAssetFolders.add(shadersOutputDir.get().asFile)
}
// add the generated files to the main set.
val mainAssetSet = assetSets[0]
assert(mainAssetSet.configName == BuilderConstants.MAIN)
mainAssetSet.addSources(generatedAssetFolders)
assetSetList.addAll(assetSets)
}
if (ignoreAssets != null) {
for (set in assetSetList) {
set.setIgnoredPatterns(ignoreAssets)
}
}
return assetSetList
}
abstract class CreationAction protected constructor(scope: VariantScope) :
VariantTaskCreationAction<MergeSourceSetFolders>(scope) {
override val type: Class<MergeSourceSetFolders>
get() = MergeSourceSetFolders::class.java
override fun configure(task: MergeSourceSetFolders) {
super.configure(task)
val scope = variantScope
task.incrementalFolder = scope.getIncrementalDir(name)
task.errorFormatMode = SyncOptions.getErrorFormatMode(scope.globalScope.projectOptions)
}
}
open class MergeAssetBaseCreationAction(
scope: VariantScope,
private val outputArtifactType: ArtifactType,
private val includeDependencies: Boolean
) : CreationAction(scope) {
override val name: String
get() = variantScope.getTaskName("merge", "Assets")
override fun handleProvider(taskProvider: TaskProvider<out MergeSourceSetFolders>) {
super.handleProvider(taskProvider)
variantScope
.artifacts
.producesDir(
outputArtifactType,
BuildArtifactsHolder.OperationType.INITIAL,
taskProvider,
MergeSourceSetFolders::outputDir,
fileName = "out"
)
variantScope.taskContainer.mergeAssetsTask = taskProvider
}
override fun configure(task: MergeSourceSetFolders) {
super.configure(task)
val scope = variantScope
val variantData = scope.variantData
val variantConfig = variantData.variantConfiguration
val assetDirFunction =
Function<SourceProvider, Collection<File>> { it.assetsDirectories }
task.assetSetSupplier = Supplier { variantConfig.getSourceFilesAsAssetSets(assetDirFunction) }
task.sourceFolderInputs = Supplier { variantConfig.getSourceFiles(assetDirFunction) }
scope.artifacts.setTaskInputToFinalProduct(
InternalArtifactType.SHADER_ASSETS,
task.shadersOutputDir
)
val options = scope.globalScope.extension.aaptOptions
if (options != null) {
task.ignoreAssets = options.ignoreAssets
}
if (includeDependencies) {
task.libraryCollection = scope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, ASSETS)
}
task.dependsOn(scope.taskContainer.assetGenTask)
}
}
class MergeAppAssetCreationAction(scope: VariantScope) :
MergeAssetBaseCreationAction(scope, InternalArtifactType.MERGED_ASSETS, true) {
override val name: String
get() = variantScope.getTaskName("merge", "Assets")
}
class LibraryAssetCreationAction(scope: VariantScope) :
MergeAssetBaseCreationAction(scope, InternalArtifactType.LIBRARY_ASSETS, false) {
override val name: String
get() = variantScope.getTaskName("package", "Assets")
}
class MergeJniLibFoldersCreationAction(scope: VariantScope) : CreationAction(scope) {
override val name: String
get() = variantScope.getTaskName("merge", "JniLibFolders")
override fun handleProvider(taskProvider: TaskProvider<out MergeSourceSetFolders>) {
super.handleProvider(taskProvider)
variantScope
.artifacts
.producesDir(
InternalArtifactType.MERGED_JNI_LIBS,
BuildArtifactsHolder.OperationType.INITIAL,
taskProvider,
MergeSourceSetFolders::outputDir,
fileName = "out"
)
}
override fun configure(task: MergeSourceSetFolders) {
super.configure(task)
val variantData = variantScope.variantData
val variantConfig = variantData.variantConfiguration
val assetDirFunction =
Function<SourceProvider, Collection<File>> { it.jniLibsDirectories }
task.assetSetSupplier =
Supplier { variantConfig.getSourceFilesAsAssetSets(assetDirFunction) }
task.sourceFolderInputs = Supplier { variantConfig.getSourceFiles(assetDirFunction) }
}
}
class MergeShaderSourceFoldersCreationAction(scope: VariantScope) : CreationAction(scope) {
override val name: String
get() = variantScope.getTaskName("merge", "Shaders")
override fun handleProvider(taskProvider: TaskProvider<out MergeSourceSetFolders>) {
super.handleProvider(taskProvider)
variantScope
.artifacts
.producesDir(
InternalArtifactType.MERGED_SHADERS,
BuildArtifactsHolder.OperationType.INITIAL,
taskProvider,
MergeSourceSetFolders::outputDir,
fileName = "out"
)
}
override fun configure(task: MergeSourceSetFolders) {
super.configure(task)
val variantData = variantScope.variantData
val variantConfig = variantData.variantConfiguration
val assetDirFunction = Function<SourceProvider, Collection<File>> { it.shadersDirectories }
task.assetSetSupplier =
Supplier { variantConfig.getSourceFilesAsAssetSets(assetDirFunction) }
task.sourceFolderInputs = Supplier { variantConfig.getSourceFiles(assetDirFunction) }
}
}
}