blob: 2a632c2ce4b183b864266099982567b8bd3e3cff [file] [log] [blame]
/*
* Copyright (C) 2020 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.DOT_JAR
import com.android.build.gradle.internal.component.ApkCreationConfig
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.builder.files.KeyedFileCache
import com.android.builder.packaging.PackagingUtils
import com.android.utils.FileUtils
import com.android.zipflinger.BytesSource
import com.android.zipflinger.ZipArchive
import com.google.common.annotations.VisibleForTesting
import com.google.common.io.Files
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileType
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
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 org.gradle.work.ChangeType
import org.gradle.work.FileChange
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import java.io.File
import java.util.function.Predicate
import java.util.zip.Deflater
import java.util.zip.Deflater.BEST_SPEED
import java.util.zip.Deflater.DEFAULT_COMPRESSION
import javax.inject.Inject
/**
* Task to compress assets before they're packaged in the APK.
*
* This task outputs a directory of single-entry jars (instead of a single jar) so that the
* downstream packaging task doesn't have to manage its incremental state via a [KeyedFileCache].
*
* Each single-entry jar file's relative path in the output directory is equal to "assets/" + the
* corresponding asset's relative path in the input directory + ".jar".
*/
@CacheableTask
abstract class CompressAssetsTask : NewIncrementalTask() {
@get:Incremental
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val inputDir: DirectoryProperty
@get:Input
abstract val noCompress: ListProperty<String>
@get:Input
abstract val compressionLevel: Property<Int>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
override fun doTaskAction(inputChanges: InputChanges) {
CompressAssetsDelegate(
workerExecutor.noIsolation(),
inputDir.get().asFile,
outputDir.get().asFile,
PackagingUtils.getNoCompressPredicateForJavaRes(noCompress.get()),
compressionLevel.get(),
inputChanges.getFileChanges(inputDir)
).run()
}
class CreationAction(
creationConfig: ApkCreationConfig
) : VariantTaskCreationAction<CompressAssetsTask, ApkCreationConfig>(
creationConfig
) {
override val name: String
get() = computeTaskName("compress", "Assets")
override val type: Class<CompressAssetsTask>
get() = CompressAssetsTask::class.java
override fun handleProvider(
taskProvider: TaskProvider<CompressAssetsTask>
) {
super.handleProvider(taskProvider)
creationConfig.artifacts.setInitialProvider(
taskProvider,
CompressAssetsTask::outputDir
).withName("out").on(InternalArtifactType.COMPRESSED_ASSETS)
}
override fun configure(
task: CompressAssetsTask
) {
super.configure(task)
creationConfig.artifacts.setTaskInputToFinalProduct(
InternalArtifactType.MERGED_ASSETS,
task.inputDir
)
task.noCompress.setDisallowChanges(creationConfig.globalScope.extension.aaptOptions.noCompress)
task.compressionLevel.setDisallowChanges(
if (creationConfig.variantDslInfo.isDebuggable) {
BEST_SPEED
} else {
DEFAULT_COMPRESSION
}
)
}
}
}
/**
* Delegate to compress assets
*/
@VisibleForTesting
class CompressAssetsDelegate(
private val workQueue: WorkQueue,
val inputDir: File,
val outputDir: File,
private val noCompressPredicate: Predicate<String>,
private val compressionLevel: Int,
val changes: Iterable<FileChange>
) {
fun run() {
for (change in changes) {
if (change.fileType == FileType.DIRECTORY) {
continue
}
val entryPath = "assets/${change.normalizedPath}"
val targetFile = File(outputDir, entryPath + DOT_JAR)
val entryCompressionLevel = if (noCompressPredicate.test(entryPath)) {
Deflater.NO_COMPRESSION
} else {
compressionLevel
}
workQueue.submit(CompressAssetsWorkAction::class.java) {
it.input.set(change.file)
it.output.set(targetFile)
it.entryPath.set(entryPath)
it.entryCompressionLevel.set(entryCompressionLevel)
it.changeType.set(change.changeType)
}
}
}
}
/**
* [WorkAction] to compress an asset file into a single-entry jar
*/
abstract class CompressAssetsWorkAction @Inject constructor(
private val compressAssetsWorkParameters: CompressAssetsWorkParameters
): WorkAction<CompressAssetsWorkParameters> {
override fun execute() {
val output = compressAssetsWorkParameters.output.get().asFile
val changeType = compressAssetsWorkParameters.changeType.get()
if (changeType != ChangeType.ADDED) {
FileUtils.deleteIfExists(output)
}
if (changeType != ChangeType.REMOVED) {
Files.createParentDirs(output)
ZipArchive(output).use { jar ->
jar.add(
BytesSource(
compressAssetsWorkParameters.input.get().asFile,
compressAssetsWorkParameters.entryPath.get(),
compressAssetsWorkParameters.entryCompressionLevel.get()
)
)
}
}
}
}
/**
* [WorkParameters] for [CompressAssetsWorkAction]
*/
abstract class CompressAssetsWorkParameters: WorkParameters {
abstract val input: RegularFileProperty
abstract val output: RegularFileProperty
abstract val entryPath: Property<String>
abstract val entryCompressionLevel: Property<Int>
abstract val changeType: Property<ChangeType>
}