blob: 7e150a84d2f890bdd70acf6ee2fb413f1756a2f4 [file] [log] [blame]
/*
* Copyright 2019 the original author or authors.
*
* 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 org.gradle.instantexecution.serialization.codecs
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.GeneratedSubclasses
import org.gradle.api.internal.TaskInputsInternal
import org.gradle.api.internal.TaskOutputsInternal
import org.gradle.api.internal.project.ProjectState
import org.gradle.api.internal.project.ProjectStateRegistry
import org.gradle.api.internal.tasks.properties.InputFilePropertyType
import org.gradle.api.internal.tasks.properties.OutputFilePropertyType
import org.gradle.api.internal.tasks.properties.PropertyValue
import org.gradle.api.internal.tasks.properties.PropertyVisitor
import org.gradle.api.tasks.FileNormalizer
import org.gradle.instantexecution.ClassicModeBuild
import org.gradle.instantexecution.extensions.uncheckedCast
import org.gradle.instantexecution.runToCompletion
import org.gradle.instantexecution.serialization.IsolateContext
import org.gradle.instantexecution.serialization.IsolateOwner
import org.gradle.instantexecution.serialization.MutableIsolateContext
import org.gradle.instantexecution.serialization.MutableReadContext
import org.gradle.instantexecution.serialization.MutableWriteContext
import org.gradle.instantexecution.serialization.PropertyKind
import org.gradle.instantexecution.serialization.PropertyTrace
import org.gradle.instantexecution.serialization.ReadContext
import org.gradle.instantexecution.serialization.WriteContext
import org.gradle.instantexecution.serialization.beans.BeanPropertyWriter
import org.gradle.instantexecution.serialization.beans.readEachProperty
import org.gradle.instantexecution.serialization.beans.writeNextProperty
import org.gradle.instantexecution.serialization.beans.writingProperties
import org.gradle.instantexecution.serialization.readCollectionInto
import org.gradle.instantexecution.serialization.readEnum
import org.gradle.instantexecution.serialization.readStrings
import org.gradle.instantexecution.serialization.withIsolate
import org.gradle.instantexecution.serialization.withPropertyTrace
import org.gradle.instantexecution.serialization.writeCollection
import org.gradle.instantexecution.serialization.writeEnum
import org.gradle.instantexecution.serialization.writeStrings
internal
class TaskGraphCodec(private val projectStateRegistry: ProjectStateRegistry) {
fun MutableWriteContext.writeTaskGraphOf(build: ClassicModeBuild, tasks: List<Task>) {
writeCollection(tasks) { task ->
try {
runToCompletionWithMutableStateOf(task.project) {
writeTask(task, build.dependenciesOf(task))
}
} catch (e: Exception) {
throw GradleException("Could not save state of $task.", e)
}
}
}
suspend fun MutableReadContext.readTaskGraph(): List<Task> {
val tasksWithDependencies = readTasksWithDependencies()
wireTaskDependencies(tasksWithDependencies)
return tasksWithDependencies.map { (task, _) -> task }
}
private
suspend fun MutableReadContext.readTasksWithDependencies(): List<Pair<Task, List<String>>> =
readCollectionInto({ size -> ArrayList(size) }) {
readTask()
}
private
fun wireTaskDependencies(tasksWithDependencies: List<Pair<Task, List<String>>>) {
val tasksByPath = tasksWithDependencies.associate { (task, _) ->
task.path to task
}
tasksWithDependencies.forEach { (task, dependencies) ->
task.dependsOn(dependencies.map(tasksByPath::getValue))
}
}
private
suspend fun MutableWriteContext.writeTask(task: Task, dependencies: Set<Task>) {
val taskType = GeneratedSubclasses.unpack(task.javaClass)
writeClass(taskType)
writeString(task.project.path)
writeString(task.name)
writeStrings(dependencies.map { it.path })
withTaskOf(taskType, task) {
beanPropertyWriterFor(taskType).run {
writeFieldsOf(task)
writeRegisteredPropertiesOf(task, this)
}
}
}
private
suspend fun MutableReadContext.readTask(): Pair<Task, List<String>> {
val taskType = readClass().asSubclass(Task::class.java)
val projectPath = readString()
val taskName = readString()
val taskDependencies = readStrings()
val task = createTask(projectPath, taskName, taskType)
withTaskOf(taskType, task) {
beanPropertyReaderFor(taskType).run {
readFieldsOf(task)
readRegisteredPropertiesOf(task)
}
}
return task to taskDependencies
}
/**
* Runs the suspending [block] to completion against the [public mutable state][ProjectState.withMutableState] of [project].
*/
private
fun runToCompletionWithMutableStateOf(project: Project, block: suspend () -> Unit) {
projectStateRegistry.stateFor(project).withMutableState {
runToCompletion(block)
}
}
}
private
inline fun <T> T.withTaskOf(
taskType: Class<*>,
task: Task,
action: () -> Unit
) where T : IsolateContext, T : MutableIsolateContext {
withIsolate(IsolateOwner.OwnerTask(task)) {
withPropertyTrace(PropertyTrace.Task(taskType, task.path)) {
action()
}
}
}
private
sealed class RegisteredProperty {
data class Input(
val propertyName: String,
val propertyValue: PropertyValue,
val optional: Boolean
) : RegisteredProperty()
data class InputFile(
val propertyName: String,
val propertyValue: PropertyValue,
val optional: Boolean,
val filePropertyType: InputFilePropertyType,
val skipWhenEmpty: Boolean,
val incremental: Boolean,
val fileNormalizer: Class<out FileNormalizer>?
) : RegisteredProperty()
data class OutputFile(
val propertyName: String,
val propertyValue: PropertyValue,
val optional: Boolean,
val filePropertyType: OutputFilePropertyType
) : RegisteredProperty()
}
private
suspend fun WriteContext.writeRegisteredPropertiesOf(
task: Task,
propertyWriter: BeanPropertyWriter
) = propertyWriter.run {
suspend fun writeProperty(propertyName: String, propertyValue: PropertyValue, kind: PropertyKind): Boolean {
val value = unpack(propertyValue.call()) ?: return false
return writeNextProperty(propertyName, value, kind)
}
suspend fun writeInputProperty(propertyName: String, propertyValue: PropertyValue): Boolean =
writeProperty(propertyName, propertyValue, PropertyKind.InputProperty)
suspend fun writeOutputProperty(propertyName: String, propertyValue: PropertyValue): Boolean =
writeProperty(propertyName, propertyValue, PropertyKind.OutputProperty)
writingProperties {
val properties = collectRegisteredInputsOf(task)
properties.forEach { property ->
property.run {
when (this) {
is RegisteredProperty.InputFile -> {
if (writeInputProperty(propertyName, propertyValue)) {
writeBoolean(optional)
writeBoolean(true)
writeEnum(filePropertyType)
writeBoolean(skipWhenEmpty)
writeClass(fileNormalizer!!)
}
}
is RegisteredProperty.Input -> {
if (writeInputProperty(propertyName, propertyValue)) {
writeBoolean(optional)
writeBoolean(false)
}
}
}
}
}
}
writingProperties {
val properties = collectRegisteredOutputsOf(task)
properties.forEach {
it.run {
if (writeOutputProperty(propertyName, propertyValue)) {
writeBoolean(optional)
writeEnum(filePropertyType)
}
}
}
}
}
private
fun collectRegisteredOutputsOf(task: Task): List<RegisteredProperty.OutputFile> {
val properties = mutableListOf<RegisteredProperty.OutputFile>()
(task.outputs as TaskOutputsInternal).visitRegisteredProperties(object : PropertyVisitor.Adapter() {
override fun visitOutputFileProperty(
propertyName: String,
optional: Boolean,
value: PropertyValue,
filePropertyType: OutputFilePropertyType
) {
properties.add(
RegisteredProperty.OutputFile(
propertyName,
value,
optional,
filePropertyType
)
)
}
})
return properties
}
private
fun collectRegisteredInputsOf(task: Task): List<RegisteredProperty> {
val properties = mutableListOf<RegisteredProperty>()
(task.inputs as TaskInputsInternal).visitRegisteredProperties(object : PropertyVisitor.Adapter() {
override fun visitInputFileProperty(
propertyName: String,
optional: Boolean,
skipWhenEmpty: Boolean,
incremental: Boolean,
fileNormalizer: Class<out FileNormalizer>?,
propertyValue: PropertyValue,
filePropertyType: InputFilePropertyType
) {
properties.add(
RegisteredProperty.InputFile(
propertyName,
propertyValue,
optional,
filePropertyType,
skipWhenEmpty,
incremental,
fileNormalizer
)
)
}
override fun visitInputProperty(
propertyName: String,
propertyValue: PropertyValue,
optional: Boolean
) {
properties.add(
RegisteredProperty.Input(
propertyName,
propertyValue,
optional
)
)
}
})
return properties
}
private
suspend fun ReadContext.readRegisteredPropertiesOf(task: Task) {
readInputPropertiesOf(task)
readOutputPropertiesOf(task)
}
private
suspend fun ReadContext.readInputPropertiesOf(task: Task) =
readEachProperty(PropertyKind.InputProperty) { propertyName, propertyValue ->
val optional = readBoolean()
val isFileInputProperty = readBoolean()
require(propertyValue != null)
when {
isFileInputProperty -> {
val filePropertyType = readEnum<InputFilePropertyType>()
val skipWhenEmpty = readBoolean()
val normalizer = readClass()
task.inputs.run {
when (filePropertyType) {
InputFilePropertyType.FILE -> file(propertyValue)
InputFilePropertyType.DIRECTORY -> dir(propertyValue)
InputFilePropertyType.FILES -> files(propertyValue)
}
}.run {
withPropertyName(propertyName)
optional(optional)
skipWhenEmpty(skipWhenEmpty)
withNormalizer(normalizer.uncheckedCast())
}
}
else -> {
task.inputs
.property(propertyName, propertyValue)
.optional(optional)
}
}
}
private
suspend fun ReadContext.readOutputPropertiesOf(task: Task) =
readEachProperty(PropertyKind.OutputProperty) { propertyName, propertyValue ->
val optional = readBoolean()
val filePropertyType = readEnum<OutputFilePropertyType>()
require(propertyValue != null)
task.outputs.run {
when (filePropertyType) {
OutputFilePropertyType.DIRECTORY -> dir(propertyValue)
OutputFilePropertyType.DIRECTORIES -> dirs(propertyValue)
OutputFilePropertyType.FILE -> file(propertyValue)
OutputFilePropertyType.FILES -> files(propertyValue)
}
}.run {
withPropertyName(propertyName)
optional(optional)
}
}
private
fun ReadContext.createTask(projectPath: String, taskName: String, taskClass: Class<out Task>) =
getProject(projectPath).tasks.createWithoutConstructor(taskName, taskClass)