diff --git a/KeepAnnotationsSample/README.md b/KeepAnnotationsSample/README.md index cf1daaa0..b8e2a754 100644 --- a/KeepAnnotationsSample/README.md +++ b/KeepAnnotationsSample/README.md @@ -4,19 +4,57 @@ This examples shows the use of the `androidx.annotation.keep` Gradle plugin. - This programmatically generates `keep` rules when an `app` / `android library` / `java library` module uses `androidx.annotation:annotation-keep`. - ## Testing -```kotlin +### Android apps and Library Modules -// For app modules +```bash + +// App Modules ./gradlew ExtractKeepRules -// For android library modules +// For Android Library Modules ./gradlew KeepRulesTransformAar +``` +### Java / Kotlin Library Modules + +For Java Libraries, you need to do the following. + +* Apply the Gradle Plugin. + +```kotlin +plugins { + alias(libs.plugins.androidx.annotation.keep) +} ``` -The generated keep rules end up in `build/generated/variantName/androidx.annotation.keep.rules.pro`. +* Register a new task that can generate the artifact with keep rules. + +```kotlin +val outputJar = project.tasks.named("jar").flatMap { task -> + task.archiveFile +} + +val outputFile = outputJar.map { file -> "keep${file.asFile.name.uppercaseFirstChar()}" } + +// Register the JAR transform for extracting keep rules. +val artifactTask = annotationKeep.registerJavaArchiveTransform( + taskName = "keepRulesJar", + input = outputJar, + fileName = outputFile +) +``` + +* Register the output artifact for publication. + +```kotlin +// Register the new artifact. +project.configurations.configureEach { + if (name == "apiElements" || name == "runtimeElements") { + outgoing.artifact(artifactTask) + } +} +``` diff --git a/KeepAnnotationsSample/build.gradle.kts b/KeepAnnotationsSample/build.gradle.kts index 89416030..7763972f 100644 --- a/KeepAnnotationsSample/build.gradle.kts +++ b/KeepAnnotationsSample/build.gradle.kts @@ -2,4 +2,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.androidx.annotation.keep) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.jetbrains.kotlin.jvm) apply false } diff --git a/KeepAnnotationsSample/gradle/libs.versions.toml b/KeepAnnotationsSample/gradle/libs.versions.toml index 606e17f8..c093282d 100644 --- a/KeepAnnotationsSample/gradle/libs.versions.toml +++ b/KeepAnnotationsSample/gradle/libs.versions.toml @@ -9,6 +9,9 @@ lifecycleRuntimeKtx = "2.11.0" activityCompose = "1.13.0" kotlin = "2.4.20" composeBom = "2026.08.00" +appcompat = "1.6.1" +material = "1.10.0" +jetbrainsKotlinJvm = "2.4.20" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -26,8 +29,12 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" } +android-library = { id = "com.android.library", version.ref = "agp" } +jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } diff --git a/KeepAnnotationsSample/java-library/.gitignore b/KeepAnnotationsSample/java-library/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/KeepAnnotationsSample/java-library/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/KeepAnnotationsSample/java-library/build.gradle.kts b/KeepAnnotationsSample/java-library/build.gradle.kts new file mode 100644 index 00000000..e0338ce8 --- /dev/null +++ b/KeepAnnotationsSample/java-library/build.gradle.kts @@ -0,0 +1,42 @@ +import org.gradle.kotlin.dsl.support.uppercaseFirstChar + +plugins { + id("java-library") + alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.androidx.annotation.keep) +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +val outputJar = project.tasks.named("jar").flatMap { task -> + task.archiveFile +} + +val outputFile = outputJar.map { file -> "keep${file.asFile.name.uppercaseFirstChar()}" } + +// Register the JAR transform for extracting keep rules. +val artifactTask = annotationKeep.registerJavaArchiveTransform( + taskName = "keepRulesJar", + input = outputJar, + fileName = outputFile +) + +// Register the new artifact. +project.configurations.configureEach { + if (name == "apiElements" || name == "runtimeElements") { + outgoing.artifact(artifactTask) + } +} + +dependencies { + implementation(libs.androidx.annotation.keep) +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 + } +} diff --git a/KeepAnnotationsSample/java-library/src/main/java/org/example/java/library/PlainLocator.kt b/KeepAnnotationsSample/java-library/src/main/java/org/example/java/library/PlainLocator.kt new file mode 100644 index 00000000..d10da014 --- /dev/null +++ b/KeepAnnotationsSample/java-library/src/main/java/org/example/java/library/PlainLocator.kt @@ -0,0 +1,16 @@ +package org.example.java.library + +import androidx.annotation.keep.UsesReflectionToConstruct + +interface PlainLocator { + companion object { + @UsesReflectionToConstruct(classConstant = PlainLocatorImpl::class) + fun load(): PlainLocator { + val klass = Class.forName(PlainLocatorImpl::class.java.name) + @Suppress("DEPRECATION") + return klass.newInstance() as PlainLocator + } + } +} + +private class PlainLocatorImpl : PlainLocator diff --git a/KeepAnnotationsSample/library/.gitignore b/KeepAnnotationsSample/library/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/KeepAnnotationsSample/library/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/KeepAnnotationsSample/library/build.gradle.kts b/KeepAnnotationsSample/library/build.gradle.kts new file mode 100644 index 00000000..a04ac703 --- /dev/null +++ b/KeepAnnotationsSample/library/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.androidx.annotation.keep) +} + +android { + namespace = "org.example.library" + compileSdk { + version = release(37) + } + defaultConfig { + minSdk = 24 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + implementation(libs.androidx.annotation.keep) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.core.ktx) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.junit) +} diff --git a/KeepAnnotationsSample/library/consumer-rules.keep b/KeepAnnotationsSample/library/consumer-rules.keep new file mode 100644 index 00000000..e69de29b diff --git a/KeepAnnotationsSample/library/src/androidTest/java/org/example/library/ExampleInstrumentedTest.kt b/KeepAnnotationsSample/library/src/androidTest/java/org/example/library/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..22ce830d --- /dev/null +++ b/KeepAnnotationsSample/library/src/androidTest/java/org/example/library/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package org.example.library + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("org.example.library.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/KeepAnnotationsSample/library/src/main/AndroidManifest.xml b/KeepAnnotationsSample/library/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/KeepAnnotationsSample/library/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/KeepAnnotationsSample/library/src/main/java/org/example/library/LibraryResourceLocator.kt b/KeepAnnotationsSample/library/src/main/java/org/example/library/LibraryResourceLocator.kt new file mode 100644 index 00000000..49fc3933 --- /dev/null +++ b/KeepAnnotationsSample/library/src/main/java/org/example/library/LibraryResourceLocator.kt @@ -0,0 +1,16 @@ +package org.example.library + +import androidx.annotation.keep.UsesReflectionToConstruct + +interface LibraryResourceLocator { + companion object { + @UsesReflectionToConstruct(classConstant = LibraryResourceLocatorImpl::class) + fun load(): LibraryResourceLocator { + val klass = Class.forName(LibraryResourceLocatorImpl::class.java.name) + @Suppress("DEPRECATION") + return klass.newInstance() as LibraryResourceLocator + } + } +} + +private class LibraryResourceLocatorImpl : LibraryResourceLocator diff --git a/KeepAnnotationsSample/library/src/main/keepRules/rules.keep b/KeepAnnotationsSample/library/src/main/keepRules/rules.keep new file mode 100644 index 00000000..d7e081a5 --- /dev/null +++ b/KeepAnnotationsSample/library/src/main/keepRules/rules.keep @@ -0,0 +1,12 @@ +# Add project specific R8 rules here. +# AGP will combine all keep rule files in src/main/keepRules to pass to R8 +# +# For more details, see +# https://d.android.com/r/tools/r8/keep-rules + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} \ No newline at end of file diff --git a/KeepAnnotationsSample/library/src/test/java/org/example/library/ExampleUnitTest.kt b/KeepAnnotationsSample/library/src/test/java/org/example/library/ExampleUnitTest.kt new file mode 100644 index 00000000..92a337ba --- /dev/null +++ b/KeepAnnotationsSample/library/src/test/java/org/example/library/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package org.example.library + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/KeepAnnotationsSample/settings.gradle.kts b/KeepAnnotationsSample/settings.gradle.kts index 948a4cb2..f81e123c 100644 --- a/KeepAnnotationsSample/settings.gradle.kts +++ b/KeepAnnotationsSample/settings.gradle.kts @@ -10,7 +10,7 @@ pluginManagement { mavenCentral() gradlePluginPortal() maven { - url = uri("https://androidx.dev/snapshots/builds/16303799/artifacts/repository") + url = uri("https://androidx.dev/snapshots/builds/16312043/artifacts/repository") } } } @@ -23,7 +23,7 @@ dependencyResolutionManagement { google() mavenCentral() maven { - url = uri("https://androidx.dev/snapshots/builds/16303799/artifacts/repository") + url = uri("https://androidx.dev/snapshots/builds/16312043/artifacts/repository") content { includeGroupByRegex("androidx\\.annotation.*") } @@ -34,3 +34,5 @@ dependencyResolutionManagement { rootProject.name = "Example" include(":app") +include(":library") +include(":java-library")