1 /* <lambda>null2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.build 18 19 import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 20 import groovy.lang.Closure 21 import org.gradle.api.Plugin 22 import org.gradle.api.Project 23 import org.gradle.api.plugins.JavaLibraryPlugin 24 import org.gradle.api.provider.Property 25 import org.gradle.api.tasks.SourceSetContainer 26 import org.gradle.api.tasks.TaskProvider 27 import org.gradle.jvm.tasks.Jar 28 import org.gradle.kotlin.dsl.create 29 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin 30 31 /** 32 * Plugin responsible for repackaging libraries. The plugin repackages what is set in the 33 * [RelocationExtension] by the user and reconfigures the JAR task to output the repackaged classes 34 * JAR. 35 */ 36 @Suppress("unused") 37 class AndroidXRepackageImplPlugin : Plugin<Project> { 38 39 override fun apply(project: Project) { 40 val relocationExtension = 41 project.extensions.create<RelocationExtension>(EXTENSION_NAME, project) 42 project.plugins.configureEach { plugin -> 43 when (plugin) { 44 is JavaLibraryPlugin, 45 is KotlinBasePlugin -> project.configureJavaOrKotlinLibrary(relocationExtension) 46 } 47 } 48 } 49 50 private fun Project.configureJavaOrKotlinLibrary(relocationExtension: RelocationExtension) { 51 createConfigurations() 52 53 val sourceSets = extensions.getByType(SourceSetContainer::class.java) 54 val libraryShadowJar = 55 tasks.register("shadowLibraryJar", ShadowJar::class.java) { task -> 56 task.transformers.add( 57 BundleInsideHelper.DontIncludeResourceTransformer().apply { 58 dropResourcesWithSuffix = ".proto" 59 } 60 ) 61 task.transformers.add( 62 BundleInsideHelper.DontIncludeResourceTransformer().apply { 63 dropResourcesWithSuffix = ".proto.bin" 64 } 65 ) 66 task.from(sourceSets.named("main").map { it.output }) 67 relocationExtension.getRelocations().forEach { 68 task.relocate(it.sourcePackage, it.targetPackage) 69 } 70 relocationExtension.artifactId.orNull?.let { 71 task.configurations = listOf(configurations.getByName("repackageClasspath")) 72 } 73 } 74 addArchiveToVariants(libraryShadowJar) 75 } 76 77 private fun Project.createConfigurations() { 78 val repackage = 79 configurations.create("repackage") { config -> 80 config.isCanBeConsumed = false 81 config.isCanBeResolved = false 82 } 83 84 configurations.create("repackageClasspath") { config -> 85 config.isCanBeConsumed = false 86 config.isCanBeResolved = true 87 config.extendsFrom(repackage) 88 } 89 90 tasks.named("jar", Jar::class.java) { 91 // We cannot have two tasks with the same output as the ListTaskOutputsTask will fail. 92 // As we want the repackaged jar as the published artifact, we change the 93 // name of classifier of the JAR task 94 it.archiveClassifier.set("before-shadow") 95 } 96 97 forceJarUsageForAndroid() 98 } 99 100 /** 101 * This forces the use of repackaged JARs as opposed to the java-classes-directory for Android. 102 * Without this, AGP uses the artifacts in java-classes-directory, which do not have the classes 103 * repackaged to the target package. 104 * 105 * We attempted to extract the contents of the repackaged library JAR into classes/java/main, 106 * but the AGP transform depends on JavaCompile. We cannot make JavaCompile depend on the task 107 * that creates the shadowed library as that would result in a circular dependency. 108 */ 109 private fun Project.forceJarUsageForAndroid() = 110 configurations.configureEach { configuration -> 111 if (configuration.name == "runtimeElements") { 112 configuration.outgoing.variants.removeIf { it.name == "classes" } 113 } 114 } 115 116 private fun Project.addArchiveToVariants(task: TaskProvider<ShadowJar>) = 117 configurations.configureEach { configuration -> 118 if (configuration.name == "apiElements" || configuration.name == "runtimeElements") { 119 configuration.outgoing.artifacts.clear() 120 configuration.outgoing.artifact(task) 121 } 122 } 123 124 companion object { 125 const val EXTENSION_NAME = "repackage" 126 } 127 } 128 129 class Relocation { 130 /* The package name and any import statements for a class that are to be relocated. */ 131 var sourcePackage: String? = null 132 133 /* The package name and any import statements for a class to which they should be relocated. */ 134 var targetPackage: String? = null 135 } 136 137 abstract class RelocationExtension(val project: Project) { 138 139 private var relocations: MutableCollection<Relocation> = ArrayList() 140 addRelocationnull141 fun addRelocation(closure: Closure<Any>): Relocation { 142 val relocation = project.configure(Relocation(), closure) as Relocation 143 relocations.add(relocation) 144 return relocation 145 } 146 getRelocationsnull147 fun getRelocations(): Collection<Relocation> { 148 return relocations 149 } 150 151 /* Optional artifact id if the user wants to publish the dependency in the shadowed config. */ 152 abstract val artifactId: Property<String?> 153 } 154