Support android builds via AGP

This commit is contained in:
You Qi
2023-04-27 21:39:08 +08:00
parent e4932d4ef9
commit c733e9af54
8 changed files with 80 additions and 38 deletions

View File

@@ -4,15 +4,17 @@ kotlin = "1.8.21"
ktlintGradle = "11.3.2" ktlintGradle = "11.3.2"
pluginPublish = "1.2.0" pluginPublish = "1.2.0"
versionCheck = "0.46.0" versionCheck = "0.46.0"
androidGradlePlugin = "7.3.0"
[plugins] [plugins]
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt"} detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintGradle"} ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintGradle" }
pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "pluginPublish"} pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "pluginPublish" }
versionCheck = { id = "com.github.ben-manes.versions", version.ref = "versionCheck"} versionCheck = { id = "com.github.ben-manes.versions", version.ref = "versionCheck" }
[libraries] [libraries]
junit = "junit:junit:4.13.2" junit = "junit:junit:4.13.2"
truth = "com.google.truth:truth:1.1.3" truth = "com.google.truth:truth:1.1.3"
asm = "org.ow2.asm:asm:9.4" asm = "org.ow2.asm:asm:9.4"
androidGradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }

View File

@@ -10,6 +10,7 @@ dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
implementation(gradleApi()) implementation(gradleApi())
implementation(libs.asm) implementation(libs.asm)
implementation(libs.androidGradlePlugin)
testImplementation(libs.junit) testImplementation(libs.junit)
testImplementation(libs.truth) testImplementation(libs.truth)

View File

@@ -1,35 +1,25 @@
package com.axzae.unmeta package com.axzae.unmeta
import org.gradle.api.logging.Logger
import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
class UnmetaClassVisitor( class UnmetaClassVisitor(
private val path: String, val path: String,
classVisitor: ClassVisitor, classVisitor: ClassVisitor,
private val logger: Logger,
) : ClassVisitor(Opcodes.ASM7, classVisitor), Opcodes { ) : ClassVisitor(Opcodes.ASM7, classVisitor), Opcodes {
var modified = false var isModified = false
private set
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? {
return when (desc) {
"Lkotlin/Metadata;" -> {
logger.debug("Removed @Metadata annotation from $path")
modified = true
null
}
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
return when (descriptor) {
"Lkotlin/coroutines/jvm/internal/DebugMetadata;" -> { "Lkotlin/coroutines/jvm/internal/DebugMetadata;" -> {
logger.debug("Removed @DebugMetadata annotation from $path") isModified = true
modified = true
null null
} }
else -> { else -> super.visitAnnotation(descriptor, visible)
super.visitAnnotation(desc, visible)
}
} }
} }
} }

View File

@@ -1,10 +1,13 @@
package com.axzae.unmeta package com.axzae.unmeta
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import javax.inject.Inject import javax.inject.Inject
const val DEFAULT_IS_ENABLED = true const val DEFAULT_IS_ENABLED = true
const val DEFAULT_VERBOSE = false
const val DEFAULT_OUTPUT_FILE = "outputs/logs/unmeta.txt"
@Suppress("UnnecessaryAbstractClass") @Suppress("UnnecessaryAbstractClass")
abstract class UnmetaExtension @Inject constructor(project: Project) { abstract class UnmetaExtension @Inject constructor(project: Project) {
@@ -13,4 +16,10 @@ abstract class UnmetaExtension @Inject constructor(project: Project) {
val isEnabled: Property<Boolean> = objects.property(Boolean::class.java) val isEnabled: Property<Boolean> = objects.property(Boolean::class.java)
.convention(DEFAULT_IS_ENABLED) .convention(DEFAULT_IS_ENABLED)
val verbose: Property<Boolean> = objects.property(Boolean::class.java)
.convention(DEFAULT_VERBOSE)
val outputFile: RegularFileProperty = objects.fileProperty()
.convention(project.layout.buildDirectory.file(DEFAULT_OUTPUT_FILE))
} }

View File

@@ -1,17 +1,25 @@
package com.axzae.unmeta package com.axzae.unmeta
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.configurationcache.extensions.capitalized
const val EXTENSION_NAME = "unmeta"
const val TASK_NAME = "unmetaTask"
abstract class UnmetaPlugin : Plugin<Project> { abstract class UnmetaPlugin : Plugin<Project> {
override fun apply(project: Project) { override fun apply(project: Project) {
val extension = project.extensions.create(EXTENSION_NAME, UnmetaExtension::class.java, project) val extension = project.extensions.create("unmeta", UnmetaExtension::class.java, project)
val androidComponents = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
project.tasks.register(TASK_NAME, UnmetaTask::class.java) { androidComponents.onVariants(androidComponents.selector().withBuildType("release")) { variant ->
it.isEnabled = extension.isEnabled.get() val compileKotlinTaskName = "compile${variant.name.capitalized()}Kotlin"
val unmetaTask = project.tasks.create("unmeta${variant.name.capitalized()}", UnmetaTask::class.java).apply {
isEnabled = extension.isEnabled.get()
verbose.set(extension.verbose.get())
variantName.set(variant.name)
outputFile.set(extension.outputFile)
}
project.afterEvaluate {
project.tasks.findByName(compileKotlinTaskName)?.finalizedBy(unmetaTask)
}
} }
} }
} }

View File

@@ -1,11 +1,17 @@
package com.axzae.unmeta package com.axzae.unmeta
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.BasePlugin
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter import org.objectweb.asm.ClassWriter
import java.io.File import java.io.File
import kotlin.system.measureTimeMillis
abstract class UnmetaTask : DefaultTask() { abstract class UnmetaTask : DefaultTask() {
@@ -14,27 +20,54 @@ abstract class UnmetaTask : DefaultTask() {
group = BasePlugin.BUILD_GROUP group = BasePlugin.BUILD_GROUP
} }
@get:Input
@get:Option(option = "variantName", description = "Android variant (flavor + buildType)")
abstract val variantName: Property<String>
@get:Input
@get:Option(option = "verbose", description = "Enable verbose logging")
abstract val verbose: Property<Boolean>
@get:OutputFile
abstract val outputFile: RegularFileProperty
private val fileLogger by lazy { outputFile.get().asFile }
@TaskAction @TaskAction
fun unmetaAction() { fun unmetaAction() {
if (!isEnabled) { if (!isEnabled) {
logger.warn("unmeta is disabled") log("unmeta is disabled")
return return
} }
log("Start dropping @DebugMetadata from kotlin classes")
logger.info("Start dropping @Metadata & @DebugMetadata from kotlin classes") val executionMs = measureTimeMillis {
project.buildDir.listFiles()?.forEach { file -> if (file.isDirectory) dropMetadata(file) } val kotlinClassesPath = project.buildDir.absolutePath + "/tmp/kotlin-classes/${variantName.get()}"
File(kotlinClassesPath).listFiles()?.forEach { file ->
if (file.isDirectory) removeAnnotation(file)
}
}
log("Unmeta Total Time: ${executionMs}ms")
} }
private fun dropMetadata(directory: File) { private fun log(message: String) {
when (verbose.get()) {
true -> logger.lifecycle(message)
else -> logger.debug(message)
}
fileLogger.appendText(message + System.lineSeparator(), Charsets.UTF_8)
}
private fun removeAnnotation(directory: File) {
directory.walk() directory.walk()
.filter { it.path.contains("classes") && it.path.endsWith(".class") && it.isFile } .filter { it.path.contains("classes") && it.path.endsWith(".class") && it.isFile }
.forEach { .forEach {
val sourceClassBytes = it.readBytes() val sourceClassBytes = it.readBytes()
val classReader = ClassReader(sourceClassBytes) val classReader = ClassReader(sourceClassBytes)
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
val unmetaClassVisitor = UnmetaClassVisitor(it.absolutePath, classWriter, logger) val unmetaClassVisitor = UnmetaClassVisitor(it.absolutePath, classWriter)
classReader.accept(unmetaClassVisitor, ClassReader.SKIP_DEBUG) classReader.accept(unmetaClassVisitor, ClassReader.SKIP_DEBUG)
if (unmetaClassVisitor.modified) { if (unmetaClassVisitor.isModified) {
log("Removed @DebugMetadata annotation from ${unmetaClassVisitor.path}")
it.writeBytes(classWriter.toByteArray()) it.writeBytes(classWriter.toByteArray())
} }
} }

View File

@@ -31,6 +31,4 @@ gradleEnterprise {
} }
} }
rootProject.name = ("com.axzae.unmeta")
include(":plugin") include(":plugin")

View File

@@ -2,6 +2,7 @@ pluginManagement {
repositories { repositories {
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
google()
} }
} }