From 4a5ae7d7585747ff81a34923df9a7c9b7a79665f Mon Sep 17 00:00:00 2001 From: shifujun Date: Fri, 1 Dec 2023 11:25:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor(core.transform-kit):=20=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=B9=B6=E5=89=A5=E7=A6=BB?= =?UTF-8?q?=E5=AF=B9=E6=97=A7Transform=20API=E7=9A=84=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E5=88=B0Wrapper=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ClassTransform是抽象的,它不知道字节码编辑工具是Javassist,也不知道Transform框架是AGP的还是Gradle的。 它定义了TransformInput,由外部Transform框架的适配器(如DeprecatedTransformWrapper)将外部Transform 框架的输入适配进来。 TransformInput支持DIR和JAR两种输入,对应ClassTransform.input中通过TransformInput拿到输入的class文件或 jar文件,然后交给loadDotClassFile和loadClassFromJar两个抽象方法加载类到字节码编辑框架中。 input同时将加载的类名记录到TransformInput.getInputClassNames中,以便output时知道该输出哪些类。 注意1个TransformInput对应多个Class。 此次简化去掉了InputClass类。这个类之前主要的作用是记录每个输入的class对应的输出文件。 这个输出文件其实不需要逐个记录路径或者entryname,可以由类名自己拼接出来。 所以这次简化改为了在output时再根据classname生成文件路径和entryname。 将ctClassInputMap: Map改为allInputCtClass: Set ,去掉renameOutput方法,都是因为早在 f1d0f5b5 引入新的Fragment支持方案时, 就不再需要修改输出类名。也就是现在所有Transform操作都不会影响输入的类名,也不会增加或减少类。 因此不再需要将控制输出文件的InputClass传给TransformManager。 最后将对已经Deprecated,即将在AGP 8中删除的Transform API的依赖代码都移到DeprecatedTransformWrapper中。 #1212 --- .../shadow/core/gradle/ShadowPlugin.kt | 15 +- .../core/transform_kit/AbstractTransform.kt | 32 +- .../transform_kit/AbstractTransformManager.kt | 15 +- .../core/transform_kit/ClassTransform.kt | 273 ++++-------------- .../core/transform_kit/JavassistTransform.kt | 32 +- .../transform/DeprecatedTransformWrapper.kt | 157 ++++++++++ .../shadow/core/transform/ShadowTransform.kt | 18 +- .../shadow/core/transform/TransformManager.kt | 22 +- 8 files changed, 285 insertions(+), 279 deletions(-) create mode 100644 projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/DeprecatedTransformWrapper.kt diff --git a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt index f7e090cd4..98c1e04cc 100644 --- a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt +++ b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt @@ -24,6 +24,7 @@ import com.android.build.gradle.api.ApplicationVariant import com.android.sdklib.AndroidVersion.VersionCodes import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension import com.tencent.shadow.core.manifest_parser.generatePluginManifest +import com.tencent.shadow.core.transform.DeprecatedTransformWrapper import com.tencent.shadow.core.transform.ShadowTransform import com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder import com.tencent.shadow.core.transform_kit.ClassPoolBuilder @@ -51,11 +52,15 @@ class ShadowPlugin : Plugin { val shadowExtension = project.extensions.create("shadow", ShadowExtension::class.java) if (!project.hasProperty("disable_shadow_transform")) { - baseExtension.registerTransform(ShadowTransform( - project, - lateInitBuilder, - { shadowExtension.transformConfig.useHostContext } - )) + baseExtension.registerTransform( + DeprecatedTransformWrapper(project, + ShadowTransform( + project, + lateInitBuilder, + { shadowExtension.transformConfig.useHostContext } + ) + ) + ) } addFlavorForTransform(baseExtension) diff --git a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransform.kt b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransform.kt index 38c65cb90..f2d171bb7 100644 --- a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransform.kt +++ b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransform.kt @@ -18,11 +18,15 @@ package com.tencent.shadow.core.transform_kit -import com.android.build.api.transform.TransformInvocation import javassist.ClassPool import javassist.CtClass import org.gradle.api.Project -import java.io.* +import java.io.BufferedWriter +import java.io.DataOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.FileWriter +import java.io.OutputStream import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import kotlin.system.measureTimeMillis @@ -46,8 +50,8 @@ abstract class AbstractTransform( mDebugClassJarZOS = ZipOutputStream(FileOutputStream(mDebugClassJar)) } - override fun beforeTransform(invocation: TransformInvocation) { - super.beforeTransform(invocation) + override fun beforeTransform() { + super.beforeTransform() ReplaceClassName.resetErrorCount() cleanDebugClassFileDir() } @@ -57,12 +61,12 @@ abstract class AbstractTransform( //原本预期是不会产生任何影响的。造成了ApplicationInfoTest失败,测试Activity没有被修改superclass。 // mOverrideCheck.prepare(mCtClassInputMap.keys.toSet()) - mTransformManager.setupAll() - mTransformManager.fireAll() + mTransformManager.setupAll(allInputCtClass) + mTransformManager.fireAll(allInputCtClass) } - override fun afterTransform(invocation: TransformInvocation) { - super.afterTransform(invocation) + override fun afterTransform() { + super.afterTransform() mDebugClassJarZOS.flush() mDebugClassJarZOS.close() @@ -71,18 +75,18 @@ abstract class AbstractTransform( //所以需要重新创建一个ClassPool,加载转换后的类,用于各种转换后的检查。 val debugClassPool = classPoolBuilder.build() debugClassPool.appendClassPath(mDebugClassJar.absolutePath) - val inputClassNames = mCtClassInputMap.keys.map { it.name } + val inputClassNames = allInputCtClass.map { it.name } onCheckTransformedClasses(debugClassPool, inputClassNames) } - override fun onOutputClass(entryName: String?, className: String, outputStream: OutputStream) { - classPool[className].debugWriteJar(entryName, mDebugClassJarZOS) - super.onOutputClass(entryName, className, outputStream) + override fun onOutputClass(className: String, outputStream: OutputStream) { + classPool[className].debugWriteJar(mDebugClassJarZOS) + super.onOutputClass(className, outputStream) } - private fun CtClass.debugWriteJar(outputEntryName: String?, outputStream: ZipOutputStream) { + private fun CtClass.debugWriteJar(outputStream: ZipOutputStream) { try { - val entryName = outputEntryName ?: (name.replace('.', '/') + ".class") + val entryName = name.replace('.', '/') + ".class" outputStream.putNextEntry(ZipEntry(entryName)) val p = stopPruning(true) toBytecode(DataOutputStream(outputStream)) diff --git a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransformManager.kt b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransformManager.kt index df6f7e66e..277d77356 100644 --- a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransformManager.kt +++ b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransformManager.kt @@ -21,24 +21,19 @@ package com.tencent.shadow.core.transform_kit import javassist.ClassPool import javassist.CtClass -abstract class AbstractTransformManager( - ctClassInputMap: Map, - private val classPool: ClassPool -) { - private val allInputClass = ctClassInputMap.keys - +abstract class AbstractTransformManager(private val classPool: ClassPool) { abstract val mTransformList: List - fun setupAll() { + fun setupAll(allInputCtClass: Set) { mTransformList.forEach { it.mClassPool = classPool - it.setup(allInputClass) + it.setup(allInputCtClass) } } - fun fireAll() { + fun fireAll(allInputCtClass: Set) { mTransformList.flatMap { it.list }.forEach { transform -> - transform.filter(allInputClass).forEach { + transform.filter(allInputCtClass).forEach { transform.transform(it) } } diff --git a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/ClassTransform.kt b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/ClassTransform.kt index 8ca4ccf2d..c6bb297ea 100644 --- a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/ClassTransform.kt +++ b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/ClassTransform.kt @@ -19,279 +19,120 @@ package com.tencent.shadow.core.transform_kit import com.android.SdkConstants -import com.android.build.api.transform.* -import com.android.build.api.transform.QualifiedContent.ContentType -import com.android.build.api.transform.QualifiedContent.Scope -import com.android.build.gradle.internal.pipeline.TransformManager import com.android.utils.FileUtils -import com.google.common.collect.ImmutableList -import com.google.common.io.Files import org.gradle.api.Project import java.io.File import java.io.FileInputStream -import java.io.FileOutputStream import java.io.OutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream -import java.util.zip.ZipOutputStream /** * 类转换基类 * * @author cubershi */ -abstract class ClassTransform(val project: Project) : Transform() { - val inputSet: MutableSet = mutableSetOf() +abstract class ClassTransform(val project: Project) { /** * 获取输入文件对应的输出文件路径.即将文件this路径中的inputDir部分替换为outputDir. */ - fun File.toOutputFile(inputDir: File, outputDir: File): File { + private fun File.toOutputFile(inputDir: File, outputDir: File): File { return File(outputDir, this.toRelativeString(inputDir)) } - fun input( - inputs: Collection, - outputProvider: TransformOutputProvider - ) { + fun input(inputs: Iterable) { val logger = project.logger if (logger.isInfoEnabled) { val sb = StringBuilder() sb.appendln() inputs.forEach { - it.directoryInputs.forEach { - sb.appendln(it.file.absolutePath) - } - it.jarInputs.forEach { - sb.appendln(it.file.absolutePath) - } + sb.appendln(it.asFile().absolutePath) } logger.info("ClassTransform input paths:$sb") } - inputs.forEach { - it.directoryInputs.forEach { - val inputDir = it.file - val transformInput = TransformInput(it) - inputSet.add(transformInput) - val allFiles = FileUtils.getAllFiles(it.file) - allFiles.filter { - it?.name?.endsWith(SdkConstants.DOT_CLASS) ?: false - }.forEach { - val inputClass = DirInputClass() - inputClass.onInputClass( - it, - it.toOutputFile(inputDir, transformInput.toOutput(outputProvider)) - ) - transformInput.addInputClass(inputClass) + inputs.forEach { transformInput -> + when (transformInput.kind) { + TransformInput.Kind.DIRECTORY -> { + val inputDir = transformInput.asFile() + val allFiles = FileUtils.getAllFiles(inputDir) + allFiles.filter { + it?.name?.endsWith(SdkConstants.DOT_CLASS) ?: false + }.forEach { + val className = loadDotClassFile(it) + transformInput.inputClassNames.add(className) + } } - } - it.jarInputs.forEach { - val transformInput = TransformInput(it) - inputSet.add(transformInput) - ZipInputStream(FileInputStream(it.file)).use { zis -> - var entry: ZipEntry? - while (true) { - entry = zis.nextEntry - if (entry == null) break + TransformInput.Kind.JAR -> { + ZipInputStream(FileInputStream(transformInput.asFile())).use { zis -> + var entry: ZipEntry? + while (true) { + entry = zis.nextEntry + if (entry == null) break - val name = entry.name + val entryName = entry.name - // 忽略一些实际上不会进入编译classpath的文件 - if (entry.isDirectory) continue - if (!name.endsWith(SdkConstants.DOT_CLASS)) continue - if (name.startsWith("META-INF/", true)) continue - if (name.endsWith("module-info.class", true)) continue - if (name.endsWith("package-info.class", true)) continue - - // 记录好entry和name的关系,添加再添加成transform的输入 - val inputClass = JarInputClass() - inputClass.onInputClass(zis, name) - transformInput.addInputClass(inputClass) - } - } - } - } - } + // 忽略一些实际上不会进入编译classpath的文件 + if (entry.isDirectory) continue + if (!entryName.endsWith(SdkConstants.DOT_CLASS)) continue + if (entryName.startsWith("META-INF/", true)) continue + if (entryName.endsWith("module-info.class", true)) continue + if (entryName.endsWith("package-info.class", true)) continue - fun output(outputProvider: TransformOutputProvider) { - inputSet.forEach { input -> - when (input.format) { - Format.DIRECTORY -> { - input.getInputClass().forEach { - val dirInputClass = it as DirInputClass - dirInputClass.getOutput().forEach { - val className = it.first - val file = it.second - Files.createParentDirs(file) - FileOutputStream(file).use { - onOutputClass(null, className, it) - } - } - } - } - Format.JAR -> { - val outputJar = input.toOutput(outputProvider) - Files.createParentDirs(outputJar) - ZipOutputStream(FileOutputStream(outputJar)).use { zos -> - input.getInputClass().forEach { - val jarInputClass = it as JarInputClass - jarInputClass.getOutput().forEach { - val className = it.first - val entryName = it.second - zos.putNextEntry(ZipEntry(entryName)) - onOutputClass(entryName, className, zos) - } + val className = loadClassFromJar(zis) + transformInput.inputClassNames.add(className) } } } } } - } - - abstract fun onOutputClass(entryName: String?, className: String, outputStream: OutputStream) - - abstract fun DirInputClass.onInputClass(classFile: File, outputFile: File) - abstract fun JarInputClass.onInputClass(zipInputStream: ZipInputStream, entryName: String) - abstract fun onTransform() + } - override fun getName(): String = this::class.simpleName!! + abstract fun onOutputClass(className: String, outputStream: OutputStream) - override fun getInputTypes(): MutableSet = TransformManager.CONTENT_CLASS + /** + * 让子类实现的字节码编辑框架加载.class文件,加载后返回类名 + */ + abstract fun loadDotClassFile(classFile: File): String - override fun isIncremental(): Boolean = false + /** + * 让子类实现的字节码编辑框架加载jar中的class,加载后返回类名 + */ + abstract fun loadClassFromJar(zipInputStream: ZipInputStream): String - override fun getScopes(): MutableSet = TransformManager.SCOPE_FULL_PROJECT + abstract fun onTransform() /** * 每一次执行transform前调用。在一次构建中可能有多个Variant,多个Variant会共用同一个 * Transform对象(就是这个类的对象)。在这里提供一个时机清理transform过程中产生的缓存, * 避免对下一次transform产生影响。 */ - open fun beforeTransform(invocation: TransformInvocation) { - invocation.outputProvider.deleteAll() - inputSet.clear() - } - - open fun afterTransform(invocation: TransformInvocation) { - } - - final override fun transform(invocation: TransformInvocation) { - beforeTransform(invocation) - input(invocation.inputs, invocation.outputProvider) - onTransform() - output(invocation.outputProvider) - afterTransform(invocation) - } - - override fun getSecondaryFiles(): ImmutableList? { - val transformJar = File(this::class.java.protectionDomain.codeSource.location.toURI()) - val transformKitJar = - File(ClassTransform::class.java.protectionDomain.codeSource.location.toURI()) - - return ImmutableList.of( - //将当前类运行所在的jar本身作为转换输入的SecondaryFiles,也就作为了这个transform task的inputs的 - //一部分,这使得当这个Transform程序变化时,构建能检测到这个Transform需要重新执行。这是直接编辑这个 - //Transform源码后,应用了这个Plugin的debug工程能直接生效的关键。 - SecondaryFile.nonIncremental(project.files(transformJar)), - SecondaryFile.nonIncremental(project.files(transformKitJar)) - ) - } -} -typealias ClassName_OutputFile = Pair -typealias ClassName_EntryName = Pair - -abstract class InputClass() { - abstract fun renameOutput(oldName: String, newName: String) -} - -class DirInputClass() : InputClass() { - private val outputs = mutableMapOf() - fun getOutput(): Set { - val mutableSet = mutableSetOf() - return outputs.mapTo(mutableSet) { - ClassName_OutputFile(it.key, it.value) - } - } - - fun addOutput(className: String, file: File) { - outputs[className] = file - } - - fun getOutput(className: String): File { - return outputs[className]!! + open fun beforeTransform() { } - override fun renameOutput(oldName: String, newName: String) { - val file = outputs.remove(oldName)!! - val newFileName = file.name.replace(getSimpleName(oldName), getSimpleName(newName)) - outputs[newName] = File(file.parent, newFileName) + open fun afterTransform() { } - private fun getSimpleName(name: String): String { - val i = name.lastIndexOf('.') - if (i == -1) { - return name - } else { - return name.substring(i + 1) - } - } } -class JarInputClass() : InputClass() { - private val outputs = mutableMapOf() - fun getOutput(): Set { - val mutableSet = mutableSetOf() - return outputs.mapTo(mutableSet) { - ClassName_EntryName(it.key, it.value) - } - } - - fun addOutput(className: String, entryName: String) { - outputs[className] = entryName - } - - fun getOutput(className: String): String { - return outputs[className]!! - } - - override fun renameOutput(oldName: String, newName: String) { - outputs[newName] = newName.replace('.', '/') + ".class" - } - -} - - -class TransformInput( - val name: String, - val contentTypes: Set, - val scopes: MutableSet, - val format: Format -) { - constructor(di: DirectoryInput) : this( - di.name, di.contentTypes, di.scopes, Format.DIRECTORY - ) - - constructor(ji: JarInput) : this( - ji.name, ji.contentTypes, ji.scopes, Format.JAR - ) - - private val inputClassSet = mutableSetOf() - - fun addInputClass(inputClass: InputClass) { - inputClassSet.add(inputClass) - } +/** + * 输入数据的封装 + * 外部Transform框架的适配器DeprecatedTransformWrapper或者GradleTransformWrapper + * 把它们的输入封装成这个抽象类的子类 + */ +abstract class TransformInput { + /** + * 一个TransformInput可能是Dir包含多个class文件,也可能是一个jar包包含多个class文件。 + * 这里把它们记下来,等输出的时候要按这个名单输出 + */ + val inputClassNames = mutableSetOf() - fun getInputClass() = inputClassSet.toSet() + enum class Kind { DIRECTORY, JAR } - fun toOutput(outputProvider: TransformOutputProvider) = - outputProvider.getContentLocation( - name, - contentTypes, - scopes, - format - ) + abstract val kind: Kind + abstract fun asFile(): File } diff --git a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/JavassistTransform.kt b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/JavassistTransform.kt index e25b42af3..53d302f34 100644 --- a/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/JavassistTransform.kt +++ b/projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/JavassistTransform.kt @@ -18,7 +18,6 @@ package com.tencent.shadow.core.transform_kit -import com.android.build.api.transform.TransformInvocation import javassist.ClassPool import javassist.CtClass import org.gradle.api.Project @@ -28,30 +27,35 @@ import java.util.zip.ZipInputStream open class JavassistTransform(project: Project, val classPoolBuilder: ClassPoolBuilder) : ClassTransform(project) { - val mCtClassInputMap = mutableMapOf() + /** + * 所有classPool.makeClass生成的CtClass存起来, + * 等AbstractTransformManager处理类时直接从这里 + * 拿到所有CtClass作为字节码编辑的输入数据。 + */ + val allInputCtClass = mutableSetOf() lateinit var classPool: ClassPool - override fun onOutputClass(entryName: String?, className: String, outputStream: OutputStream) { + override fun onOutputClass(className: String, outputStream: OutputStream) { classPool[className].writeOut(outputStream) } - override fun DirInputClass.onInputClass(classFile: File, outputFile: File) { - classFile.inputStream().use { - val ctClass: CtClass = classPool.makeClass(it) - addOutput(ctClass.name, outputFile) - mCtClassInputMap[ctClass] = this + override fun loadDotClassFile(classFile: File): String { + val ctClass: CtClass = classFile.inputStream().use { + classPool.makeClass(it) } + allInputCtClass.add(ctClass) + return ctClass.name } - override fun JarInputClass.onInputClass(zipInputStream: ZipInputStream, entryName: String) { + override fun loadClassFromJar(zipInputStream: ZipInputStream): String { val ctClass = classPool.makeClass(zipInputStream) - addOutput(ctClass.name, entryName) - mCtClassInputMap[ctClass] = this + allInputCtClass.add(ctClass) + return ctClass.name } - override fun beforeTransform(invocation: TransformInvocation) { - super.beforeTransform(invocation) - mCtClassInputMap.clear() + override fun beforeTransform() { + super.beforeTransform() + allInputCtClass.clear() classPool = classPoolBuilder.build() } diff --git a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/DeprecatedTransformWrapper.kt b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/DeprecatedTransformWrapper.kt new file mode 100644 index 000000000..539f4cfba --- /dev/null +++ b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/DeprecatedTransformWrapper.kt @@ -0,0 +1,157 @@ +@file:Suppress("DEPRECATION") + +package com.tencent.shadow.core.transform + +import com.android.build.api.transform.DirectoryInput +import com.android.build.api.transform.Format +import com.android.build.api.transform.JarInput +import com.android.build.api.transform.QualifiedContent +import com.android.build.api.transform.SecondaryFile +import com.android.build.api.transform.Transform +import com.android.build.api.transform.TransformInvocation +import com.android.build.api.transform.TransformOutputProvider +import com.android.build.api.variant.VariantInfo +import com.android.build.gradle.internal.pipeline.TransformManager +import com.google.common.collect.ImmutableList +import com.google.common.io.Files +import com.tencent.shadow.core.transform_kit.ClassTransform +import com.tencent.shadow.core.transform_kit.TransformInput +import org.gradle.api.Project +import java.io.File +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +/** + * 适配ClassTransform到AGP 8之前的Transform API + * 这是最初开发的、长期使用的比较稳定的实现。因为AGP 8去掉了这个API,所以不得不做两种适配。 + */ +class DeprecatedTransformWrapper( + val project: Project, val classTransform: ClassTransform +) : Transform() { + override fun getName(): String = "ShadowTransform" + + override fun getInputTypes(): MutableSet = + TransformManager.CONTENT_CLASS + + override fun isIncremental(): Boolean = false + + override fun isCacheable(): Boolean { + return true + } + + override fun applyToVariant(variant: VariantInfo): Boolean { + return if (variant.isTest) false + else variant.flavorNames.contains(ShadowTransform.ApplyShadowTransformFlavorName) + } + + override fun getScopes(): MutableSet = + TransformManager.SCOPE_FULL_PROJECT + + override fun transform(invocation: TransformInvocation) { + //before Transform clean output + val outputProvider = invocation.outputProvider + outputProvider.deleteAll() + + val inputs: List = invocation.inputs.flatMap { transformInput -> + transformInput.directoryInputs.map { + TransformInputImpl(it) + } + transformInput.jarInputs.map { + TransformInputImpl(it) + } + } + + classTransform.beforeTransform() + classTransform.input(inputs) + classTransform.onTransform() + output(inputs, outputProvider) + classTransform.afterTransform() + } + + /** + * 这个旧版Transform API需要把DIR里的class和jar里的class分别输出到DIR和对应的jar里, + * 这个行为是API决定的,不是通用的,因此要写在这里。对于ClassTransform来说,唯一可以复用的 + * 就是把class输出到OutputStream中。 + */ + private fun output(inputs: Iterable, outputProvider: TransformOutputProvider) { + inputs.forEach { transformInput -> + transformInput as TransformInputImpl + + val outputLocation = outputProvider.getContentLocation( + transformInput.name, + transformInput.contentTypes, + transformInput.scopes, + transformInput.format + ) + + when (transformInput.kind) { + TransformInput.Kind.DIRECTORY -> { + transformInput.inputClassNames.forEach { className -> + val relativePath = className.replace('.', File.separatorChar) + ".class" + val outputClassFile = File(outputLocation, relativePath) + Files.createParentDirs(outputClassFile) + FileOutputStream(outputClassFile).use { + classTransform.onOutputClass(className, it) + } + } + } + + TransformInput.Kind.JAR -> { + Files.createParentDirs(outputLocation) + ZipOutputStream(FileOutputStream(outputLocation)).use { zos -> + transformInput.inputClassNames.forEach { className -> + val entryName = className.replace('.', '/') + ".class" + zos.putNextEntry(ZipEntry(entryName)) + classTransform.onOutputClass(className, zos) + } + } + } + } + } + } + + + override fun getSecondaryFiles(): ImmutableList? { + val transformJar = File(this::class.java.protectionDomain.codeSource.location.toURI()) + val transformKitJar = + File(ClassTransform::class.java.protectionDomain.codeSource.location.toURI()) + + return ImmutableList.of( + //将当前类运行所在的jar本身作为转换输入的SecondaryFiles,也就作为了这个transform task的inputs的 + //一部分,这使得当这个Transform程序变化时,构建能检测到这个Transform需要重新执行。这是直接编辑这个 + //Transform源码后,应用了这个Plugin的debug工程能直接生效的关键。 + SecondaryFile.nonIncremental(project.files(transformJar)), + SecondaryFile.nonIncremental(project.files(transformKitJar)) + ) + } + + private class TransformInputImpl( + val file: File, + val name: String, + val contentTypes: Set, + val scopes: MutableSet, + val format: Format, + override val kind: Kind, + ) : TransformInput() { + constructor(di: DirectoryInput) : this( + di.file, + di.name, + di.contentTypes, + di.scopes, + Format.DIRECTORY, + Kind.DIRECTORY + ) + + constructor(ji: JarInput) : this( + ji.file, + ji.name, + ji.contentTypes, + ji.scopes, + Format.JAR, + Kind.JAR + ) + + override fun asFile(): File = file + + } +} \ No newline at end of file diff --git a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/ShadowTransform.kt b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/ShadowTransform.kt index 5d9f098b6..f235f49c3 100644 --- a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/ShadowTransform.kt +++ b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/ShadowTransform.kt @@ -18,8 +18,6 @@ package com.tencent.shadow.core.transform -import com.android.build.api.transform.TransformInvocation -import com.android.build.api.variant.VariantInfo import com.tencent.shadow.core.transform_kit.AbstractTransform import com.tencent.shadow.core.transform_kit.AbstractTransformManager import com.tencent.shadow.core.transform_kit.ClassPoolBuilder @@ -43,20 +41,10 @@ class ShadowTransform( override val mTransformManager: AbstractTransformManager get() = _mTransformManager - override fun beforeTransform(invocation: TransformInvocation) { - super.beforeTransform(invocation) - _mTransformManager = TransformManager(mCtClassInputMap, classPool, useHostContext) + override fun beforeTransform() { + super.beforeTransform() + _mTransformManager = TransformManager(classPool, useHostContext) classPool.makeInterface(SelfClassNamePlaceholder) } - override fun isCacheable(): Boolean { - return true - } - - override fun getName(): String = "ShadowTransform" - - override fun applyToVariant(variant: VariantInfo): Boolean { - return if (variant.isTest) false - else variant.flavorNames.contains(ApplyShadowTransformFlavorName) - } } \ No newline at end of file diff --git a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt index 3e3622073..d0790952a 100644 --- a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt +++ b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt @@ -18,18 +18,30 @@ package com.tencent.shadow.core.transform -import com.tencent.shadow.core.transform.specific.* +import com.tencent.shadow.core.transform.specific.ActivityOptionsSupportTransform +import com.tencent.shadow.core.transform.specific.ActivityTransform +import com.tencent.shadow.core.transform.specific.AppComponentFactoryTransform +import com.tencent.shadow.core.transform.specific.ApplicationTransform +import com.tencent.shadow.core.transform.specific.ContentProviderTransform +import com.tencent.shadow.core.transform.specific.DialogSupportTransform +import com.tencent.shadow.core.transform.specific.FragmentSupportTransform +import com.tencent.shadow.core.transform.specific.InstrumentationTransform +import com.tencent.shadow.core.transform.specific.IntentServiceTransform +import com.tencent.shadow.core.transform.specific.KeepHostContextTransform +import com.tencent.shadow.core.transform.specific.LayoutInflaterTransform +import com.tencent.shadow.core.transform.specific.PackageItemInfoTransform +import com.tencent.shadow.core.transform.specific.PackageManagerTransform +import com.tencent.shadow.core.transform.specific.ReceiverSupportTransform +import com.tencent.shadow.core.transform.specific.ServiceTransform +import com.tencent.shadow.core.transform.specific.WebViewTransform import com.tencent.shadow.core.transform_kit.AbstractTransformManager -import com.tencent.shadow.core.transform_kit.InputClass import com.tencent.shadow.core.transform_kit.SpecificTransform import javassist.ClassPool -import javassist.CtClass class TransformManager( - ctClassInputMap: Map, classPool: ClassPool, useHostContext: () -> Array -) : AbstractTransformManager(ctClassInputMap, classPool) { +) : AbstractTransformManager(classPool) { /** * 按这个列表的顺序应用各子Transform逻辑。