Skip to content

Instantly share code, notes, and snippets.

@NikolaDespotoski
Last active October 10, 2025 14:20
Show Gist options
  • Save NikolaDespotoski/acce94ac347a152df2afbcb14d9ff41b to your computer and use it in GitHub Desktop.
Save NikolaDespotoski/acce94ac347a152df2afbcb14d9ff41b to your computer and use it in GitHub Desktop.
Generate proguard rules out of output of apiDump task of Kotlin binary compatibility plugin
tasks.register("updateProguardFromApiDump") {
group = "verification"
description =
"Updates proguard-rules.pro with -keep rules for public classes found in a .api dump (default: uses the first .api file under api/)."
dependsOn("apiDump")
doLast {
val apiDir = file("api")
val proguardFile = file("proguard-rules.pro")
val apiFilePath = if (project.hasProperty("apiFile")) project.property("apiFile") else null
val apiFile = if (apiFilePath != null) {
file(apiFilePath.toString())
} else {
val candidates =
apiDir.listFiles { _: File, name: String -> name.endsWith(".api") }.orEmpty()
if (candidates.isEmpty()) {
throw GradleException("No .api files found in $apiDir")
}
println("No -PapiFile specified, using: ${candidates.first()}")
}
val apiRelativePath =
project.rootDir.toPath().relativize(apiFile.toPath()).toString().replace("\\", "/")
// Check if the .api file is modified in the current changelist (uncommitted)
val statusProc = ProcessBuilder("git", "status", "--porcelain", apiRelativePath)
.directory(project.rootDir)
.start()
statusProc.waitFor()
val statusOut = statusProc.inputStream.bufferedReader().readText().trim()
if (statusOut.isEmpty()) {
println("No uncommitted changes to ${apiRelativePath}. Skipping ProGuard rules update.")
return@doLast
} else {
println("Uncommitted changes to $apiRelativePath found. Updating ProGuard rules.")
}
if (!apiFile.exists()) {
println(".api dump not found at ${apiFile}. Attempting to resolve from git...")
val proc = ProcessBuilder("git", "checkout", "HEAD", "--", apiRelativePath)
.directory(project.rootDir)
.start()
proc.waitFor()
if (!apiFile.exists()) {
throw GradleException(".api dump could not be resolved from git. Make sure you have committed the file.")
} else {
println(".api file resolved from git.")
}
}
// Step 1. Extract all public top-level classes from API dump
val apiLines = apiFile.readLines()
val classPattern = Regex("^public (((final|abstract) )?(class|interface|enum)) ([^ ]+) .*")
val classNames = apiLines.filter { it.matches(classPattern) }.mapNotNull { line ->
val match = classPattern.find(line) ?: return@mapNotNull null
val className = match.groupValues[5]
className.replace("/", ".")
}
// Step 2. Prepare proguard keep rules
val newRules = classNames.map { "-keep public class $it { public *; }" }
val proguardText = proguardFile.readText()
val missingRules = newRules.filter { rule ->
!proguardText.contains(rule)
}
if (missingRules.isNotEmpty()) {
missingRules.forEach { rule ->
proguardFile.appendText("$rule\n")
}
println("Added new ProGuard rules from ${apiFile.name}:\n${missingRules.joinToString("\n")}")
} else {
println("No new ProGuard rules to add from ${apiFile.name}.")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment