Created
August 30, 2024 11:05
-
-
Save eaceto/5a19ba57547316361bfb36d7db76f0cf to your computer and use it in GitHub Desktop.
Shadow AAR (Gradle) dependencies plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.eaceto.android.plugins.shadow | |
| import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar | |
| import org.gradle.api.Plugin | |
| import org.gradle.api.Project | |
| import org.gradle.api.artifacts.DependencySet | |
| import org.gradle.api.artifacts.ResolvedArtifact | |
| import org.gradle.api.provider.ListProperty | |
| import org.gradle.api.tasks.Input | |
| import org.gradle.kotlin.dsl.dependencies | |
| import java.io.File | |
| // Extension class to allow users to specify relocation rules for namespaces | |
| abstract class AddShadowedAARDependenciesPluginExtension { | |
| // List of namespace pairs to be relocated (from -> to) | |
| @get:Input | |
| abstract val relocations: ListProperty<List<String>> | |
| } | |
| // Main plugin class | |
| class AddShadowedAARDependenciesPlugin : Plugin<Project> { | |
| // Constants for configuration and task names | |
| private val SHADOW_AAR_EXTENSION_NAME = "shadowAarDependencies" | |
| private val SHADOW_AAR_CONFIGURATION_NAME = "shadowAARImplementation" | |
| private val UNZIP_AAR_DIR = "unzip-aars" | |
| private val SHADOW_JAR_TASK_NAME = "relocateImplementationJARs" | |
| private val ANDROID_ARTIFACT_EXTENSION = "aar" | |
| private val JAVA_ARTIFACT_EXTENSION = "jar" | |
| private val JAR_IN_AAR_FILE_NAME = "classes.jar" | |
| // Checks if the artifact is an Android AAR package | |
| private fun isAndroidArtifact(artifact: ResolvedArtifact): Boolean = | |
| artifact.extension == ANDROID_ARTIFACT_EXTENSION | |
| // Checks if the artifact is a Java JAR file | |
| private fun isJavaArtifact(artifact: ResolvedArtifact): Boolean = | |
| artifact.extension == JAVA_ARTIFACT_EXTENSION | |
| // Checks if the artifact belongs to a specific group | |
| private fun isFromGroups(artifact: ResolvedArtifact, groups: List<String?>): Boolean = | |
| groups.any { artifact.moduleVersion.id.group == it } | |
| // Filters AAR artifacts that belong to the top-level dependency groups | |
| private fun getAarsWithTopLevelDependencyGroups( | |
| artifacts: Set<ResolvedArtifact>, | |
| topLevelDeps: DependencySet | |
| ): List<ResolvedArtifact> { | |
| val topLevelDepGroups = topLevelDeps.map { it.group } | |
| return artifacts.filter { | |
| isAndroidArtifact(it) && isFromGroups(it, topLevelDepGroups) | |
| } | |
| } | |
| // Filters JAR artifacts that belong to the top-level dependency groups | |
| private fun getJarsWithTopLevelDependencyGroups( | |
| artifacts: Set<ResolvedArtifact>, | |
| topLevelDeps: DependencySet | |
| ): List<ResolvedArtifact> { | |
| val topLevelDepGroups = topLevelDeps.map { it.group } | |
| return artifacts.filter { | |
| isJavaArtifact(it) && isFromGroups(it, topLevelDepGroups) | |
| } | |
| } | |
| // Extracts the classes.jar from an AAR file and renames it | |
| private fun extractJarFromAar(project: Project, targetDir: File, aar: ResolvedArtifact): File { | |
| val jarName = "${aar.moduleVersion.id.name}.$JAVA_ARTIFACT_EXTENSION" | |
| project.copy { | |
| from(project.zipTree(aar.file)) { | |
| rename(JAR_IN_AAR_FILE_NAME, jarName) | |
| } | |
| into(targetDir) | |
| } | |
| return targetDir.resolve(jarName) | |
| } | |
| // Generates a fully qualified dependency coordinate string (group:name:version) | |
| private fun getFullyQualifiedDependencyCoordinates(artifact: ResolvedArtifact): String { | |
| val version = artifact.moduleVersion.id.version | |
| val group = artifact.moduleVersion.id.group | |
| val name = artifact.moduleVersion.id.name | |
| return "$group:$name:$version" | |
| } | |
| // The entry point for the plugin, where configurations and tasks are created | |
| override fun apply(project: Project) { | |
| // Create an extension to allow users to configure relocation rules | |
| val extension = project.extensions.create( | |
| SHADOW_AAR_EXTENSION_NAME, | |
| AddShadowedAARDependenciesPluginExtension::class.java | |
| ) | |
| // Create a custom configuration to handle AAR dependencies that need to be relocated | |
| val aarConfig = project.configurations.create(SHADOW_AAR_CONFIGURATION_NAME) | |
| // Get the existing 'implementation' configuration | |
| val implementationConfig = project.configurations.getByName("implementation") | |
| // After the project is evaluated (configured), execute the following logic | |
| project.afterEvaluate { | |
| val unzipDir = project.mkdir(UNZIP_AAR_DIR) | |
| val relocations = extension.relocations.get() | |
| // Resolve all artifacts for the AAR configuration | |
| val allArtifacts = aarConfig.resolvedConfiguration.resolvedArtifacts | |
| val aarTopLevelDeps = aarConfig.dependencies | |
| // Get the AAR and JAR dependencies that belong to the top-level groups | |
| val aarDepsForRelocation = getAarsWithTopLevelDependencyGroups(allArtifacts, aarTopLevelDeps) | |
| val jarDepsForRelocation = getJarsWithTopLevelDependencyGroups(allArtifacts, aarTopLevelDeps) | |
| // Extract JAR files from AARs and combine them with JAR dependencies | |
| val allJarsForRelocation = aarDepsForRelocation.map { | |
| extractJarFromAar(project, unzipDir, it) | |
| } + jarDepsForRelocation.map { it.file } | |
| // Register a ShadowJar task to relocate namespaces in the extracted JAR files | |
| val task = project.tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar::class.java) { | |
| from(allJarsForRelocation) | |
| relocations.forEach { | |
| val (namespaceFrom, namespaceTo) = it | |
| relocate(namespaceFrom, namespaceTo) | |
| } | |
| } | |
| // Add non-relocated dependencies to the 'implementation' configuration | |
| project.dependencies { | |
| val artifactsNotInNeedOfRelocation = allArtifacts - aarDepsForRelocation.toSet() - jarDepsForRelocation.toSet() | |
| artifactsNotInNeedOfRelocation.forEach { | |
| val depCoords = getFullyQualifiedDependencyCoordinates(it) | |
| add(implementationConfig.name, depCoords) | |
| } | |
| // Add the relocated JARs to the 'implementation' configuration | |
| val relocatedJars = task.get().outputs.files | |
| add(implementationConfig.name, relocatedJars) | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment