-
Notifications
You must be signed in to change notification settings - Fork 210
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test: self test Bug: NA Change-Id: If427157f2040ad5be0b191509b092179f2c73fb8
- Loading branch information
1 parent
0895bd2
commit 5f89bb8
Showing
17 changed files
with
493 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,3 +94,7 @@ recipe_test( | |
recipe_test( | ||
name = "workerEnabledTransformation", | ||
) | ||
|
||
recipe_test( | ||
name = "transformAllClasses", | ||
) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Transform classes with ScopedArtifactsOperation.toTransform() | ||
|
||
This sample shows how to transform classes that will be used to create the `.dex` files. | ||
There are two lists that need to be used to obtain the complete set of classes because some | ||
classes are present as .class files in directories and others are present in jar files. | ||
Therefore, you must query both [ListProperty] of [Directory] (classes) and [RegularFile] | ||
(jars) to get the full list. | ||
|
||
In this example, we query all classes to invoke some bytecode instrumentation on them. | ||
|
||
The Variant API provides a convenient API to transform bytecodes based on ASM but this example | ||
is using javassist to show how this can be done using a different bytecode enhancer. | ||
|
||
Example deals with classes as `scoped artifacts`. Scoped artifacts are artifacts that can | ||
be made available in the current variant scope, or may be optionally include the project's | ||
dependencies in the results. Scoped artifacts can be classes or Java resources. | ||
|
||
The [onVariants] block will create the [ModifyClassesTask] provider with input properties `allJars`, | ||
`allDirectories` and the `output` jar file. `ScopedArtifactsOperation.toTransform()` | ||
wires together transformation type [ScopedArtifact.CLASSES], input files and directories, output | ||
to make scoped artifact transformer. | ||
|
||
## To Run | ||
To execute example you need to enter command: | ||
|
||
`./gradlew :app:assembleDebug` | ||
|
||
You will see output similar to following: | ||
|
||
``` | ||
> Task :app:debugModifyClasses | ||
handling .../app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/processDebugResources/R.jar | ||
Adding from jar com/example/android/recipes/transform_classes/R.class | ||
handling .../app/build/tmp/kotlin-classes/debug | ||
Found .../app/build/tmp/kotlin-classes/debug/com/example/android/recipes/sample/SomeSource.class.name | ||
Adding javassist.CtNewClass@3c77f2f5[hasConstructor changed public abstract interface class com.example.android.recipes.sample.SomeInterface fields= constructors= methods=] | ||
``` | ||
|
||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright 2022 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. | ||
*/ | ||
|
||
plugins { | ||
alias(libs.plugins.android.application) | ||
alias(libs.plugins.kotlin.android) | ||
id("android.recipes.transform_classes") | ||
} | ||
|
||
|
||
|
||
android { | ||
namespace = "com.example.android.recipes.transform_classes" | ||
compileSdk = $COMPILE_SDK | ||
defaultConfig { | ||
minSdk = $MINIMUM_SDK | ||
targetSdk = $COMPILE_SDK | ||
} | ||
} | ||
|
||
java { | ||
toolchain { | ||
languageVersion.set(JavaLanguageVersion.of(17)) | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
recipes/transformAllClasses/app/src/main/AndroidManifest.xml
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
<!-- | ||
Copyright 2022 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 | ||
http://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. | ||
--> | ||
<application android:label="Minimal"> | ||
</application> | ||
</manifest> |
20 changes: 20 additions & 0 deletions
20
...es/transformAllClasses/app/src/main/java/com/example/android/recipes/sample/SomeSource.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* Copyright 2023 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.example.android.recipes.sample | ||
|
||
class SomeSource { | ||
override fun toString() = "Something !" | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534 | ||
org.gradle.parallel=true |
9 changes: 9 additions & 0 deletions
9
recipes/transformAllClasses/build-logic/gradle/libs.versions.toml
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[versions] | ||
androidGradlePlugin = $AGP_VERSION | ||
kotlin = $KOTLIN_VERSION | ||
|
||
[libraries] | ||
android-gradlePlugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" } | ||
|
||
[plugins] | ||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } |
42 changes: 42 additions & 0 deletions
42
recipes/transformAllClasses/build-logic/plugins/build.gradle.kts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright 2022 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. | ||
*/ | ||
|
||
plugins { | ||
`java-gradle-plugin` | ||
alias(libs.plugins.kotlin.jvm) | ||
} | ||
|
||
java { | ||
toolchain { | ||
languageVersion.set(JavaLanguageVersion.of(17)) | ||
} | ||
} | ||
|
||
dependencies { | ||
compileOnly(libs.android.gradlePlugin.api) | ||
implementation(gradleKotlinDsl()) | ||
implementation(files("libs/javassist-3.26.0-GA.jar")) | ||
} | ||
|
||
gradlePlugin { | ||
plugins { | ||
create("customPlugin") { | ||
id = "android.recipes.transform_classes" | ||
implementationClass = "CustomPlugin" | ||
} | ||
} | ||
} | ||
|
Binary file added
BIN
+764 KB
recipes/transformAllClasses/build-logic/plugins/libs/javassist-3.26.0-GA.jar
Binary file not shown.
60 changes: 60 additions & 0 deletions
60
recipes/transformAllClasses/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* Copyright 2023 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. | ||
*/ | ||
|
||
import com.android.build.api.variant.ApplicationAndroidComponentsExtension | ||
import com.android.build.gradle.AppPlugin | ||
import org.gradle.api.DefaultTask | ||
import org.gradle.api.Plugin | ||
import org.gradle.api.Project | ||
import org.gradle.api.file.DirectoryProperty | ||
import org.gradle.api.tasks.OutputDirectory | ||
import org.gradle.api.tasks.TaskAction | ||
import org.gradle.kotlin.dsl.register | ||
import com.android.build.api.variant.ScopedArtifacts | ||
import com.android.build.api.artifact.ScopedArtifact | ||
|
||
/** | ||
* This custom plugin will register a callback that is applied to all variants. | ||
*/ | ||
class CustomPlugin : Plugin<Project> { | ||
override fun apply(project: Project) { | ||
|
||
// Registers a callback on the application of the Android Application plugin. | ||
// This allows the CustomPlugin to work whether it's applied before or after | ||
// the Android Application plugin. | ||
project.plugins.withType(AppPlugin::class.java) { | ||
|
||
// Queries for the extension set by the Android Application plugin. | ||
// This is the second of two entry points into the Android Gradle plugin | ||
val androidComponents = | ||
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java) | ||
// Registers a callback to be called, when a new variant is configured | ||
androidComponents.onVariants { variant -> | ||
val taskProvider = project.tasks.register<ModifyClassesTask>("${variant.name}ModifyClasses") | ||
|
||
// Register modify classes task | ||
variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT) | ||
.use(taskProvider) | ||
.toTransform( | ||
ScopedArtifact.CLASSES, | ||
ModifyClassesTask::allJars, | ||
ModifyClassesTask::allDirectories, | ||
ModifyClassesTask::output | ||
) | ||
} | ||
} | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
recipes/transformAllClasses/build-logic/plugins/src/main/kotlin/ModifyClassesTask.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* Copyright (C) 2023 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 | ||
* | ||
* http://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. | ||
*/ | ||
|
||
import com.android.build.api.variant.ScopedArtifacts | ||
import com.android.build.api.artifact.ScopedArtifact | ||
|
||
import org.gradle.api.DefaultTask | ||
import org.gradle.api.file.Directory | ||
import org.gradle.api.provider.ListProperty | ||
import org.gradle.api.tasks.InputFiles | ||
import org.gradle.api.tasks.TaskAction | ||
import javassist.ClassPool | ||
import javassist.CtClass | ||
import java.io.FileInputStream | ||
import java.io.FileOutputStream | ||
import java.io.BufferedOutputStream | ||
import java.io.File | ||
import java.util.jar.JarFile | ||
import java.util.jar.JarEntry | ||
import java.util.jar.JarOutputStream | ||
import org.gradle.api.file.RegularFile | ||
import org.gradle.api.tasks.OutputFile | ||
import org.gradle.api.file.RegularFileProperty | ||
|
||
abstract class ModifyClassesTask: DefaultTask() { | ||
// This property will be set to all Jar files available in scope | ||
@get:InputFiles | ||
abstract val allJars: ListProperty<RegularFile> | ||
|
||
// Gradle will set this property with all class directories that available in scope | ||
@get:InputFiles | ||
abstract val allDirectories: ListProperty<Directory> | ||
|
||
// Task will put all classes from directories and jars after optional modification into single jar | ||
@get:OutputFile | ||
abstract val output: RegularFileProperty | ||
|
||
@TaskAction | ||
fun taskAction() { | ||
|
||
val pool = ClassPool(ClassPool.getDefault()) | ||
|
||
val jarOutput = JarOutputStream(BufferedOutputStream(FileOutputStream( | ||
output.get().asFile | ||
))) | ||
// we just copying classes fromjar files without modification | ||
allJars.get().forEach { file -> | ||
println("handling " + file.asFile.getAbsolutePath()) | ||
val jarFile = JarFile(file.asFile) | ||
jarFile.entries().iterator().forEach { jarEntry -> | ||
println("Adding from jar ${jarEntry.name}") | ||
jarOutput.putNextEntry(JarEntry(jarEntry.name)) | ||
jarFile.getInputStream(jarEntry).use { | ||
// just copying to output jar without changes | ||
it.copyTo(jarOutput) | ||
} | ||
jarOutput.closeEntry() | ||
} | ||
jarFile.close() | ||
} | ||
// Iterating through class files from directories | ||
// Looking for SomeSource.class to add generated interface and instrument with additional output in | ||
// toString methods (in our case it's just System.out) | ||
allDirectories.get().forEach { directory -> | ||
println("handling " + directory.asFile.getAbsolutePath()) | ||
directory.asFile.walk().forEach { file -> | ||
if (file.isFile) { | ||
if (file.name.endsWith("SomeSource.class")) { | ||
println("Found $file.name") | ||
val interfaceClass = pool.makeInterface("com.example.android.recipes.sample.SomeInterface"); | ||
println("Adding $interfaceClass") | ||
jarOutput.putNextEntry(JarEntry("com/example/android/recipes/sample/SomeInterface.class")) | ||
jarOutput.write(interfaceClass.toBytecode()) | ||
jarOutput.closeEntry() | ||
val ctClass = file.inputStream().use { | ||
pool.makeClass(it); | ||
} | ||
ctClass.addInterface(interfaceClass) | ||
|
||
val m = ctClass.getDeclaredMethod("toString"); | ||
if (m != null) { | ||
// injecting additional code that will be located at the beginning of toString method | ||
m.insertBefore("{ System.out.println(\"Some Extensive Tracing\"); }"); | ||
|
||
val relativePath = directory.asFile.toURI().relativize(file.toURI()).getPath() | ||
// Writing changed class to output jar | ||
jarOutput.putNextEntry(JarEntry(relativePath.replace(File.separatorChar, '/'))) | ||
jarOutput.write(ctClass.toBytecode()) | ||
jarOutput.closeEntry() | ||
} | ||
} else { | ||
// if class is not SomeSource.class - just copy it to output without modification | ||
val relativePath = directory.asFile.toURI().relativize(file.toURI()).getPath() | ||
println("Adding from directory ${relativePath.replace(File.separatorChar, '/')}") | ||
jarOutput.putNextEntry(JarEntry(relativePath.replace(File.separatorChar, '/'))) | ||
file.inputStream().use { inputStream -> | ||
inputStream.copyTo(jarOutput) | ||
} | ||
jarOutput.closeEntry() | ||
} | ||
} | ||
} | ||
} | ||
jarOutput.close() | ||
} | ||
} |
Oops, something went wrong.