blob: b0ccb6855168e63ad5d5d93ce73e7f2a3bc003d5 [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.SdkConstants
import com.android.build.api.component.impl.ComponentPropertiesImpl
import com.android.build.api.transform.QualifiedContent
import com.android.build.gradle.internal.LoggerWrapper
import com.android.build.gradle.internal.coverage.JacocoConfigurations
import com.android.build.gradle.internal.pipeline.OriginalStream
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.builder.core.DesugarProcessArgs
import com.android.builder.core.DesugarProcessBuilder
import com.android.ide.common.repository.GradleVersion
import com.android.ide.common.workers.WorkerExecutorFacade
import com.android.utils.FileUtils
import com.android.utils.PathUtils
import com.google.common.collect.ArrayListMultimap
import org.apache.log4j.Logger
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.CompileClasspath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.LocalState
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import javax.inject.Inject
/**
* Desugar all bytecode that is using Java 8 langauge features, using the desugar tool. This
* task processes all runtime classes and it uses the runtime classpath and bootclasspath to
* rewrite the code.
*/
@CacheableTask
abstract class DesugarTask @Inject constructor(objectFactory: ObjectFactory) :
NonIncrementalTask() {
@get: Classpath
abstract val projectClasses: ConfigurableFileCollection
@get: Classpath
abstract val subProjectClasses: ConfigurableFileCollection
@get: Classpath
abstract val externaLibsClasses: DirectoryProperty
@get: CompileClasspath
abstract val desugaringClasspath: ConfigurableFileCollection
@get: CompileClasspath
abstract val bootClasspath: ConfigurableFileCollection
@get: OutputDirectory
abstract val projectOutput: DirectoryProperty
@get: OutputDirectory
abstract val subProjectOutput: DirectoryProperty
// A directory containing jars.
@get: OutputDirectory
abstract val externalLibsOutput: DirectoryProperty
@get: LocalState
abstract val tmpDir: DirectoryProperty
@get: Input
val minSdk: Property<Int> = objectFactory.property(Int::class.java)
@get: Input
val enableBugFixForJacoco: Property<Boolean> = objectFactory.property(Boolean::class.java)
override fun doTaskAction() {
getWorkerFacadeWithWorkers().use { executorFacade ->
val libs = externaLibsClasses.asFile.get().listFiles()!!.toList().sortedBy { it.name }
DesugarTaskDelegate(
projectClasses = projectClasses.files,
subProjectClasses = subProjectClasses.files,
externaLibsClasses = libs,
desugaringClasspath = desugaringClasspath.files,
projectOutput = projectOutput.asFile.get(),
subProjectOutput = subProjectOutput.asFile.get(),
externalLibsOutput = externalLibsOutput.asFile.get(),
tmpDir = tmpDir.asFile.get(),
bootClasspath = bootClasspath.files,
minSdk = minSdk.get(),
enableBugFixForJacoco = enableBugFixForJacoco.get(),
verbose = Logger.getLogger(DesugarTask::class.java).isDebugEnabled,
executorFacade = executorFacade
).doProcess()
}
}
class CreationAction(componentProperties: ComponentPropertiesImpl) :
VariantTaskCreationAction<DesugarTask, ComponentPropertiesImpl>(
componentProperties
) {
override val name: String = computeTaskName("desugar")
override val type: Class<DesugarTask> = DesugarTask::class.java
private val projectClasses: FileCollection
init {
projectClasses =
componentProperties.transformManager.getPipelineOutputAsFileCollection { types, scopes ->
QualifiedContent.DefaultContentType.CLASSES in types &&
scopes == setOf(QualifiedContent.Scope.PROJECT)
}
componentProperties.transformManager.consumeStreams(
mutableSetOf(
QualifiedContent.Scope.PROJECT,
QualifiedContent.Scope.SUB_PROJECTS,
QualifiedContent.Scope.EXTERNAL_LIBRARIES
), setOf(QualifiedContent.DefaultContentType.CLASSES)
)
// Put processed classes back into Transform pipeline, external transforms may be
// consuming them.
mapOf(
InternalArtifactType.DESUGAR_PROJECT_CLASSES to QualifiedContent.Scope.PROJECT,
InternalArtifactType.DESUGAR_SUB_PROJECT_CLASSES to QualifiedContent.Scope.SUB_PROJECTS,
InternalArtifactType.DESUGAR_EXTERNAL_LIBS_CLASSES to QualifiedContent.Scope.EXTERNAL_LIBRARIES
).forEach { (output, scope) ->
val processedClasses = componentProperties.globalScope.project.files(
componentProperties.artifacts.get(output)
)
.asFileTree
componentProperties
.transformManager
.addStream(
OriginalStream.builder(
componentProperties.globalScope.project,
"desugared-classes-${scope.name}"
)
.addContentTypes(TransformManager.CONTENT_CLASS)
.addScope(scope)
.setFileCollection(processedClasses)
.build()
)
}
}
override fun handleProvider(
taskProvider: TaskProvider<DesugarTask>
) {
super.handleProvider(taskProvider)
creationConfig.artifacts.setInitialProvider(
taskProvider,
DesugarTask::projectOutput
).on(InternalArtifactType.DESUGAR_PROJECT_CLASSES)
creationConfig.artifacts.setInitialProvider(
taskProvider,
DesugarTask::subProjectOutput
).on(InternalArtifactType.DESUGAR_SUB_PROJECT_CLASSES)
creationConfig.artifacts.setInitialProvider(
taskProvider,
DesugarTask::externalLibsOutput
).on(InternalArtifactType.DESUGAR_EXTERNAL_LIBS_CLASSES)
creationConfig.artifacts.setInitialProvider(
taskProvider,
DesugarTask::tmpDir
).on(InternalArtifactType.DESUGAR_LOCAL_STATE_OUTPUT)
}
override fun configure(
task: DesugarTask
) {
super.configure(task)
val variantScope = creationConfig.variantScope
task.minSdk.set(creationConfig.minSdkVersion.featureLevel)
/**
* If a fix in Desugar should be enabled to handle broken bytecode produced by older
* Jacoco, see http://b/62623509.
*/
val enableDesugarBugFixForJacoco = try {
val current = GradleVersion.parse(JacocoTask.getJacocoVersion(creationConfig))
JacocoConfigurations.MIN_WITHOUT_BROKEN_BYTECODE > current
} catch (ignored: Throwable) {
// Cannot determine using version comparison, avoid passing the flag.
true
}
task.enableBugFixForJacoco.set(enableDesugarBugFixForJacoco)
task.projectClasses.from(projectClasses)
task.subProjectClasses.from(
creationConfig.variantDependencies.getArtifactFileCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.PROJECT,
AndroidArtifacts.ArtifactType.CLASSES_JAR
)
)
creationConfig.artifacts.setTaskInputToFinalProduct(
InternalArtifactType.FIXED_STACK_FRAMES,
task.externaLibsClasses
)
task.desugaringClasspath.from(variantScope.providedOnlyClasspath)
task.bootClasspath.from(variantScope.bootClasspath)
creationConfig.onTestedConfig {
task.desugaringClasspath.from(
it.variantDependencies.getArtifactCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.ALL,
AndroidArtifacts.ArtifactType.CLASSES_JAR
).artifactFiles
)
}
}
}
}
class DesugarTaskDelegate(
private val projectClasses: Set<File>,
private val subProjectClasses: Set<File>,
private val externaLibsClasses: List<File>,
private val desugaringClasspath: Set<File>,
private val projectOutput: File,
private val subProjectOutput: File,
private val externalLibsOutput: File,
private val tmpDir: File,
private val bootClasspath: Set<File>,
private val minSdk: Int,
private val enableBugFixForJacoco: Boolean,
private val verbose: Boolean,
private val executorFacade: WorkerExecutorFacade
) {
fun doProcess() {
FileUtils.cleanOutputDir(projectOutput)
FileUtils.cleanOutputDir(subProjectOutput)
FileUtils.cleanOutputDir(externalLibsOutput)
FileUtils.cleanOutputDir(tmpDir)
val inputToOutputs = mutableMapOf<Path, Path>()
projectClasses.forEachIndexed { index, file ->
inputToOutputs[file.toPath()] = projectOutput.resolve("$index.jar").toPath()
}
subProjectClasses.forEachIndexed { index, file ->
inputToOutputs[file.toPath()] = subProjectOutput.resolve("$index.jar").toPath()
}
externaLibsClasses.forEachIndexed { index, file ->
inputToOutputs[file.toPath()] = externalLibsOutput.resolve("$index.jar").toPath()
}
val classpath =
(projectClasses + subProjectClasses + externaLibsClasses + desugaringClasspath).map { it.path }
val processArgs =
getProcessArgs(inputToOutputs, classpath, bootClasspath.map { it.path })
for (processArg in processArgs) {
val lambdaDir = PathUtils.createTmpDirToRemoveOnShutdown("gradle_lambdas")
val isWindows = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS
val desugarArgs = processArg.getArgs(isWindows)
loggerWrapper.info("Desugar process args: $desugarArgs")
executorFacade.submit(
DesugarWorkerItem.DesugarAction::class.java, WorkerExecutorFacade.Configuration(
isolationMode = WorkerExecutorFacade.IsolationMode.PROCESS,
classPath = setOf(desugarJar.value),
parameter = DesugarWorkerItem.DesugarActionParams(desugarArgs),
jvmArgs = listOf(
"-Xmx64m",
"-Djdk.internal.lambda.dumpProxyClasses=$lambdaDir"
)
)
)
}
}
private fun getProcessArgs(
inputToOutput: Map<Path, Path>,
classpath: List<String>, bootclasspath: List<String>
): List<DesugarProcessArgs> {
val parallelExecutions = Runtime.getRuntime().availableProcessors() / 2
val procBuckets = ArrayListMultimap.create<Int, Map.Entry<Path, Path>>()
for ((index, entry) in inputToOutput.entries.withIndex()) {
val bucketId = index % parallelExecutions
procBuckets.put(bucketId, entry)
}
val args = ArrayList<DesugarProcessArgs>(procBuckets.keySet().size)
for (bucketId in procBuckets.keySet()) {
val inToOut = mutableMapOf<String, String>()
for (e in procBuckets.get(bucketId)) {
inToOut[e.key.toString()] = e.value.toString()
}
val processArgs = DesugarProcessArgs(
inToOut,
classpath,
bootclasspath,
tmpDir.toString(),
verbose,
minSdk,
enableBugFixForJacoco
)
args.add(processArgs)
}
return args
}
companion object {
val desugarJar = lazy(LazyThreadSafetyMode.SYNCHRONIZED) { initDesugarJar() }
/** Set this location of extracted desugar jar that is used for processing. */
private fun initDesugarJar(): File {
val url = DesugarProcessBuilder::class.java.classLoader.getResource(DESUGAR_JAR)!!
val extractedDesugar = PathUtils.createTmpToRemoveOnShutdown(DESUGAR_JAR)
url.openConnection().getInputStream().buffered().use { inputStream ->
Files.copy(
inputStream,
extractedDesugar,
StandardCopyOption.REPLACE_EXISTING
)
}
return extractedDesugar.toFile()
}
}
}
private val loggerWrapper = LoggerWrapper.getLogger(DesugarTask::class.java)
private const val DESUGAR_JAR = "desugar_deploy.jar"