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