1 /*
<lambda>null2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.build
18
19 import java.io.File
20 import org.gradle.api.DefaultTask
21 import org.gradle.api.GradleException
22 import org.gradle.api.Project
23 import org.gradle.api.Task
24 import org.gradle.api.file.RegularFileProperty
25 import org.gradle.api.tasks.CacheableTask
26 import org.gradle.api.tasks.Input
27 import org.gradle.api.tasks.OutputFile
28 import org.gradle.api.tasks.TaskAction
29
30 /** Finds the outputs of every task and saves this mapping into a file */
31 @CacheableTask
32 abstract class ListTaskOutputsTask : DefaultTask() {
33 @OutputFile val outputFile: RegularFileProperty = project.objects.fileProperty()
34 @Input val removePrefixes: MutableList<String> = mutableListOf()
35 @Input val tasks: MutableList<Task> = mutableListOf()
36
37 @get:Input val outputText by lazy { computeOutputText() }
38
39 init {
40 group = "Help"
41 // compute the output text when the taskgraph is ready so that the output text can be
42 // saved in the configuration cache and not generate a configuration cache violation
43 project.gradle.taskGraph.whenReady { outputText }
44 }
45
46 fun setOutput(f: File) {
47 outputFile.set(f)
48 description = "Finds the outputs of every task and saves the resulting mapping into $f"
49 }
50
51 fun removePrefix(prefix: String) {
52 removePrefixes.add("$prefix/")
53 }
54
55 // Given a map from output file to Task, formats into a String
56 private fun formatTasks(tasksByOutput: Map<File, Task>): String {
57 val messages: MutableList<String> = mutableListOf()
58 for ((output, task) in tasksByOutput) {
59 var filePath = output.path
60 for (prefix in removePrefixes) {
61 filePath = filePath.removePrefix(prefix)
62 }
63
64 messages.add(
65 formatInColumns(
66 listOf(filePath, " - " + task.path + " (" + task::class.qualifiedName + ")")
67 )
68 )
69 }
70 messages.sort()
71 return messages.joinToString("\n")
72 }
73
74 // Given a list of columns, indents and joins them to be easy to read
75 private fun formatInColumns(columns: List<String>): String {
76 val components = mutableListOf<String>()
77 var textLength = 0
78 for (column in columns) {
79 val roundedTextLength =
80 if (textLength == 0) {
81 textLength
82 } else {
83 ((textLength / 32) + 1) * 32
84 }
85 val extraSpaces = " ".repeat(roundedTextLength - textLength)
86 components.add(extraSpaces)
87 textLength = roundedTextLength
88 components.add(column)
89 textLength += column.length
90 }
91 return components.joinToString("")
92 }
93
94 fun computeOutputText(): String {
95 val tasksByOutput = project.rootProject.findAllTasksByOutput()
96 return formatTasks(tasksByOutput)
97 }
98
99 @TaskAction
100 fun exec() {
101 val outputFile = outputFile.get()
102 outputFile.asFile.writeText(outputText)
103 }
104 }
105
106 // TODO(149103692): remove all elements of this set
107 val taskNamesKnownToDuplicateOutputs =
108 setOf(
109 // Instead of adding new elements to this set, prefer to disable unused tasks when possible
110
111 // b/308798582
112 "transformNonJvmMainCInteropDependenciesMetadataForIde",
113 "transformDarwinTestCInteropDependenciesMetadataForIde",
114 "transformDarwinMainCInteropDependenciesMetadataForIde",
115 "transformCommonMainCInteropDependenciesMetadataForIde",
116 "transformCommonTestCInteropDependenciesMetadataForIde",
117 "transformIosMainCInteropDependenciesMetadataForIde",
118 "transformIosTestCInteropDependenciesMetadataForIde",
119 "transformNativeTestCInteropDependenciesMetadataForIde",
120 "transformNativeMainCInteropDependenciesMetadataForIde",
121 "transformLinuxMainCInteropDependenciesMetadataForIde",
122 "transformNonJvmCommonMainCInteropDependenciesMetadataForIde",
123
124 // The following tests intentionally have the same output of golden images
125 "updateGoldenDesktopTest",
126 "updateGoldenDebugUnitTest",
127
128 // The following tasks have the same output file:
129 // ../../prebuilts/androidx/javascript-for-kotlin/yarn.lock
130 "kotlinRestoreYarnLock",
131 "kotlinNpmInstall",
132 "kotlinUpgradePackageLock",
133 "kotlinUpgradeYarnLock",
134 "kotlinStorePackageLock",
135 "kotlinStoreYarnLock",
136
137 // The following tasks have the same output configFile file:
138 // projectBuildDir/js/packages/projectName-wasm-js/webpack.config.js
139 // Remove when https://youtrack.jetbrains.com/issue/KT-70029 / b/361319689 is resolved
140 // and set configFile location for each task
141 "wasmJsBrowserDevelopmentWebpack",
142 "wasmJsBrowserDevelopmentRun",
143 "wasmJsBrowserProductionWebpack",
144 "wasmJsBrowserProductionRun",
145 "jsTestTestDevelopmentExecutableCompileSync",
146
147 // Remove when https://youtrack.jetbrains.com/issue/KT-71688 is resolved and set
148 // destinationDirectory to the project's build directory
149 "wasmJsTestTestDevelopmentExecutableCompileSync",
150 "wasmJsTestTestProductionExecutableCompileSync",
151
152 // TODO file a bug
153 "kotlinNodeJsSetup",
154 )
155
shouldValidateTaskOutputnull156 fun shouldValidateTaskOutput(task: Task): Boolean {
157 if (!task.enabled) {
158 return false
159 }
160 return !taskNamesKnownToDuplicateOutputs.contains(task.name)
161 }
162
163 // For this project and all subprojects, collects all tasks and creates a map keyed by their output
164 // files
Projectnull165 fun Project.findAllTasksByOutput(): Map<File, Task> {
166 // find list of all tasks
167 val allTasks = mutableListOf<Task>()
168 project.allprojects { otherProject ->
169 otherProject.tasks.forEach { task -> allTasks.add(task) }
170 }
171
172 // group tasks by their outputs
173 val tasksByOutput: MutableMap<File, Task> = hashMapOf()
174 for (otherTask in allTasks) {
175 for (otherTaskOutput in otherTask.outputs.files.files) {
176 val existingTask = tasksByOutput[otherTaskOutput]
177 if (existingTask != null) {
178 if (shouldValidateTaskOutput(existingTask) && shouldValidateTaskOutput(otherTask)) {
179 throw GradleException(
180 "Output file " +
181 otherTaskOutput +
182 " was declared as an output of " +
183 "multiple tasks: " +
184 otherTask +
185 " and " +
186 existingTask
187 )
188 }
189 // if there is an exempt conflict, keep the alphabetically earlier task to ensure
190 // consistency
191 if (existingTask.path > otherTask.path) continue
192 }
193 tasksByOutput[otherTaskOutput] = otherTask
194 }
195 }
196 return tasksByOutput
197 }
198