<lambda>null1 #!/usr/bin/env kotlin
2 
3 /*
4  * Copyright 2024 The Android Open Source Project
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 /**
20  * To run .kts files, follow these steps:
21  *
22  * 1. Download and install the Kotlin compiler (kotlinc). There are several ways to do this; see
23  *    https://kotlinlang.org/docs/command-line.html
24  * 2. Run the script from the command line:
25  *    <path_to>/kotlinc -script <script_file>.kts <arguments>
26  */
27 
28 @file:Repository("https://repo1.maven.org/maven2")
29 @file:DependsOn("com.google.code.gson:gson:2.11.0")
30 
31 import java.io.File
32 import java.util.Locale
33 import kotlin.system.exitProcess
34 import com.google.gson.Gson
35 
36 if (args.isEmpty() || !args.contains("-input")) {
37     println("Expected path to buildHealth Advice json file using -input.")
38     println("Provide output file path using -output. By default, output.csv will be generated " +
39         "in the current directory")
40     println("Usage ex: kotlinc -script buildHealthAdviceToCsv.main.kts -- -input " +
41         "path/to/build-health-report.json -output /path/to/output.csv")
42     exitProcess(1)
43 }
44 
45 val input = args[1]
46 if (!File(input).exists()) {
47     println("Could not find input files: $input")
48     exitProcess(1)
49 }
50 val inputJsonFile = File(input)
51 println("Parsing ${inputJsonFile.path}...")
52 
53 val outputFile = if(args.contains("-output")) {
54     args[3]
55 } else {
56     "output.csv"
57 }
58 
59 val csvOutputFile = File(outputFile)
60 if(csvOutputFile.exists()) {
61     csvOutputFile.delete()
62 }
63 
64 val csvData = StringBuilder()
65 val columnHeaders = listOf(
66     "ID", // leave blank
67     "Priority",
68     "Summary",
69     "HotlistIds",
70     "Assignee", // leave blank
71     "Status",
72     "ComponentPath",
73     "Reporter",
74     "FirstNote"
75 )
76 csvData.append(columnHeaders.joinToString(","))
77 csvData.append("\n")
78 
79 val gson = Gson()
80 val advice: Advice = gson.fromJson(inputJsonFile.readLines().first(), Advice::class.java)
81 
82 //list of projects we want to file issues for
83 //val androidxProjects = listOf("lint", "lint-checks", "buildSrc-tests", "androidx-demos", "stableaidl", "test")
84 //val flanProjects = listOf("activity", "fragment", "lifecycle", "navigation")
85 
86 var numProjects = 0
projectAdvicenull87 advice.projectAdvice.forEach projectAdvice@{ projectAdvice ->
88     val projectPath = projectAdvice.projectPath
89     val title = "Please fix misconfigured dependencies for $projectPath"
90 
91     val project = projectPath.split(":")[1]
92 
93 //    Uncomment the following section if you want to create bugs for specific projects
94 //    if(project !in flanProjects) {
95 //        return@projectAdvice
96 //    }
97 
98     // Ignore advice for lint projects: b/350084892
99     if (projectPath.contains("lint")) {
100         return@projectAdvice
101     }
102 
103     val description = StringBuilder()
104     description.appendLine(
105         "The dependency analysis gradle plugin found some dependencies that may have been " +
106             "misconfigured. Please fix the following dependencies: \n"
107     )
108 
109     val unused = mutableSetOf<String>()
110     val transitive = mutableSetOf<String>()
111     val modified = mutableSetOf<String>()
112     projectAdvice.dependencyAdvice.forEach { dependencyAdvice ->
113         val fromConfiguration = dependencyAdvice.fromConfiguration
114         val toConfiguration = dependencyAdvice.toConfiguration
115         val coordinates = dependencyAdvice.coordinates
116         val resolvedVersion = coordinates.resolvedVersion
117 
118         val isCompileOnly = toConfiguration?.endsWith("compileOnly", ignoreCase = true) == true
119         val isModifyDependencyAdvice = fromConfiguration != null && toConfiguration != null
120         val isTransitiveDependencyAdvice = fromConfiguration == null && toConfiguration != null && !isCompileOnly
121         val isUnusedDependencyAdvice = fromConfiguration != null && toConfiguration == null
122 
123         // Ignore advice for androidx.profileinstaller:profileinstaller.
124         // It needs to remain implementation as that needs to be part of the manifest merger
125         // which is before runtime (b/355239547)
126         if(coordinates.identifier == "androidx.profileinstaller:profileinstaller") {
127             return@forEach
128         }
129 
130         var identifier = if(resolvedVersion == null) {
131             "'${coordinates.identifier}'"
132         } else {
133             "'${coordinates.identifier}:${coordinates.resolvedVersion}'"
134         }
135         if (coordinates.type == "project") {
136             identifier = "project($identifier)"
137         }
138         if (isModifyDependencyAdvice) {
139             modified.add("$toConfiguration($identifier) (was $fromConfiguration)")
140         }
141         if(isTransitiveDependencyAdvice) {
142             transitive.add("$toConfiguration($identifier)")
143         }
144         if(isUnusedDependencyAdvice) {
145             unused.add("$fromConfiguration($identifier)")
146         }
147     }
148 
149     if(unused.isNotEmpty()) {
150         description.appendLine("Unused dependencies which should be removed:")
151         description.appendLine("```")
152         description.appendLine(unused.sorted().joinToString(separator = "\n"))
153         description.appendLine("```")
154 
155     }
156     if (transitive.isNotEmpty()) {
157         description.appendLine("These transitive dependencies can be declared directly:")
158         description.appendLine("```")
159         description.appendLine(transitive.sorted().joinToString(separator = "\n"))
160         description.appendLine("```")
161     }
162     if (modified.isNotEmpty()) {
163         description.appendLine(
164             "These dependencies can be modified to be as indicated. Please be careful " +
165                 "while changing the type of dependencies since it can affect the consumers of " +
166                 "this library. To learn more about the various dependency configurations, " +
167                 "please visit: [dac]" +
168                 "(https://developer.android.com/build/dependencies#dependency_configurations). " +
169                 "Also check [Gradle's guide for dependency management]" +
170                 "(https://docs.gradle.org/current/userguide/dependency_management.html).\n"
171         )
172         description.appendLine("```")
173         description.appendLine(modified.sorted().joinToString(separator = "\n"))
174         description.appendLine("```")
175     }
176 
177     description.appendLine("To get more up-to-date project advice, please run:")
178     description.appendLine("```")
179     description.appendLine("./gradlew $projectPath:projectHealth")
180     description.appendLine("```")
181     val newColumns = listOf(
182         "NEW00000", // ID
183         "P2", // priority
184         title,
185         "=HYPERLINK(\"https://issuetracker.google.com/issues?q=status%3Aopen%20hotlistid%3A(5997499)\", \"Androidx misconfigured dependencies\")",
186         "", // Assignee: leave blank
187         "Assigned", // status
188         "Android > Android OS & Apps > Jetpack (androidx) > ${project.replaceFirstChar {
189             if (it.isLowerCase()) it.titlecase(
190                 Locale.getDefault()
191             ) else it.toString()
192         }}",
193         "", // reporter: add your ldap here
194         description.toString()
195     )
196 
197     if (projectAdvice.dependencyAdvice.isNotEmpty()) {
198         numProjects++
199         csvData.append(newColumns.joinToString(",") { data ->
200             "\"${data.replace("\"", "\"\"")}\""
201         })
202         csvData.append("\n")
203     }
204 }
205 
206 csvOutputFile.appendText(csvData.toString())
207 println("Wrote CSV output to ${csvOutputFile.path} for $numProjects projects")
208 
209 data class Advice(
210     val projectAdvice: List<ProjectAdvice>,
211 )
212 
213 data class ProjectAdvice(
214     val projectPath: String,
215     val dependencyAdvice: List<DependencyAdvice>,
216     val pluginAdvice: List<PluginAdvice>,
217 )
218 
219 data class DependencyAdvice(
220     val coordinates: Coordinates,
221     val fromConfiguration: String?,
222     val toConfiguration: String?
223 )
224 
225 data class Coordinates(
226     val type: String,
227     val identifier: String,
228     val resolvedVersion: String?
229 )
230 
231 data class PluginAdvice(
232     val redundantPlugin: String,
233     val reason: String
234 )
235