blob: 05d43f2acc4e1934209dc9cdbda615a2bef7d411 [file] [log] [blame]
/*
* Copyright (C) 2019 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.api.transform.Format
import com.android.build.gradle.internal.LoggerWrapper
import com.android.build.gradle.internal.PostprocessingFeatures
import com.android.build.gradle.internal.component.BaseCreationConfig
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.scope.InternalArtifactType.DUPLICATE_CLASSES_CHECK
import com.android.build.gradle.internal.scope.InternalMultipleArtifactType
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.utils.getDesugarLibConfig
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.android.build.gradle.options.BooleanOption
import com.android.builder.core.VariantType
import com.android.builder.dexing.DexingType
import com.android.builder.dexing.MainDexListConfig
import com.android.builder.dexing.ProguardConfig
import com.android.builder.dexing.ProguardOutputFiles
import com.android.builder.dexing.R8OutputType
import com.android.builder.dexing.ToolConfig
import com.android.builder.dexing.getR8Version
import com.android.builder.dexing.runR8
import com.android.ide.common.blame.MessageReceiver
import com.android.utils.FileUtils
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
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.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 java.io.File
import java.nio.file.Path
/**
* Task that uses R8 to convert class files to dex. In case of a library variant, this
* task outputs class files.
*
* R8 task inputs are: program class files, library class files (e.g. android.jar), java resources,
* Proguard configuration files, main dex list configuration files, other tool-specific parameters.
* Output is dex or class files, depending on whether we are building an APK, or AAR.
*/
// TODO(b/139181913): add workers
@CacheableTask
abstract class R8Task: ProguardConfigurableTask() {
@get:Input
abstract val enableDesugaring: Property<Boolean>
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
abstract val mainDexListFiles: ConfigurableFileCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
abstract val mainDexRulesFiles: ConfigurableFileCollection
@get:Classpath
abstract val bootClasspath: ConfigurableFileCollection
private lateinit var messageReceiver: MessageReceiver
@get:Input
abstract val minSdkVersion: Property<Int>
@get:Input
abstract val debuggable: Property<Boolean>
@get:Input
abstract val disableTreeShaking: Property<Boolean>
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
abstract val duplicateClassesCheck: ConfigurableFileCollection
@get:Input
abstract val disableMinification: Property<Boolean>
@get:Input
lateinit var proguardConfigurations: MutableList<String>
private set
@get:Input
abstract val useFullR8: Property<Boolean>
@get:Input
lateinit var dexingType: DexingType
private set
@get:Optional
@get:Classpath
abstract val featureJars: ConfigurableFileCollection
@get:Optional
@get:Classpath
abstract val baseJar: RegularFileProperty
@get:Input
@get:Optional
abstract val coreLibDesugarConfig: Property<String>
// R8 will produce either classes or dex
@get:Optional
@get:OutputFile
abstract val outputClasses: RegularFileProperty
@get:Optional
@get:OutputDirectory
abstract val outputDex: DirectoryProperty
@get:Optional
@get:OutputDirectory
abstract val projectOutputKeepRules: DirectoryProperty
@get:Optional
@get:OutputDirectory
abstract val baseDexDir: DirectoryProperty
@get:Optional
@get:OutputDirectory
abstract val featureDexDir: DirectoryProperty
@get:OutputFile
abstract val outputResources: RegularFileProperty
@Optional
@OutputFile
fun getProguardSeedsOutput(): File? =
mappingFile.orNull?.asFile?.resolveSibling("seeds.txt")
@Optional
@OutputFile
fun getProguardUsageOutput(): File? =
mappingFile.orNull?.asFile?.resolveSibling("usage.txt")
@Optional
@OutputFile
fun getProguardConfigurationOutput(): File? =
mappingFile.orNull?.asFile?.resolveSibling("configuration.txt")
@get:Optional
@get:OutputFile
abstract val mainDexListOutput: RegularFileProperty
class CreationAction(
creationConfig: BaseCreationConfig,
isTestApplication: Boolean = false
) : ProguardConfigurableTask.CreationAction<R8Task, BaseCreationConfig>(creationConfig, isTestApplication) {
override val type = R8Task::class.java
override val name = computeTaskName("minify", "WithR8")
private var disableTreeShaking: Boolean = false
private var disableMinification: Boolean = false
// This is a huge sledgehammer, but it is necessary until http://b/72683872 is fixed.
private val proguardConfigurations: MutableList<String> = mutableListOf("-ignorewarnings")
override fun handleProvider(
taskProvider: TaskProvider<R8Task>
) {
super.handleProvider(taskProvider)
when {
variantType.isAar -> creationConfig.artifacts.setInitialProvider(
taskProvider,
R8Task::outputClasses)
.withName("shrunkClasses.jar")
.on(InternalArtifactType.SHRUNK_CLASSES)
creationConfig.variantScope.consumesFeatureJars() -> {
creationConfig.artifacts.setInitialProvider(
taskProvider,
R8Task::featureDexDir
).on(InternalArtifactType.FEATURE_DEX)
creationConfig.artifacts.setInitialProvider(
taskProvider,
R8Task::baseDexDir
).on(InternalArtifactType.BASE_DEX)
if (creationConfig.variantScope.needsShrinkDesugarLibrary) {
creationConfig.artifacts
.setInitialProvider(taskProvider, R8Task::projectOutputKeepRules)
.on(InternalArtifactType.DESUGAR_LIB_PROJECT_KEEP_RULES)
}
}
else -> {
creationConfig.artifacts.use(taskProvider)
.wiredWith(R8Task::outputDex)
.toAppendTo(InternalMultipleArtifactType.DEX)
if (creationConfig.variantScope.needsShrinkDesugarLibrary) {
creationConfig.artifacts
.setInitialProvider(taskProvider, R8Task::projectOutputKeepRules)
.on(InternalArtifactType.DESUGAR_LIB_PROJECT_KEEP_RULES)
}
}
}
creationConfig.artifacts.setInitialProvider(
taskProvider,
R8Task::outputResources
).withName("shrunkJavaRes.jar").on(InternalArtifactType.SHRUNK_JAVA_RES)
if (creationConfig.variantScope.needsMainDexListForBundle) {
creationConfig.artifacts.setInitialProvider(
taskProvider,
R8Task::mainDexListOutput
).withName("mainDexList.txt").on(InternalArtifactType.MAIN_DEX_LIST_FOR_BUNDLE)
}
}
override fun configure(
task: R8Task
) {
super.configure(task)
val artifacts = creationConfig.artifacts
task.enableDesugaring.set(
creationConfig.variantScope.java8LangSupportType == VariantScope.Java8LangSupport.R8
&& !variantType.isAar)
task.bootClasspath.from(creationConfig.globalScope.fullBootClasspath)
task.minSdkVersion
.set(creationConfig.variantDslInfo.minSdkVersionWithTargetDeviceApi.apiLevel)
task.debuggable
.setDisallowChanges(creationConfig.variantDslInfo.isDebuggable)
task.disableTreeShaking.set(disableTreeShaking)
task.disableMinification.set(disableMinification)
task.messageReceiver = creationConfig.globalScope.messageReceiver
task.dexingType = creationConfig.dexingType
task.useFullR8.set(creationConfig.services.projectOptions[BooleanOption.FULL_R8])
task.proguardConfigurations = proguardConfigurations
if (variantType.isApk) {
// options applicable only when building APKs, do not apply with AARs
task.duplicateClassesCheck.from(artifacts.get(DUPLICATE_CLASSES_CHECK))
creationConfig.variantDslInfo.multiDexKeepProguard?.let { multiDexKeepProguard ->
task.mainDexRulesFiles.from(multiDexKeepProguard)
}
if (creationConfig.needsMainDexList
&& !creationConfig.globalScope.extension.aaptOptions.namespaced
) {
task.mainDexRulesFiles.from(
artifacts.get(
InternalArtifactType.LEGACY_MULTIDEX_AAPT_DERIVED_PROGUARD_RULES
)
)
}
creationConfig.variantDslInfo.multiDexKeepFile?.let { multiDexKeepFile ->
task.mainDexListFiles.from(multiDexKeepFile)
}
if (creationConfig.variantScope.consumesFeatureJars()) {
creationConfig.artifacts.setTaskInputToFinalProduct(
InternalArtifactType.MODULE_AND_RUNTIME_DEPS_CLASSES,
task.baseJar
)
task.featureJars.from(
creationConfig.variantDependencies.getArtifactFileCollection(
AndroidArtifacts.ConsumedConfigType.REVERSE_METADATA_VALUES,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.REVERSE_METADATA_CLASSES
)
)
}
if (creationConfig.variantScope.isCoreLibraryDesugaringEnabled) {
task.coreLibDesugarConfig.set(getDesugarLibConfig(creationConfig.globalScope.project))
}
}
task.baseJar.disallowChanges()
task.featureJars.disallowChanges()
}
override fun keep(keep: String) {
proguardConfigurations.add("-keep $keep")
}
override fun keepAttributes() {
proguardConfigurations.add("-keepattributes *")
}
override fun dontWarn(dontWarn: String) {
proguardConfigurations.add("-dontwarn $dontWarn")
}
override fun setActions(actions: PostprocessingFeatures) {
disableTreeShaking = !actions.isRemoveUnusedCode
disableMinification = !actions.isObfuscate
if (!actions.isOptimize) {
proguardConfigurations.add("-dontoptimize")
}
}
}
override fun doTaskAction() {
val output: Property<out FileSystemLocation> =
when {
variantType.orNull?.isAar == true -> outputClasses
includeFeaturesInScopes.get() -> baseDexDir
else -> outputDex
}
shrink(
bootClasspath = bootClasspath.toList(),
minSdkVersion = minSdkVersion.get(),
isDebuggable = debuggable.get(),
enableDesugaring = enableDesugaring.get(),
disableTreeShaking = disableTreeShaking.get(),
disableMinification = disableMinification.get(),
mainDexListFiles = mainDexListFiles.toList(),
mainDexRulesFiles = mainDexRulesFiles.toList(),
inputProguardMapping =
if (testedMappingFile.isEmpty) {
null
} else {
testedMappingFile.singleFile
},
proguardConfigurationFiles = configurationFiles.toList(),
proguardConfigurations = proguardConfigurations,
variantType = variantType.orNull,
messageReceiver = messageReceiver,
dexingType = dexingType,
useFullR8 = useFullR8.get(),
referencedInputs = (referencedClasses + referencedResources).toList(),
classes =
if (includeFeaturesInScopes.get()) {
listOf(baseJar.get().asFile)
} else {
classes.toList()
},
resources = resources.toList(),
proguardOutputFiles =
if (mappingFile.isPresent) {
ProguardOutputFiles(
mappingFile.get().asFile.toPath(),
getProguardSeedsOutput()!!.toPath(),
getProguardUsageOutput()!!.toPath(),
getProguardConfigurationOutput()!!.toPath())
} else {
null
},
output = output.get().asFile,
outputResources = outputResources.get().asFile,
mainDexListOutput = mainDexListOutput.orNull?.asFile,
featureJars = featureJars.toList(),
featureDexDir = featureDexDir.asFile.orNull,
libConfiguration = coreLibDesugarConfig.orNull,
outputKeepRulesDir = projectOutputKeepRules.asFile.orNull
)
}
companion object {
fun shrink(
bootClasspath: List<File>,
minSdkVersion: Int,
isDebuggable: Boolean,
enableDesugaring: Boolean,
disableTreeShaking: Boolean,
disableMinification: Boolean,
mainDexListFiles: List<File>,
mainDexRulesFiles: List<File>,
inputProguardMapping: File?,
proguardConfigurationFiles: List<File>,
proguardConfigurations: MutableList<String>,
variantType: VariantType?,
messageReceiver: MessageReceiver,
dexingType: DexingType,
useFullR8: Boolean,
referencedInputs: List<File>,
classes: List<File>,
resources: List<File>,
proguardOutputFiles: ProguardOutputFiles?,
output: File,
outputResources: File,
mainDexListOutput: File?,
featureJars: List<File>,
featureDexDir: File?,
libConfiguration: String?,
outputKeepRulesDir: File?
) {
val logger = LoggerWrapper.getLogger(R8Task::class.java)
logger
.info(
"""
|R8 is a new Android code shrinker. If you experience any issues, please file a bug at
|https://issuetracker.google.com, using 'Shrinker (R8)' as component name. You can
|disable R8 by updating gradle.properties with 'android.enableR8=false'.
|Current version is: ${getR8Version()}.
|""".trimMargin()
)
val r8OutputType: R8OutputType
val outputFormat: Format
if (variantType?.isAar == true) {
r8OutputType = R8OutputType.CLASSES
outputFormat = Format.JAR
} else {
r8OutputType = R8OutputType.DEX
outputFormat = Format.DIRECTORY
}
FileUtils.deleteIfExists(outputResources)
when (outputFormat) {
Format.DIRECTORY -> {
FileUtils.cleanOutputDir(output)
featureDexDir?.let { FileUtils.cleanOutputDir(it) }
outputKeepRulesDir?.let { FileUtils.cleanOutputDir(it) }
}
Format.JAR -> FileUtils.deleteIfExists(output)
}
val toolConfig = ToolConfig(
minSdkVersion = minSdkVersion,
isDebuggable = isDebuggable,
disableTreeShaking = disableTreeShaking,
disableDesugaring = !enableDesugaring,
disableMinification = disableMinification,
r8OutputType = r8OutputType
)
val proguardConfig = ProguardConfig(
proguardConfigurationFiles.map { it.toPath() },
inputProguardMapping?.toPath(),
proguardConfigurations,
proguardOutputFiles
)
val mainDexListConfig = if (dexingType == DexingType.LEGACY_MULTIDEX) {
MainDexListConfig(
mainDexRulesFiles.map { it.toPath() },
mainDexListFiles.map { it.toPath() },
getPlatformRules(),
mainDexListOutput?.toPath()
)
} else {
MainDexListConfig()
}
val outputKeepRulesFile = outputKeepRulesDir?.resolve("output")
// When invoking R8 we filter out missing files. E.g. javac output may not exist if
// there are no Java sources. See b/151605314 for details.
runR8(
filterMissingFiles(classes, logger),
output.toPath(),
filterMissingFiles(resources, logger),
outputResources.toPath(),
bootClasspath.map { it.toPath() },
filterMissingFiles(referencedInputs, logger),
toolConfig,
proguardConfig,
mainDexListConfig,
messageReceiver,
useFullR8,
featureJars.map { it.toPath() },
featureDexDir?.toPath(),
libConfiguration,
outputKeepRulesFile?.toPath()
)
}
private fun filterMissingFiles(files: List<File>, logger: LoggerWrapper): List<Path> {
return files.mapNotNull { file ->
if (file.exists()) file.toPath()
else {
logger.verbose("$file is ignored as it does not exist.")
null
}
}
}
}
}