Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue bundling kotlin metadataJvm in permissions lint #1804

Merged
merged 2 commits into from
Dec 10, 2024
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 build-logic/convention/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ kotlin {
}

dependencies {
// used by BundleInsideHelper.kt
implementation(libs.apacheAnt)
implementation(libs.shadow)

compileOnly(libs.android.gradlePlugin)
compileOnly(libs.android.tools.common)
compileOnly(libs.compose.gradlePlugin)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.accompanist

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.apache.tools.zip.ZipOutputStream
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Usage
import org.gradle.api.file.FileTreeElement
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register

/**
* Originally from https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/public/src/main/kotlin/androidx/build/BundleInsideHelper.kt
* Small modifications based on gradle version
*/

/** Allow java and Android libraries to bundle other projects inside the project jar/aar. */
object BundleInsideHelper {
val CONFIGURATION_NAME = "bundleInside"
val REPACKAGE_TASK_NAME = "repackageBundledJars"

/**
* Creates a configuration for the users to use that will be used to bundle these dependency
* jars inside of libs/ directory inside of the aar.
*
* ```
* dependencies {
* bundleInside(project(":foo"))
* }
* ```
*
* Used project are expected
*
* @param relocations a list of package relocations to apply
* @param dropResourcesWithSuffix used to drop Java resources if they match this suffix, null
* means no filtering
* @receiver the project that should bundle jars specified by this configuration
* @see forInsideAar(String, String)
*/
@JvmStatic
fun Project.forInsideAar(relocations: List<Relocation>?, dropResourcesWithSuffix: String?) {
val bundle = createBundleConfiguration()
val repackage = configureRepackageTaskForType(relocations, bundle, dropResourcesWithSuffix)
// Add to AGP's configuration so this jar get packaged inside of the aar.
dependencies.add("implementation", files(repackage.flatMap { it.archiveFile }))
}

/**
* Creates 3 configurations for the users to use that will be used bundle these dependency jars
* inside of libs/ directory inside of the aar.
*
* ```
* dependencies {
* bundleInside(project(":foo"))
* }
* ```
*
* Used project are expected
*
* @param from specifies from which package the rename should happen
* @param to specifies to which package to put the renamed classes
* @param dropResourcesWithSuffix used to drop Java resources if they match this suffix, null
* means no filtering
* @receiver the project that should bundle jars specified by these configurations
*/
@JvmStatic
fun Project.forInsideAar(from: String, to: String, dropResourcesWithSuffix: String?) {
forInsideAar(listOf(Relocation(from, to)), dropResourcesWithSuffix)
}

/**
* Creates a configuration for users to use that will bundle the dependency jars inside of this
* lint check's jar. This is required because lintPublish does not currently support
* dependencies, so instead we need to bundle any dependencies with the lint jar manually.
* (b/182319899)
*
* ```
* dependencies {
* if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
* compileOnly(LINT_API_LATEST)
* } else {
* compileOnly(LINT_API_MIN)
* }
* compileOnly(KOTLIN_STDLIB)
* // Include this library inside the resulting lint jar
* bundleInside(project(":foo-lint-utils"))
* }
* ```
*
* @receiver the project that should bundle jars specified by these configurations
*/
@JvmStatic
fun Project.forInsideLintJar(): Configuration {
val bundle = createBundleConfiguration()
val compileOnly = configurations.getByName("compileOnly")
val testImplementation = configurations.getByName("testImplementation")

compileOnly.extendsFrom(bundle)
testImplementation.extendsFrom(bundle)

// Relocation needed to avoid classpath conflicts with Android Studio (b/337980250)
// Can be removed if we migrate from using kotlin-metadata-jvm inside of lint checks
val relocations = listOf(Relocation("kotlin.metadata", "androidx.lint.kotlin.metadata"))
val repackage = configureRepackageTaskForType(relocations, bundle, null)
val sourceSets = extensions.getByType(SourceSetContainer::class.java)
repackage.configure {
this.from(sourceSets.findByName("main")?.output)
// kotlin-metadata-jvm has a service descriptor that needs transformation
this.mergeServiceFiles()
// Exclude Kotlin metadata files from kotlin-metadata-jvm
this.exclude(
"META-INF/kotlin-metadata-jvm.kotlin_module",
"META-INF/kotlin-metadata.kotlin_module",
"META-INF/metadata.jvm.kotlin_module",
"META-INF/metadata.kotlin_module"
)
}

listOf("apiElements", "runtimeElements").forEach { config ->
configurations.getByName(config).apply {
outgoing.artifacts.clear()
outgoing.artifact(repackage)
}
}

return bundle
}

data class Relocation(val from: String, val to: String)

private fun Project.configureRepackageTaskForType(
relocations: List<Relocation>?,
configuration: Configuration,
dropResourcesWithSuffix: String?
): TaskProvider<ShadowJar> {
val action = Action<ShadowJar> {
configurations = listOf(configuration)
if (relocations != null) {
for (relocation in relocations) {
println("Relocating ${relocation.from} to ${relocation.to}")
relocate(relocation.from, relocation.to)
}
}
val dontIncludeResourceTransformer = DontIncludeResourceTransformer()
dontIncludeResourceTransformer.dropResourcesWithSuffix = dropResourcesWithSuffix
transformers.add(dontIncludeResourceTransformer)
archiveBaseName.set("repackaged")
archiveVersion.set("")
destinationDirectory.set(layout.buildDirectory.dir("repackaged"))
}
return tasks.register(REPACKAGE_TASK_NAME, ShadowJar::class.java, action)
}

private fun Project.createBundleConfiguration(): Configuration {
val bundle =
configurations.create(CONFIGURATION_NAME) {
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Usage.JAVA_RUNTIME))
}
isCanBeConsumed = false
}
return bundle
}

class DontIncludeResourceTransformer : Transformer {
@Optional @Input var dropResourcesWithSuffix: String? = null

override fun getName(): String {
return "DontIncludeResourceTransformer"
}

override fun canTransformResource(element: FileTreeElement?): Boolean {
val path = element?.relativePath?.pathString
return dropResourcesWithSuffix != null &&
(path?.endsWith(dropResourcesWithSuffix!!) == true)
}

override fun transform(context: TransformerContext?) {
// no op
}

override fun hasTransformedResource(): Boolean {
return true
}

override fun modifyOutputStream(zipOutputStream: ZipOutputStream?, b: Boolean) {
// no op
}
}
}
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,18 @@ compose-material3-material3 = { module = "androidx.compose.material3:material3",
compose-animation-animation = { module = "androidx.compose.animation:animation", version.ref = "compose" }
compose-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }

apacheAnt = { module = "org.apache.ant:ant", version = "1.10.11" }
android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "gradlePlugin" }
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
gradleMavenPublishPlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "vanniktechPublish" }
metalavaGradle = { module = "me.tylerbwong.gradle.metalava:plugin", version.ref = "metalava" }
shadow = { module = "com.gradleup.shadow:shadow-gradle-plugin", version = "8.3.3" }

kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlin-stdlibJdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-metadataJvm = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0"
kotlin-metadataJvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" }

kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }

Expand Down
28 changes: 3 additions & 25 deletions permissions-lint/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.accompanist.BundleInsideHelper
import com.google.accompanist.BundleInsideHelper.forInsideLintJar

plugins {
`java-library`
Expand All @@ -21,10 +23,6 @@ plugins {
id(libs.plugins.android.lint.get().pluginId)
}

kotlin {
explicitApi()
}

lint {
htmlReport = true
htmlOutput = file("lint-report.html")
Expand All @@ -43,27 +41,7 @@ affectedTestConfiguration {
* not currently support dependencies, so instead we need to bundle any dependencies with the
* lint jar manually. (b/182319899)
*/
val bundleInside: Configuration = configurations.create("bundleInside")
// bundleInside dependencies should be included as compileOnly and testImplementation as well
configurations.getByName("compileOnly").setExtendsFrom(setOf(bundleInside))
configurations.getByName("testImplementation").setExtendsFrom(setOf(bundleInside))

tasks.getByName<Jar>("jar") {
this.dependsOn(bundleInside)
this.from({
bundleInside
// The stdlib is already bundled with lint, so no need to include it manually
// in the lint.jar if any dependencies here depend on it
.filter { !it.name.contains("kotlin-stdlib") }
.map { file ->
if (file.isDirectory) {
file
} else {
zipTree(file)
}
}
})
}
val bundleInside = forInsideLintJar()

dependencies {
// Bundle metadataJvm inside the Jar
Expand Down
Loading
Loading