Skip to content

Commit

Permalink
include kotlin_module files to final fat aar
Browse files Browse the repository at this point in the history
  • Loading branch information
0xera authored and natario1 committed Aug 29, 2024
1 parent 125230a commit 08b060d
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 71 deletions.
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
[versions]
agp = "8.1.4"
apache-ant = "1.10.14"
asm-commons = "9.6"
android-tools = "31.1.4"
kotlin = "2.0.0"
shadow = "8.3.0"
publisher = "0.14.0"
kotlinx-metadata = "0.9.0"

[libraries]
apache-ant = { module = "org.apache.ant:ant", version.ref = "apache-ant" }
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm-commons" }
gradle-android-common = { module = "com.android.tools:common", version.ref = "android-tools" }
gradle-android-sdk-common = { module = "com.android.tools:sdk-common", version.ref = "android-tools" }
gradle-android-layoutlib = { module = "com.android.tools.layoutlib:layoutlib-api", version.ref = "android-tools" }
gradle-android-build = { module = "com.android.tools.build:gradle", version.ref = "agp" }
gradle-shadow = { module = "com.gradleup.shadow:shadow-gradle-plugin", version.ref = "shadow" }
kotlinx-metadata-jvm = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version.ref = "kotlinx-metadata" }

[bundles]
gradle-android = ["gradle-android-sdk-common", "gradle-android-build", "gradle-android-common", "gradle-android-layoutlib"]
Expand Down
13 changes: 12 additions & 1 deletion grease/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = "io.deepmedia.tools"
version = "0.3.0"
version = "0.7.0"

gradlePlugin {
plugins {
Expand All @@ -19,6 +19,8 @@ dependencies {
implementation(libs.asm.commons)
implementation(libs.gradle.shadow)
implementation(libs.bundles.gradle.android)
implementation(libs.kotlinx.metadata.jvm)
implementation(libs.apache.ant)
}

deployer {
Expand Down Expand Up @@ -75,4 +77,13 @@ deployer {
token = secret("GHUB_PERSONAL_ACCESS_TOKEN")
}
}
}

publishing {
repositories {
maven {
name = "Local"
url = uri(rootProject.layout.buildDirectory.dir("grease_pub"))
}
}
}
154 changes: 84 additions & 70 deletions grease/src/main/kotlin/io/deepmedia/tools/grease/GreasePlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

package io.deepmedia.tools.grease

import com.android.build.api.component.analytics.AnalyticsEnabledLibraryVariant
import com.android.build.api.component.analytics.AnalyticsEnabledVariant
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.Variant
Expand Down Expand Up @@ -31,12 +30,13 @@ import com.android.ide.common.resources.CopyToOutputDirectoryResourceCompilation
import com.android.manifmerger.ManifestMerger2
import com.android.manifmerger.ManifestProvider
import com.android.utils.StdLogger
import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.kotlin.dsl.get
import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication
import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
import org.gradle.kotlin.dsl.support.unzipTo
import org.gradle.kotlin.dsl.support.zipTo
import java.io.File
Expand All @@ -56,40 +56,39 @@ open class GreasePlugin : Plugin<Project> {

@Suppress("NAME_SHADOWING")
override fun apply(target: Project) {
require(target.plugins.hasPlugin("com.android.library")) {
"Grease must be applied after the com.android.library plugin."
}
val log = Logger(target, "grease")
val android = target.extensions["android"] as LibraryExtension
val androidComponents = target.extensions.getByType(AndroidComponentsExtension::class.java)
val greaseExtension = target.extensions.create("grease", GreaseExtension::class.java)

debugGreasyConfigurationHierarchy(target, log)

// Create the configurations.
fun createConfigurations(isTransitive: Boolean) {
target.createRootConfiguration(isTransitive, log)
target.createProductFlavorConfigurations(androidComponents, isTransitive, log)
target.createBuildTypeConfigurations(android.buildTypes, isTransitive, log)
target.createVariantConfigurations(androidComponents, isTransitive, log)
}
createConfigurations(false)
createConfigurations(true)

fun configure(variant: Variant, vararg configurations: Configuration) {
configureVariantManifest(target, variant, configurations, log)
configureVariantJniLibs(target, variant, configurations, log)
configureVariantResources(target, variant, configurations, log)
configureVariantSources(target, variant, configurations, greaseExtension, log)
configureVariantAssets(target, variant, configurations, log)
configureVariantProguardFiles(target, variant, configurations, log)
}
// Configure all variants.
androidComponents.onVariants { variant ->
val log = log.child("configureVariant")
log.d { "Configuring variant ${variant.name}..." }
target.afterEvaluate {
configure(variant, target.greaseOf(variant), target.greaseOf(variant, true))
target.plugins.withId("com.android.library") {
val log = Logger(target, "grease")
val android = target.extensions.getByType(LibraryExtension::class.java)
val androidComponents = target.extensions.getByType(AndroidComponentsExtension::class.java)
val greaseExtension = target.extensions.create("grease", GreaseExtension::class.java)

debugGreasyConfigurationHierarchy(target, log)

// Create the configurations.
fun createConfigurations(isTransitive: Boolean) {
target.createRootConfiguration(isTransitive, log)
target.createProductFlavorConfigurations(androidComponents, isTransitive, log)
target.createBuildTypeConfigurations(android.buildTypes, isTransitive, log)
target.createVariantConfigurations(androidComponents, isTransitive, log)
}
createConfigurations(false)
createConfigurations(true)

fun configure(variant: Variant, vararg configurations: Configuration) {
configureVariantManifest(target, variant, configurations, log)
configureVariantJniLibs(target, variant, configurations, log)
configureVariantResources(target, variant, configurations, log)
configureVariantSources(target, variant, configurations, greaseExtension, log)
configureVariantAssets(target, variant, configurations, log)
configureVariantProguardFiles(target, variant, configurations, log)
}
// Configure all variants.
androidComponents.onVariants { variant ->
val log = log.child("configureVariant")
log.d { "Configuring variant ${variant.name}..." }
target.afterEvaluate {
configure(variant, target.greaseOf(variant), target.greaseOf(variant, true))
}
}
}
}
Expand Down Expand Up @@ -432,7 +431,7 @@ open class GreasePlugin : Plugin<Project> {

fun injectClasses(inputJar: File) {
log.d { "Processing inputJar=$inputJar outputDir=${jarExtractWorkdir}..." }
val inputFiles = target.zipTree(inputJar).matching { include("**/*.class") }
val inputFiles = target.zipTree(inputJar).matching { include("**/*.class", "**/*.kotlin_module") }
target.copy {
from(inputFiles)
into(jarExtractWorkdir)
Expand Down Expand Up @@ -466,7 +465,7 @@ open class GreasePlugin : Plugin<Project> {
destinationDirectory.set(greaseShadowDir)

from(greaseProcessTask.get().outputs)
val packagesToReplace = mutableMapOf<String, String>()
val addedPackagesNames = mutableSetOf<String>()

doFirst {
target.delete(greaseShadowDir)
Expand All @@ -487,52 +486,35 @@ open class GreasePlugin : Plugin<Project> {
.asSequence()
.flatMap { inputFile -> inputFile.packageNames }
.distinct()
.forEach { packageName ->
val newPackageName = "${relocationPrefix}.$packageName"
.map { packageName -> packageName to "${relocationPrefix}.$packageName" }
.distinct()
.filterNot { (packageName, _) -> addedPackagesNames.any(packageName::contains) }
.forEach { (packageName, newPackageName) ->
log.d { "Relocate package from $packageName to $newPackageName" }
relocate(packageName, newPackageName)
packagesToReplace[packageName] = newPackageName
addedPackagesNames += packageName
}
}

greaseExtension.relocators.get().forEach { relocator ->
relocate(relocator)
if (relocator is SimpleRelocator) {
packagesToReplace[relocator.pattern] = relocator.shadedPattern
}
}

greaseExtension.relocators.get().forEach<Relocator?>(::relocate)
greaseExtension.transformers.get().forEach(::transform)
transform(KotlinModuleShadowTransformer(logger.child("kotlin_module")))
}

doLast {
val shadowJar = greaseShadowDir.file(jarFileName).asFile
val shadowManifest = greaseShadowDir.file("AndroidManifest.xml").asFile
log.d { "Copy shaded inputJar=${shadowJar} outputDir=$aarExtractWorkdir..." }
target.copy {
from(shadowJar)
into(aarExtractWorkdir)
}

val manifestWriter = shadowManifest.bufferedWriter()
val manifestReader = aarExtractWorkdir.file("AndroidManifest.xml").asFile.bufferedReader()

manifestReader.useLines { strings ->
strings
.map { string ->
packagesToReplace.entries.fold(string) { acc, (from, to) ->
acc.replace(from, to)
}
}.forEach {
manifestWriter.write(it)
manifestWriter.newLine()
}
}
manifestWriter.close()
target.copy {
from(shadowManifest)
into(aarExtractWorkdir)
}
replacePackagesInFile(
aarExtractWorkdir.file("AndroidManifest.xml").asFile,
greaseShadowDir.file("AndroidManifest.xml").asFile,
relocators,
target,
)

val oldArchive = bundleAar.archiveFile.get().asFile
val archiveParent = oldArchive.parentFile
Expand All @@ -543,16 +525,48 @@ open class GreasePlugin : Plugin<Project> {
}

bundleLibraryTask?.configure {
finalizedBy(greaseExpandTask)
outputs.upToDateWhen { false }
finalizedBy(greaseShadowTask)
}
greaseExpandTask.configure {
mustRunAfter(bundleLibraryTask)
finalizedBy(greaseProcessTask)
}
greaseProcessTask.configure {
mustRunAfter(bundleLibraryTask)
finalizedBy(greaseShadowTask)
}
}

private fun replacePackagesInFile(
input: File,
output: File,
relocators: List<Relocator>,
target: Project,
) {
val reader = input.bufferedReader()
val writer = output.bufferedWriter()
reader.useLines { strings ->
strings
.map { string ->
relocators
.filterNot { it is RClassRelocator }
.fold(string) { acc, relocator ->
relocator.applyToSourceContent(acc)
}
}.forEach {
writer.write(it)
writer.newLine()
}
}
writer.close()

target.copy {
from(output)
into(input.parentFile)
}
}

/**
* Interesting tasks:
* 1. generate<>Assets: See [MutableTaskContainer].
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.deepmedia.tools.grease

import com.github.jengelman.gradle.plugins.shadow.transformers.CacheableTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.gradle.api.file.FileTreeElement
import kotlinx.metadata.jvm.KotlinModuleMetadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipOutputStream

// from kotlin sources

@CacheableTransformer
@OptIn(UnstableMetadataApi::class)
internal class KotlinModuleShadowTransformer(private val logger: Logger) : Transformer {
@Suppress("ArrayInDataClass")
private data class Entry(val path: String, val bytes: ByteArray)

private val data = mutableListOf<Entry>()

override fun getName() = "KotlinModuleShadowTransformer"

override fun canTransformResource(element: FileTreeElement): Boolean =
element.path.substringAfterLast(".") == KOTLIN_MODULE

override fun transform(context: TransformerContext) {
fun relocate(content: String): String =
context.relocators
.filterNot { it is RClassRelocator }
.fold(content) { acc, relocator -> relocator.applyToSourceContent(acc) }

logger.i { "Transforming kotlin_module ${context.path}" }
val metadata = KotlinModuleMetadata.read(context.`is`.readBytes())
val module = metadata.kmModule

val packageParts = module.packageParts.toMap()
module.packageParts.clear()
packageParts.map { (fqName, parts) ->
require(parts.multiFileClassParts.isEmpty()) { parts.multiFileClassParts } // There are no multi-file class parts in core

val fileFacades = parts.fileFacades.toList()
parts.fileFacades.clear()
fileFacades.mapTo(parts.fileFacades) { relocate(it) }

relocate(fqName) to parts
}.toMap(module.packageParts)

data += Entry(context.path, metadata.write())
}

override fun hasTransformedResource(): Boolean = data.isNotEmpty()

override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
for ((path, bytes) in data) {
os.putNextEntry(ZipEntry(path))
os.write(bytes)
}
data.clear()
}

companion object {
const val KOTLIN_MODULE = "kotlin_module"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ package io.deepmedia.tools.grease.sample.dependency.pure

object PureDependencyClass {
fun foo() = "bar"
}

fun PureDependencyFunction() {
println("foo")
}

0 comments on commit 08b060d

Please sign in to comment.