blob: ca604aa16a13716b232d6a4a947e1ca1d302c581 [file] [log] [blame]
Radha Nakade3556e792024-06-07 11:43:50 -07001#!/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
31import java.io.File
32import java.util.Locale
33import kotlin.system.exitProcess
34import com.google.gson.Gson
35
36if (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
45val input = args[1]
46if (!File(input).exists()) {
47 println("Could not find input files: $input")
48 exitProcess(1)
49}
50val inputJsonFile = File(input)
51println("Parsing ${inputJsonFile.path}...")
52
53val outputFile = if(args.contains("-output")) {
54 args[3]
55} else {
56 "output.csv"
57}
58
59val csvOutputFile = File(outputFile)
60if(csvOutputFile.exists()) {
61 csvOutputFile.delete()
62}
63
64val csvData = StringBuilder()
65val 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)
76csvData.append(columnHeaders.joinToString(","))
77csvData.append("\n")
78
79val gson = Gson()
80val 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
86var numProjects = 0
87advice.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
Radha Nakadeb3382892024-07-26 13:57:50 -070098 // Ignore advice for lint projects: b/350084892
99 if (projectPath.contains("lint")) {
100 return@projectAdvice
101 }
102
Radha Nakade3556e792024-06-07 11:43:50 -0700103 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
Radha Nakadeb3382892024-07-26 13:57:50 -0700123 // 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
Radha Nakade3556e792024-06-07 11:43:50 -0700130 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
206csvOutputFile.appendText(csvData.toString())
207println("Wrote CSV output to ${csvOutputFile.path} for $numProjects projects")
208
209data class Advice(
210 val projectAdvice: List<ProjectAdvice>,
211)
212
213data class ProjectAdvice(
214 val projectPath: String,
215 val dependencyAdvice: List<DependencyAdvice>,
216 val pluginAdvice: List<PluginAdvice>,
217)
218
219data class DependencyAdvice(
220 val coordinates: Coordinates,
221 val fromConfiguration: String?,
222 val toConfiguration: String?
223)
224
225data class Coordinates(
226 val type: String,
227 val identifier: String,
228 val resolvedVersion: String?
229)
230
231data class PluginAdvice(
232 val redundantPlugin: String,
233 val reason: String
234)