blob: a3e291710cffcf7fdadc558db52ee6b4ba83add8 [file] [log] [blame]
/*
* Copyright 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 androidx.build
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
/**
* Finds the outputs of every task and saves this mapping into a file
*/
@CacheableTask
abstract class ListTaskOutputsTask : DefaultTask() {
@OutputFile
val outputFile: Property<File> = project.objects.property(File::class.java)
@Input
val removePrefixes: MutableList<String> = mutableListOf()
@Input
val tasks: MutableList<Task> = mutableListOf()
@get:Input
val outputText by lazy { computeOutputText() }
init {
group = "Help"
// compute the output text when the taskgraph is ready so that the output text can be
// saved in the configuration cache and not generate a configuration cache violation
project.gradle.taskGraph.whenReady { outputText }
}
fun setOutput(f: File) {
outputFile.set(f)
description = "Finds the outputs of every task and saves the resulting mapping into $f"
}
fun removePrefix(prefix: String) {
removePrefixes.add("$prefix/")
}
// Given a map from output file to Task, formats into a String
private fun formatTasks(tasksByOutput: Map<File, Task>): String {
val messages: MutableList<String> = mutableListOf()
for ((output, task) in tasksByOutput) {
var filePath = output.path
for (prefix in removePrefixes) {
filePath = filePath.removePrefix(prefix)
}
messages.add(
formatInColumns(
listOf(filePath, " - " + task.path + " (" + task::class.qualifiedName + ")")
)
)
}
messages.sort()
return messages.joinToString("\n")
}
// Given a list of columns, indents and joins them to be easy to read
private fun formatInColumns(columns: List<String>): String {
val components = mutableListOf<String>()
var textLength = 0
for (column in columns) {
val roundedTextLength = if (textLength == 0) {
textLength
} else {
((textLength / 32) + 1) * 32
}
val extraSpaces = " ".repeat(roundedTextLength - textLength)
components.add(extraSpaces)
textLength = roundedTextLength
components.add(column)
textLength += column.length
}
return components.joinToString("")
}
fun computeOutputText(): String {
val tasksByOutput = project.rootProject.findAllTasksByOutput()
return formatTasks(tasksByOutput)
}
@TaskAction
fun exec() {
val outputFile = outputFile.get()
outputFile.writeText(outputText)
}
}
// TODO(149103692): remove all elements of this set
val taskNamesKnownToDuplicateOutputs = setOf(
"kotlinSourcesJar",
"releaseSourcesJar",
"sourceJarRelease",
"sourceJar",
// The following tests intentionally have the same output of golden images
"updateGoldenDesktopTest",
"updateGoldenDebugUnitTest"
)
fun shouldValidateTaskOutput(task: Task): Boolean {
if (!task.enabled) {
return false
}
return !taskNamesKnownToDuplicateOutputs.contains(task.name)
}
// For this project and all subprojects, collects all tasks and creates a map keyed by their output files
fun Project.findAllTasksByOutput(): Map<File, Task> {
// find list of all tasks
val allTasks = mutableListOf<Task>()
project.allprojects { otherProject ->
otherProject.tasks.forEach { task ->
allTasks.add(task)
}
}
// group tasks by their outputs
val tasksByOutput: MutableMap<File, Task> = hashMapOf()
for (otherTask in allTasks) {
for (otherTaskOutput in otherTask.outputs.files.files) {
val existingTask = tasksByOutput[otherTaskOutput]
if (existingTask != null) {
if (shouldValidateTaskOutput(existingTask) && shouldValidateTaskOutput(otherTask)) {
throw GradleException(
"Output file " + otherTaskOutput + " was declared as an output of " +
"multiple tasks: " + otherTask + " and " + existingTask
)
}
// if there is an exempt conflict, keep the alphabetically earlier task to ensure consistency
if (existingTask.path > otherTask.path)
continue
}
tasksByOutput[otherTaskOutput] = otherTask
}
}
return tasksByOutput
}