blob: 3a56b00cace6571cbf4768e41064e21072da0d70 [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.annotations.NonNull
import com.android.build.gradle.internal.scope.ConventionMappingHelper
import com.android.build.gradle.internal.scope.TaskConfigAction
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.tasks.IncrementalTask
import com.android.build.gradle.internal.variant.BaseVariantData
import com.android.build.gradle.internal.variant.BaseVariantOutputData
import com.android.builder.core.VariantType
import com.android.builder.png.QueuedCruncher
import com.android.ide.common.internal.PngCruncher
import com.android.ide.common.res2.FileStatus
import com.android.ide.common.res2.FileValidity
import com.android.ide.common.res2.MergedResourceWriter
import com.android.ide.common.res2.MergingException
import com.android.ide.common.res2.ResourceMerger
import com.android.ide.common.res2.ResourceSet
import com.android.sdklib.BuildToolInfo
import com.android.sdklib.repository.FullRevision
import com.google.common.collect.Lists
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.ParallelizableTask
import org.gradle.api.tasks.OutputFile
import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES
@ParallelizableTask
public class MergeResources extends IncrementalTask {
// ----- PUBLIC TASK API -----
/** Directory to write the merged resources to */
@OutputDirectory
File outputDir
/** Optional file to write any publicly imported resource types and names to */
@Optional
@OutputFile
File publicFile
// ----- PRIVATE TASK API -----
// fake input to detect changes. Not actually used by the task
@InputFiles
Iterable<File> getRawInputFolders() {
return flattenSourceSets(getInputResourceSets())
}
@Input
String getBuildToolsVersion() {
getBuildTools().getRevision()
}
@Input
boolean process9Patch
@Input
boolean crunchPng
@Input
boolean useNewCruncher;
@Input
boolean insertSourceMarkers = true
@Input
boolean normalizeResources
// actual inputs
List<ResourceSet> inputResourceSets
private final FileValidity<ResourceSet> fileValidity = new FileValidity<ResourceSet>();
@Override
protected boolean isIncremental() {
return true
}
private PngCruncher getCruncher() {
if (getUseNewCruncher()) {
if (builder.getTargetInfo().buildTools.getRevision().getMajor() >= 22) {
return QueuedCruncher.Builder.INSTANCE.newCruncher(
builder.getTargetInfo().buildTools.getPath(
BuildToolInfo.PathId.AAPT), builder.getLogger())
}
logger.info("New PNG cruncher will be enabled with build tools 22 and above.")
}
return builder.aaptCruncher;
}
@Override
protected void doFullTaskAction() {
// this is full run, clean the previous output
File destinationDir = getOutputDir()
emptyFolder(destinationDir)
List<ResourceSet> resourceSets = getInputResourceSets()
// create a new merger and populate it with the sets.
ResourceMerger merger = new ResourceMerger()
try {
for (ResourceSet resourceSet : resourceSets) {
resourceSet.setNormalizeResources(normalizeResources)
// set needs to be loaded.
resourceSet.loadFromFiles(getILogger())
merger.addDataSet(resourceSet)
}
// get the merged set and write it down.
MergedResourceWriter writer = new MergedResourceWriter(
destinationDir, getCruncher(),
getCrunchPng(), getProcess9Patch(), getPublicFile())
writer.setInsertSourceMarkers(getInsertSourceMarkers())
merger.mergeData(writer, false /*doCleanUp*/)
// No exception? Write the known state.
merger.writeBlobTo(getIncrementalFolder(), writer)
} catch (MergingException e) {
println e.getMessage()
merger.cleanBlob(getIncrementalFolder())
throw new ResourceException(e.getMessage(), e)
}
}
@Override
protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
// create a merger and load the known state.
ResourceMerger merger = new ResourceMerger()
try {
if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
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.
List<ResourceSet> resourceSets = getInputResourceSets()
for (ResourceSet resourceSet : resourceSets) {
resourceSet.setNormalizeResources(normalizeResources)
}
if (!merger.checkValidUpdate(resourceSets)) {
project.logger.info("Changed Resource sets: full task run!")
doFullTaskAction()
return
}
// 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 (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
File changedFile = entry.getKey()
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, entry.getValue(), getILogger())) {
project.logger.info(
String.format("Failed to process %s event! Full task run",
entry.getValue()))
doFullTaskAction()
return
}
}
}
MergedResourceWriter writer = new MergedResourceWriter(
getOutputDir(), getCruncher(),
getCrunchPng(), getProcess9Patch(), getPublicFile())
writer.setInsertSourceMarkers(getInsertSourceMarkers())
merger.mergeData(writer, false /*doCleanUp*/)
// No exception? Write the known state.
merger.writeBlobTo(getIncrementalFolder(), writer)
} catch (MergingException e) {
println e.getMessage()
merger.cleanBlob(getIncrementalFolder())
throw new ResourceException(e.getMessage(), e)
}
}
public static class ConfigAction implements TaskConfigAction<MergeResources> {
@NonNull
VariantScope scope
@NonNull
String taskNamePrefix
@NonNull
File outputLocation;
boolean includeDependencies;
boolean process9Patch;
ConfigAction(VariantScope scope, String taskNamePrefix, File outputLocation,
boolean includeDependencies, boolean process9Patch) {
this.scope = scope
this.taskNamePrefix = taskNamePrefix
this.outputLocation = outputLocation
this.includeDependencies = includeDependencies
this.process9Patch = process9Patch
scope.setMergeResourceOutputDir(outputLocation)
}
@Override
String getName() {
return scope.getTaskName(taskNamePrefix, "Resources")
}
@Override
Class<MergeResources> getType() {
return MergeResources
}
@Override
void execute(MergeResources mergeResourcesTask) {
BaseVariantData<? extends BaseVariantOutputData> variantData = scope.variantData
mergeResourcesTask.androidBuilder = scope.globalScope.androidBuilder
mergeResourcesTask.incrementalFolder = new File(
"$scope.globalScope.buildDir/${FD_INTERMEDIATES}/incremental/" +
"${taskNamePrefix}Resources/${variantData.variantConfiguration.dirName}")
mergeResourcesTask.process9Patch = process9Patch
mergeResourcesTask.crunchPng = scope.globalScope.extension.aaptOptions.getCruncherEnabled()
mergeResourcesTask.normalizeResources = scope.globalScope.extension.buildToolsRevision.compareTo(new FullRevision(21, 0, 0)) < 0
ConventionMappingHelper.map(mergeResourcesTask, "useNewCruncher") { scope.globalScope.getExtension().aaptOptions.useNewCruncher }
ConventionMappingHelper.map(mergeResourcesTask, "inputResourceSets") {
List<File> generatedResFolders = Lists.newArrayList(
scope.getRenderscriptResOutputDir(),
scope.getGeneratedResOutputDir())
if (variantData.extraGeneratedResFolders != null) {
generatedResFolders += variantData.extraGeneratedResFolders
}
if (variantData.generateApkDataTask != null &&
variantData.getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
generatedResFolders.add(variantData.generateApkDataTask.getResOutputDir())
}
variantData.variantConfiguration.getResourceSets(generatedResFolders,
includeDependencies)
}
ConventionMappingHelper.map(mergeResourcesTask, "outputDir") {
outputLocation ?: scope.getDefaultMergeResourcesOutputDir()
}
variantData.mergeResourcesTask = mergeResourcesTask
}
}
}