Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 2 additions & 0 deletions grease/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
171 changes: 97 additions & 74 deletions grease/src/main/kotlin/io/deepmedia/tools/grease/GreasePlugin.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
@file:Suppress("UnstableApiUsage")

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 +28,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 @@ -54,42 +52,40 @@ open class GreasePlugin : Plugin<Project> {

private val defaultIssueReporter = DefaultIssueReporter(StdLogger(StdLogger.Level.WARNING))

@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 childLog = log.child("configureVariant")
childLog.d { "Configuring variant ${variant.name}..." }
target.afterEvaluate {
configure(variant, target.greaseOf(variant), target.greaseOf(variant, true))
}
}
}
}
Expand Down Expand Up @@ -141,7 +137,6 @@ open class GreasePlugin : Plugin<Project> {
// To retrieve the secondary files, we must query the configuration artifacts.
val primaryManifest = processManifestTask.manifestOutputFile.asFile // overwrite

@Suppress("DEPRECATION")
val mergedFlavor = componentConfig.oldVariantApiLegacySupport?.mergedFlavor

log.d { "Merging manifests... primary=${primaryManifest.get()}, secondary=${extraManifests.files.joinToString()}" }
Expand Down Expand Up @@ -432,7 +427,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 +461,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 +482,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,14 +521,59 @@ 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)
}
greaseShadowTask.configure {
mustRunAfter(bundleLibraryTask)
}
target.plugins.withId("org.gradle.maven-publish") {
target.tasks.withType(PublishToMavenRepository::class.java) {
val publication = publication
if (publication is DefaultMavenPublication) {
if (creationConfig.name == publication.component.get().name) {
dependsOn(greaseShadowTask)
}
}
Comment on lines +538 to +545
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong to me. Can you explain what's your goal here? There should be a more a robust solution.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was problem with publication, because publish task doesnt wait grease tasks, so grease task will be executed after publication and final aar will be incorrect. Therefore i made publish task to execute after grease task for common cases

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem is that people should be able to rely on bundle AAR task in all scenarios, without tricks like this one which only solves the publication use-case. We can fix this later though, I'll open an issue with some ideas without using finalizedBy.

Another problem is that code above may crash at component.get(). You can do component.getOrNull() but then it may be added later and you don't catch that.

Maybe it'd be better to iterate over components?

project.components.matching { it.name == creationConfig.name }.all {
    tasks.matching { it.name == "generate${creationConfig.name.capitalize()}PomFileForPublication" }.configureEach {
        mustRunAfter(greaseShadowTask)
    }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but there is so many publish tasks and we can skip one of them. Also we are configuring grease after evaluation so i think everything should be good

}
}
}

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)
}
}

/**
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
Comment on lines +9 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use Java's ZipOutputStream?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes from shadow jar dependency, because it is using in transform interface functions


// 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")
}