blob: ca604aa16a13716b232d6a4a947e1ca1d302c581 [file] [log] [blame]
#!/usr/bin/env kotlin
/*
* Copyright 2024 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.
*/
/**
* To run .kts files, follow these steps:
*
* 1. Download and install the Kotlin compiler (kotlinc). There are several ways to do this; see
* https://kotlinlang.org/docs/command-line.html
* 2. Run the script from the command line:
* <path_to>/kotlinc -script <script_file>.kts <arguments>
*/
@file:Repository("https://repo1.maven.org/maven2")
@file:DependsOn("com.google.code.gson:gson:2.11.0")
import java.io.File
import java.util.Locale
import kotlin.system.exitProcess
import com.google.gson.Gson
if (args.isEmpty() || !args.contains("-input")) {
println("Expected path to buildHealth Advice json file using -input.")
println("Provide output file path using -output. By default, output.csv will be generated " +
"in the current directory")
println("Usage ex: kotlinc -script buildHealthAdviceToCsv.main.kts -- -input " +
"path/to/build-health-report.json -output /path/to/output.csv")
exitProcess(1)
}
val input = args[1]
if (!File(input).exists()) {
println("Could not find input files: $input")
exitProcess(1)
}
val inputJsonFile = File(input)
println("Parsing ${inputJsonFile.path}...")
val outputFile = if(args.contains("-output")) {
args[3]
} else {
"output.csv"
}
val csvOutputFile = File(outputFile)
if(csvOutputFile.exists()) {
csvOutputFile.delete()
}
val csvData = StringBuilder()
val columnHeaders = listOf(
"ID", // leave blank
"Priority",
"Summary",
"HotlistIds",
"Assignee", // leave blank
"Status",
"ComponentPath",
"Reporter",
"FirstNote"
)
csvData.append(columnHeaders.joinToString(","))
csvData.append("\n")
val gson = Gson()
val advice: Advice = gson.fromJson(inputJsonFile.readLines().first(), Advice::class.java)
//list of projects we want to file issues for
//val androidxProjects = listOf("lint", "lint-checks", "buildSrc-tests", "androidx-demos", "stableaidl", "test")
//val flanProjects = listOf("activity", "fragment", "lifecycle", "navigation")
var numProjects = 0
advice.projectAdvice.forEach projectAdvice@{ projectAdvice ->
val projectPath = projectAdvice.projectPath
val title = "Please fix misconfigured dependencies for $projectPath"
val project = projectPath.split(":")[1]
// Uncomment the following section if you want to create bugs for specific projects
// if(project !in flanProjects) {
// return@projectAdvice
// }
// Ignore advice for lint projects: b/350084892
if (projectPath.contains("lint")) {
return@projectAdvice
}
val description = StringBuilder()
description.appendLine(
"The dependency analysis gradle plugin found some dependencies that may have been " +
"misconfigured. Please fix the following dependencies: \n"
)
val unused = mutableSetOf<String>()
val transitive = mutableSetOf<String>()
val modified = mutableSetOf<String>()
projectAdvice.dependencyAdvice.forEach { dependencyAdvice ->
val fromConfiguration = dependencyAdvice.fromConfiguration
val toConfiguration = dependencyAdvice.toConfiguration
val coordinates = dependencyAdvice.coordinates
val resolvedVersion = coordinates.resolvedVersion
val isCompileOnly = toConfiguration?.endsWith("compileOnly", ignoreCase = true) == true
val isModifyDependencyAdvice = fromConfiguration != null && toConfiguration != null
val isTransitiveDependencyAdvice = fromConfiguration == null && toConfiguration != null && !isCompileOnly
val isUnusedDependencyAdvice = fromConfiguration != null && toConfiguration == null
// Ignore advice for androidx.profileinstaller:profileinstaller.
// It needs to remain implementation as that needs to be part of the manifest merger
// which is before runtime (b/355239547)
if(coordinates.identifier == "androidx.profileinstaller:profileinstaller") {
return@forEach
}
var identifier = if(resolvedVersion == null) {
"'${coordinates.identifier}'"
} else {
"'${coordinates.identifier}:${coordinates.resolvedVersion}'"
}
if (coordinates.type == "project") {
identifier = "project($identifier)"
}
if (isModifyDependencyAdvice) {
modified.add("$toConfiguration($identifier) (was $fromConfiguration)")
}
if(isTransitiveDependencyAdvice) {
transitive.add("$toConfiguration($identifier)")
}
if(isUnusedDependencyAdvice) {
unused.add("$fromConfiguration($identifier)")
}
}
if(unused.isNotEmpty()) {
description.appendLine("Unused dependencies which should be removed:")
description.appendLine("```")
description.appendLine(unused.sorted().joinToString(separator = "\n"))
description.appendLine("```")
}
if (transitive.isNotEmpty()) {
description.appendLine("These transitive dependencies can be declared directly:")
description.appendLine("```")
description.appendLine(transitive.sorted().joinToString(separator = "\n"))
description.appendLine("```")
}
if (modified.isNotEmpty()) {
description.appendLine(
"These dependencies can be modified to be as indicated. Please be careful " +
"while changing the type of dependencies since it can affect the consumers of " +
"this library. To learn more about the various dependency configurations, " +
"please visit: [dac]" +
"(https://developer.android.com/build/dependencies#dependency_configurations). " +
"Also check [Gradle's guide for dependency management]" +
"(https://docs.gradle.org/current/userguide/dependency_management.html).\n"
)
description.appendLine("```")
description.appendLine(modified.sorted().joinToString(separator = "\n"))
description.appendLine("```")
}
description.appendLine("To get more up-to-date project advice, please run:")
description.appendLine("```")
description.appendLine("./gradlew $projectPath:projectHealth")
description.appendLine("```")
val newColumns = listOf(
"NEW00000", // ID
"P2", // priority
title,
"=HYPERLINK(\"https://issuetracker.google.com/issues?q=status%3Aopen%20hotlistid%3A(5997499)\", \"Androidx misconfigured dependencies\")",
"", // Assignee: leave blank
"Assigned", // status
"Android > Android OS & Apps > Jetpack (androidx) > ${project.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.getDefault()
) else it.toString()
}}",
"", // reporter: add your ldap here
description.toString()
)
if (projectAdvice.dependencyAdvice.isNotEmpty()) {
numProjects++
csvData.append(newColumns.joinToString(",") { data ->
"\"${data.replace("\"", "\"\"")}\""
})
csvData.append("\n")
}
}
csvOutputFile.appendText(csvData.toString())
println("Wrote CSV output to ${csvOutputFile.path} for $numProjects projects")
data class Advice(
val projectAdvice: List<ProjectAdvice>,
)
data class ProjectAdvice(
val projectPath: String,
val dependencyAdvice: List<DependencyAdvice>,
val pluginAdvice: List<PluginAdvice>,
)
data class DependencyAdvice(
val coordinates: Coordinates,
val fromConfiguration: String?,
val toConfiguration: String?
)
data class Coordinates(
val type: String,
val identifier: String,
val resolvedVersion: String?
)
data class PluginAdvice(
val redundantPlugin: String,
val reason: String
)