diff --git a/GradleToolKit/GradleEnvApi/src/main/java/com/ss/android/ugc/bytex/gradletoolkit/Artifact.java b/GradleToolKit/GradleEnvApi/src/main/java/com/ss/android/ugc/bytex/gradletoolkit/Artifact.java index 34497bc..33015cf 100644 --- a/GradleToolKit/GradleEnvApi/src/main/java/com/ss/android/ugc/bytex/gradletoolkit/Artifact.java +++ b/GradleToolKit/GradleEnvApi/src/main/java/com/ss/android/ugc/bytex/gradletoolkit/Artifact.java @@ -4,6 +4,6 @@ * Created by tanlehua on 2019-04-29. */ public enum Artifact { - AAR, ALL_CLASSES/*project*/, APK, JAR, PROCESSED_JAR, CLASSES/*from jar classes*/, JAVAC, MERGED_ASSETS, MERGED_RES, MERGED_MANIFESTS, MERGED_MANIFESTS_WITH_FEATURES, PROCESSED_RES, SYMBOL_LIST, SYMBOL_LIST_WITH_PACKAGE_NAME, + AAR, ALL_CLASSES/*project*/, APK, JAR, @Deprecated PROCESSED_JAR, CLASSES/*from jar classes*/, JAVAC, MERGED_ASSETS, MERGED_RES, MERGED_MANIFESTS, MERGED_MANIFESTS_WITH_FEATURES, PROCESSED_RES, SYMBOL_LIST, SYMBOL_LIST_WITH_PACKAGE_NAME, RAW_RESOURCE_SETS, RAW_ASSET_SETS; } diff --git a/GradleToolKit/build.gradle b/GradleToolKit/build.gradle index bc09a9e..c91c774 100644 --- a/GradleToolKit/build.gradle +++ b/GradleToolKit/build.gradle @@ -10,11 +10,12 @@ dependencies { compileOnly "com.android.tools.build:gradle:$gradle_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile project(':GradleEnvApi') + compile("com.bytedance.android.build:gradle-compat-api:$gradle_compat_version") + runtime("com.bytedance.android.build:gradle-compat-impl:$gradle_compat_version") compile('com.google.auto.service:auto-service:1.0-rc4') { exclude module: 'guava' } - implementation "com.didiglobal.booster:booster-android-gradle-api:3.1.0" } sourceCompatibility = "1.8" targetCompatibility = "1.8" diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BaseVariant.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BaseVariant.kt index ec7423c..1aafcc6 100644 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BaseVariant.kt +++ b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BaseVariant.kt @@ -1,81 +1,18 @@ package com.ss.android.ugc.bytex.gradletoolkit -import com.android.build.api.artifact.ArtifactType import com.android.build.gradle.api.BaseVariant -import com.android.build.gradle.internal.api.BaseVariantImpl import com.android.build.gradle.internal.publishing.AndroidArtifacts import com.android.build.gradle.internal.scope.VariantScope -import com.android.build.gradle.internal.variant.BaseVariantData -import com.android.build.gradle.tasks.MergeResources +import com.bytedance.gradle.compat.extension.compatVariant import org.gradle.api.artifacts.ArtifactCollection -import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty import java.io.File val BaseVariant.scope: VariantScope - get() = if (ANDROID_GRADLE_PLUGIN_VERSION.major < 4 || (ANDROID_GRADLE_PLUGIN_VERSION.major == 4 && ANDROID_GRADLE_PLUGIN_VERSION.minor == 0)) { - this.scope40 - } else { - this.scope41 - } -private val BaseVariant.scope40: VariantScope - get() = ReflectionUtils.callMethod(this, javaClass, "getVariantData", arrayOf(), arrayOf()).scope - -private val BaseVariant.scope41: VariantScope - get() = ReflectionUtils.getField(this, BaseVariantImpl::class.java, "componentProperties").let { - ReflectionUtils.callMethod(it, it.javaClass, "getVariantScope", arrayOf(), arrayOf()) as VariantScope - } + get() = compatVariant.variantScope.originScope() fun BaseVariant.getArtifactCollection(configType: AndroidArtifacts.ConsumedConfigType, artifactScope: AndroidArtifacts.ArtifactScope, artifactType: AndroidArtifacts.ArtifactType): ArtifactCollection { - return if (ANDROID_GRADLE_PLUGIN_VERSION.major < 4 || (ANDROID_GRADLE_PLUGIN_VERSION.major == 4 && ANDROID_GRADLE_PLUGIN_VERSION.minor == 0)) { - this.getArtifactCollection40(configType, artifactScope, artifactType) - } else { - this.getArtifactCollection41(configType, artifactScope, artifactType) - } -} - -private fun BaseVariant.getArtifactCollection40(configType: AndroidArtifacts.ConsumedConfigType, artifactScope: AndroidArtifacts.ArtifactScope, artifactType: AndroidArtifacts.ArtifactType): ArtifactCollection { - return scope.getArtifactCollection(configType, artifactScope, artifactType) -} - -private fun BaseVariant.getArtifactCollection41(configType: AndroidArtifacts.ConsumedConfigType, artifactScope: AndroidArtifacts.ArtifactScope, artifactType: AndroidArtifacts.ArtifactType): ArtifactCollection { - return ReflectionUtils.getField(this, BaseVariantImpl::class.java, "componentProperties").let { - ReflectionUtils.callPublicMethod(it, it.javaClass, "getVariantDependencies", arrayOf(), arrayOf()).let { - ReflectionUtils.callPublicMethod(it, it.javaClass, "getArtifactCollection", arrayOf( - AndroidArtifacts.ConsumedConfigType::class.java, AndroidArtifacts.ArtifactScope::class.java, AndroidArtifacts.ArtifactType::class.java - ), arrayOf(configType, artifactScope, artifactType)) - } - } -} - -fun BaseVariant.getArtifactFiles(artifactType: ArtifactType): Collection { - return if (ANDROID_GRADLE_PLUGIN_VERSION.major > 3 || (ANDROID_GRADLE_PLUGIN_VERSION.major == 3 && ANDROID_GRADLE_PLUGIN_VERSION.minor >= 5)) { - this.getArtifactFiles35_(artifactType) - } else { - this.getArtifactFiles30_(artifactType) - } -} - -private fun BaseVariant.getArtifactFiles35_(artifactType: ArtifactType): Collection { - return scope.artifacts.getFinalProducts(artifactType).orNull?.map { it.asFile }?.toSet() - ?: emptyList() -} - -private fun BaseVariant.getArtifactFiles30_(artifactType: ArtifactType): Collection { - return scope.artifacts.getArtifactFiles(artifactType).files + return compatVariant.rawArtifacts.getArtifactCollection(configType, artifactScope, artifactType) } val BaseVariant.blameLogOutputFolder: File - get() = if (ANDROID_GRADLE_PLUGIN_VERSION.major < 4 || (ANDROID_GRADLE_PLUGIN_VERSION.major == 4 && ANDROID_GRADLE_PLUGIN_VERSION.minor == 0)) { - this.blameLogOutputFolder40 - } else { - this.blameLogOutputFolder41 - } -private val BaseVariant.blameLogOutputFolder40: File - get() = scope.resourceBlameLogDir - - -private val BaseVariant.blameLogOutputFolder41: File - get() = mergeResources.let { - ReflectionUtils.callMethod(it, MergeResources::class.java, "getBlameLogOutputFolder", arrayOf(), arrayOf()).asFile.get() - } \ No newline at end of file + get() = compatVariant.blameLogOutputFolder diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BoosterBridge.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BoosterBridge.kt deleted file mode 100644 index 122d17c..0000000 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/BoosterBridge.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.ss.android.ugc.bytex.gradletoolkit - -import com.android.build.gradle.api.ApplicationVariant -import com.android.build.gradle.api.BaseVariant -import com.android.build.gradle.api.LibraryVariant -import com.android.build.gradle.internal.scope.InternalArtifactType -import com.android.build.gradle.internal.scope.getOutputDir -import com.didiglobal.booster.gradle.* -import java.io.File - -internal val BaseVariant.bridgeAllClass: Collection - get() = this.allClasses -internal val BaseVariant.bridgeApk: Collection - get() = this.apk -internal val BaseVariant.bridgeMergedAssets: Collection - get() = this.mergedAssets -internal val BaseVariant.bridgeMergedRes: Collection - get() = this.mergedRes -internal val BaseVariant.bridgeMergedManifests: Collection - get() = if (ANDROID_GRADLE_PLUGIN_VERSION.major == 3 && ANDROID_GRADLE_PLUGIN_VERSION.minor == 4) { - listOf(when (this) { - is ApplicationVariant -> File(InternalArtifactType.MERGED_MANIFESTS.getOutputDir(scope.globalScope.buildDir), name) - is LibraryVariant -> File(InternalArtifactType.LIBRARY_MANIFEST.getOutputDir(scope.globalScope.buildDir), name) - else -> throw IllegalArgumentException(this.name) - }) - } else { - this.mergedManifests - } -internal val BaseVariant.bridgeMergedProcessedRes: Collection - get() = this.processedRes -internal val BaseVariant.bridgeSymbolList: Collection - get() = this.symbolList -internal val BaseVariant.bridgeSymbolListWithPackageName: Collection - get() = this.symbolListWithPackageName - diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/MergeResources.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/MergeResources.kt deleted file mode 100644 index 137d4d5..0000000 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/MergeResources.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.ss.android.ugc.bytex.gradletoolkit - -import com.android.build.gradle.internal.DependencyResourcesComputer -import com.android.build.gradle.tasks.MergeResources -import com.android.build.gradle.tasks.ResourceAwareTask -import com.android.ide.common.resources.ResourceSet -import org.gradle.api.provider.Property -import java.io.File - - -//todo fix reflection -internal fun MergeResources.resourceSetList(): Collection { - return if (ANDROID_GRADLE_PLUGIN_VERSION.major > 4 || (ANDROID_GRADLE_PLUGIN_VERSION.major == 4 && ANDROID_GRADLE_PLUGIN_VERSION.minor >= 1)) { - resourceSetList41_() - } else if (ANDROID_GRADLE_PLUGIN_VERSION.major >= 4 || ANDROID_GRADLE_PLUGIN_VERSION.minor >= 5) { - resourceSetList35_() - } else { - resourceSetList30_() - } -} - -private fun MergeResources.resourceSetList30_(): Collection { - //this.computeResourceSetList().flatMap { it.sourceFiles }.toSet() - return ReflectionUtils.callMethod>(this, MergeResources::class.java, "computeResourceSetList", arrayOf(), arrayOf()) - .flatMap { - ReflectionUtils.callPublicMethod>(it, it.javaClass, "getSourceFiles", arrayOf(), arrayOf()) - }.toSet() -} - -private fun MergeResources.resourceSetList35_(): Collection { - //this.getConfiguredResourceSets(this.getPreprocessor()).flatMap { it.sourceFiles }.toSet() - val preprocessor = ReflectionUtils.callMethod(this, MergeResources::class.java, "getPreprocessor", arrayOf(), arrayOf()) - return ReflectionUtils.callMethod>(this, MergeResources::class.java, "getConfiguredResourceSets", arrayOf(Class.forName("com.android.ide.common.resources.ResourcePreprocessor")), arrayOf(preprocessor)) - .flatMap { - ReflectionUtils.callPublicMethod>(it, it.javaClass, "getSourceFiles", arrayOf(), arrayOf()) - }.toSet() -} - -private fun MergeResources.resourceSetList41_(): Collection { - //this.getConfiguredResourceSets(this.getPreprocessor(),this.getAaptEnv().getOrNull()).flatMap { it.sourceFiles }.toSet() - val preprocessor = ReflectionUtils.callMethod(this, MergeResources::class.java, "getPreprocessor", arrayOf(), arrayOf()) - val aaptEnv = ReflectionUtils.callPublicMethod>(this, this::class.java, "getAaptEnv", arrayOf(), arrayOf()).orNull - return ReflectionUtils.callMethod>(this, MergeResources::class.java, "getConfiguredResourceSets", arrayOf(Class.forName("com.android.ide.common.resources.ResourcePreprocessor"), String::class.java), arrayOf(preprocessor, aaptEnv)) - .flatMap { - ReflectionUtils.callPublicMethod>(it, it.javaClass, "getSourceFiles", arrayOf(), arrayOf()) - }.toSet() -} - -fun MergeResources.dependenciesResourcesSet(): List { - return if (ANDROID_GRADLE_PLUGIN_VERSION.major > 4 || (ANDROID_GRADLE_PLUGIN_VERSION.major == 4 && ANDROID_GRADLE_PLUGIN_VERSION.minor >= 1)) { - dependenciesResourcesSet41_() - } else { - dependenciesResourcesSet35_() - } -} - -private fun MergeResources.dependenciesResourcesSet41_(): List { - val computer = ReflectionUtils.callMethod(this, ResourceAwareTask::class.java, "getResourcesComputer", arrayOf(), arrayOf()); - val aaptEnv = ReflectionUtils.callPublicMethod>(this, this::class.java, "getAaptEnv", arrayOf(), arrayOf()).orNull - return ReflectionUtils.callMethod>(computer, DependencyResourcesComputer::class.java, "compute", arrayOf(Boolean::class.java, String::class.java), arrayOf(false, aaptEnv)); -} - -private fun MergeResources.dependenciesResourcesSet35_(): List { - val computer = ReflectionUtils.callMethod(this, ResourceAwareTask::class.java, "getResourcesComputer", arrayOf(), arrayOf()); - return ReflectionUtils.callMethod>(computer, DependencyResourcesComputer::class.java, "compute", arrayOf(Boolean::class.java), arrayOf(false)); -} \ No newline at end of file diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/MergeSourceSetFolders.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/MergeSourceSetFolders.kt deleted file mode 100644 index b2ac110..0000000 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/MergeSourceSetFolders.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.ss.android.ugc.bytex.gradletoolkit - -import com.android.build.gradle.tasks.MergeSourceSetFolders -import java.io.File - -internal fun MergeSourceSetFolders.assetSetList(): Collection { - return if (ANDROID_GRADLE_PLUGIN_VERSION.major >= 4 || ANDROID_GRADLE_PLUGIN_VERSION.minor >= 5) { - assetSetList35_() - } else { - assetSetList30_() - } -} - - -private fun MergeSourceSetFolders.assetSetList30_(): Collection { - //this.computeAssetSetList().flatMap { it.sourceFiles }.toSet() - return ReflectionUtils.callMethod>(this, MergeSourceSetFolders::class.java, "computeAssetSetList", arrayOf(), arrayOf()) - .flatMap { - ReflectionUtils.callPublicMethod>(it, it.javaClass, "getSourceFiles", arrayOf(), arrayOf()) - }.toSet() -} - -fun MergeSourceSetFolders.assetSetList35_(): Collection { - //this.computeAssetSetList$gradle().flatMap { it.sourceFiles }.toSet() - return ReflectionUtils.callMethod>(this, MergeSourceSetFolders::class.java, "computeAssetSetList\$gradle", arrayOf(), arrayOf()) - .flatMap { - ReflectionUtils.callPublicMethod>(it, it.javaClass, "getSourceFiles", arrayOf(), arrayOf()) - }.toSet() -} diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Project.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Project.kt index 6f2e072..5b18ff8 100644 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Project.kt +++ b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Project.kt @@ -1,61 +1,17 @@ package com.ss.android.ugc.bytex.gradletoolkit -import com.android.build.gradle.internal.VariantManager + + + import com.android.build.gradle.internal.scope.VariantScope import com.android.builder.model.Version import com.android.repository.Revision +import com.bytedance.gradle.compat.AGP + import org.gradle.api.Project //todo:fix me val revision = Revision.parseRevision(Version.ANDROID_GRADLE_PLUGIN_VERSION) fun Project.findVariantScope(variantName: String): VariantScope? { - return findVariantManager().findVariantScope(variantName) -} - - -fun Project.findVariantManager(): VariantManager { - return if (revision.major > 3 || revision.minor >= 6) { - findVariantManager36() - } else { - findVariantManager35() - } -} - -private fun Project.findVariantManager35(): VariantManager { - return project.plugins.findPlugin(com.android.build.gradle.AppPlugin::class.java)!!.variantManager -} - -private fun Project.findVariantManager36(): VariantManager { - return project.plugins.findPlugin("com.android.internal.application")!!.let { - it.javaClass.getMethod("getVariantManager").invoke(it) as VariantManager - } -} - -fun VariantManager.findVariantScope(variantName: String): VariantScope? { - return if (revision.major < 4) { - findVariantScope3X(variantName) - } else if (revision.minor == 0) { - findVariantScope40(variantName) - } else { - findVariantScope41(variantName) - } -} - -private fun VariantManager.findVariantScope3X(variantName: String): VariantScope? { - return variantScopes.firstOrNull { it.fullVariantName == variantName } -} - -private fun VariantManager.findVariantScope40(variantName: String): VariantScope? { - return variantScopes.firstOrNull { it::class.java.getMethod("getName").invoke(it) == variantName } -} - - -private fun VariantManager.findVariantScope41(variantName: String): VariantScope? { - for (info in this.javaClass.getMethod("getMainComponents").invoke(this) as List) { - val properties = info.javaClass.getMethod("getProperties").invoke(info) - if (properties.javaClass.getMethod("getName").invoke(properties) == variantName) { - return properties.javaClass.getMethod("getVariantScope").invoke(properties) as VariantScope - } - } - return null + return AGP.withAndroidPlugin(this).variantManager.variants.firstOrNull { it.name==variantName }?.originScope() } \ No newline at end of file diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Task.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Task.kt index 438b85a..ec5c1f8 100644 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Task.kt +++ b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/Task.kt @@ -1,26 +1,20 @@ package com.ss.android.ugc.bytex.gradletoolkit -import com.android.build.gradle.AppExtension -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.LibraryExtension + import com.android.build.gradle.api.BaseVariant +import com.bytedance.gradle.compat.AGP import org.gradle.api.Task val Task.variant: BaseVariant? get() = this.let { - val android = project.extensions.findByName("android") as? BaseExtension ?: return@let null - val allVariant = if (android is AppExtension) { - android.applicationVariants.map { it as BaseVariant } - } else if (android is LibraryExtension) { - android.libraryVariants.map { it as BaseVariant } - } else { - return@let null - } + if(!AGP.isAndroidModule(project)) return@let null + val android = AGP.withAndroidExtension(project) + val allVariant = android.variants var matchedVariant: BaseVariant? = null for (variant in allVariant) { if (it.name.endsWith(variant.name.capitalize())) { if (matchedVariant == null || matchedVariant.name.length < variant.name.length) { - matchedVariant = variant + matchedVariant = variant.raw() } } } diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformEnvImpl.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformEnvImpl.kt index bff64e1..dddd36c 100644 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformEnvImpl.kt +++ b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformEnvImpl.kt @@ -1,15 +1,12 @@ package com.ss.android.ugc.bytex.gradletoolkit import com.android.build.api.transform.TransformInvocation -import com.android.build.gradle.AppExtension -import com.android.build.gradle.api.ApplicationVariant import com.android.build.gradle.internal.publishing.AndroidArtifacts -import com.android.build.gradle.internal.scope.InternalArtifactType +import com.bytedance.gradle.compat.AGP import com.google.auto.service.AutoService import java.io.File import java.util.* -import kotlin.streams.asStream -import kotlin.streams.toList +import com.bytedance.gradle.compat.extension.variant /** * Created by tanlehua on 2019-04-29. @@ -27,87 +24,29 @@ class TransformEnvImpl() : TransformEnv { return Collections.emptyList() } return when (artifact) { - Artifact.AAR -> { - invocation!!.variant.getArtifactCollection( - AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, - AndroidArtifacts.ArtifactScope.ALL, - AndroidArtifacts.ArtifactType.AAR - ).artifactFiles.files - } - Artifact.JAR -> { - invocation!!.variant.getArtifactCollection( - AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, - AndroidArtifacts.ArtifactScope.ALL, - AndroidArtifacts.ArtifactType.JAR - ).artifactFiles.files - } + Artifact.AAR -> invocation!!.variant.aarArtifactCollection() + Artifact.JAR -> invocation!!.variant.jarArtifactCollection() Artifact.PROCESSED_JAR -> { - invocation!!.variant.getArtifactCollection( - AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, - AndroidArtifacts.ArtifactScope.ALL, - if (ANDROID_GRADLE_PLUGIN_VERSION.major > 3 || ANDROID_GRADLE_PLUGIN_VERSION.minor >= 2) { - AndroidArtifacts.ArtifactType.PROCESSED_JAR - } else { - AndroidArtifacts.ArtifactType.JAR - } - ).artifactFiles.files - } - Artifact.CLASSES -> { - invocation!!.variant.getArtifactCollection( - AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, - AndroidArtifacts.ArtifactScope.ALL, - AndroidArtifacts.ArtifactType.CLASSES - ).artifactFiles.files - } - Artifact.ALL_CLASSES -> invocation!!.variant.bridgeAllClass - Artifact.APK -> invocation!!.variant.bridgeApk - Artifact.JAVAC -> - if (ANDROID_GRADLE_PLUGIN_VERSION.major == 3 && ANDROID_GRADLE_PLUGIN_VERSION.minor >= 2 && ANDROID_GRADLE_PLUGIN_VERSION.minor <= 5) { - invocation!!.variant.getArtifactFiles(InternalArtifactType.JAVAC) + if (AGP.agpVersionCode() >= 400) { + invocation!!.variant.processedJarArtifactCollection() } else { - emptyList() - } - Artifact.MERGED_ASSETS -> invocation!!.variant.bridgeMergedAssets - Artifact.MERGED_RES -> invocation!!.variant.bridgeMergedRes - Artifact.MERGED_MANIFESTS -> invocation!!.variant.bridgeMergedManifests.flatMap { - when { - it.isDirectory -> - it.walk().asStream().filter { it.isFile }.toList() - it.isFile -> listOf(it) - else -> emptyList() - }.filter { file -> - file.isFile && file.name.endsWith(".xml") + invocation!!.variant.jarArtifactCollection() } } - Artifact.MERGED_MANIFESTS_WITH_FEATURES -> - invocation!!.project.rootProject.subprojects - .mapNotNull { it.extensions.findByName("android") as? AppExtension? } - .map { - var result: ApplicationVariant? = null - it.applicationVariants.forEach { - if (invocation!!.context.variantName.contains(it.name) && (result == null || it.name.contains(result!!.name))) { - result = it - } - } - result!! - } - .flatMap { - it.bridgeMergedManifests - } - .flatMap { - when { - it.isDirectory -> it.walk().asStream().filter { it.isFile }.toList() - it.isFile -> listOf(it) - else -> emptyList() - } - }.filter { it -> - it.isFile && it.name.endsWith(".xml") - }.toList() - Artifact.PROCESSED_RES -> invocation!!.variant.bridgeMergedProcessedRes - Artifact.SYMBOL_LIST -> invocation!!.variant.bridgeSymbolList - Artifact.SYMBOL_LIST_WITH_PACKAGE_NAME -> invocation!!.variant.bridgeSymbolListWithPackageName - Artifact.RAW_RESOURCE_SETS -> invocation!!.variant.mergeResources.resourceSetList() - Artifact.RAW_ASSET_SETS -> invocation!!.variant.mergeAssets.assetSetList() + Artifact.CLASSES -> invocation!!.variant.classesArtifactCollection() + Artifact.ALL_CLASSES -> invocation!!.variant.allClasses + Artifact.APK -> invocation!!.variant.apk + Artifact.JAVAC -> invocation!!.variant.javacClasses + Artifact.MERGED_ASSETS -> invocation!!.variant.mergedAssets + Artifact.MERGED_RES -> invocation!!.variant.mergedRes + Artifact.MERGED_MANIFESTS -> invocation!!.variant.mergedManifestFiles + Artifact.MERGED_MANIFESTS_WITH_FEATURES -> invocation!!.variant.mergedManifestFilesWithFeatures + Artifact.PROCESSED_RES -> invocation!!.variant.processedRes + Artifact.SYMBOL_LIST -> invocation!!.variant.symbolList + Artifact.SYMBOL_LIST_WITH_PACKAGE_NAME -> invocation!!.variant.symbolListWithPackageName + Artifact.RAW_RESOURCE_SETS -> invocation!!.variant.mergeResources.computeResourceSetList0() + ?: emptyList() + Artifact.RAW_ASSET_SETS -> invocation!!.variant.mergeSourceSet.assetSetList() } } } \ No newline at end of file diff --git a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformInvocation.kt b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformInvocation.kt index df6c438..0343526 100644 --- a/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformInvocation.kt +++ b/GradleToolKit/src/main/kotlin/com/ss/android/ugc/bytex/gradletoolkit/TransformInvocation.kt @@ -1,59 +1,17 @@ package com.ss.android.ugc.bytex.gradletoolkit import com.android.build.api.transform.TransformInvocation -import com.android.build.gradle.AppExtension -import com.android.build.gradle.LibraryExtension import com.android.build.gradle.api.BaseVariant +import com.bytedance.gradle.compat.extension.project +import com.bytedance.gradle.compat.extension.variant import org.gradle.api.Project -import org.gradle.api.Task -import java.lang.reflect.Field -import java.util.* /** * Created by yangzhiqian on 2020-01-13
*/ val TransformInvocation.project: Project - get() = context.let { context -> - if (context is Task) { - context.project - } else { - try { - (context.javaClass.getDeclaredField("this$1").apply { - isAccessible = true - }.get(context)?.run { - javaClass.getDeclaredField("this$0").apply { - isAccessible = true - }.get(this) as? Task - })?.project - } catch (e: ReflectiveOperationException) { - null - } ?: context.javaClass.allFields().filter { it.type == Project::class.java }.map { - it.isAccessible = true - it.get(context) - }.filterIsInstance().firstOrNull()!! - } - } + get() = project -private fun Class<*>.allFields(): List { - val result = LinkedList() - result.addAll(declaredFields) - superclass?.allFields()?.apply { - result.addAll(this) - } - return result -} val TransformInvocation.variant: BaseVariant - get() = project.extensions.getByName("android").let { android -> - this.context.variantName.let { variant -> - when (android) { - is AppExtension -> when { - variant.endsWith("AndroidTest") -> android.testVariants.single { it.name == variant } - variant.endsWith("UnitTest") -> android.unitTestVariants.single { it.name == variant } - else -> android.applicationVariants.single { it.name == variant } - } - is LibraryExtension -> android.libraryVariants.single { it.name == variant } - else -> TODO("variant not found") - } - } - } + get() = variant.raw() diff --git a/TransformEngine/src/main/java/com/ss/android/ugc/bytex/transformer/TransformContext.java b/TransformEngine/src/main/java/com/ss/android/ugc/bytex/transformer/TransformContext.java index b14199d..9dcafe8 100644 --- a/TransformEngine/src/main/java/com/ss/android/ugc/bytex/transformer/TransformContext.java +++ b/TransformEngine/src/main/java/com/ss/android/ugc/bytex/transformer/TransformContext.java @@ -251,7 +251,7 @@ synchronized void markRunningState(State state) { public boolean isDaemonSingleUse() { try { return ((ProjectInternal) project).getServices().get(DaemonScanInfo.class).isSingleUse(); - } catch (UnknownServiceException e) { + } catch (Throwable e) { e.printStackTrace(); return false; } diff --git a/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlineExtension.java b/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlineExtension.java index ecd50b6..e31b397 100644 --- a/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlineExtension.java +++ b/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlineExtension.java @@ -3,7 +3,19 @@ import com.ss.android.ugc.bytex.common.BaseExtension; +import java.util.HashSet; +import java.util.Set; + public class AccessInlineExtension extends BaseExtension { + private Set whiteList = new HashSet<>(); + + public Set getWhiteList() { + return whiteList; + } + + public void setWhiteList(Set whiteList) { + this.whiteList = whiteList; + } @Override public String getName() { diff --git a/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlinePlugin.java b/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlinePlugin.java index ea43a2f..aa58010 100644 --- a/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlinePlugin.java +++ b/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/AccessInlinePlugin.java @@ -6,6 +6,7 @@ import com.ss.android.ugc.bytex.access_inline.visitor.ShrinkAccessClassVisitor; import com.ss.android.ugc.bytex.common.CommonPlugin; import com.ss.android.ugc.bytex.common.TransformConfiguration; +import com.ss.android.ugc.bytex.common.utils.Utils; import com.ss.android.ugc.bytex.common.visitor.ClassVisitorChain; import com.ss.android.ugc.bytex.transformer.TransformEngine; @@ -28,13 +29,17 @@ protected Context getContext(Project project, AppExtension android, AccessInline @Override public void traverse(@NotNull String relativePath, @NotNull ClassVisitorChain chain) { super.traverse(relativePath, chain); - chain.connect(new PreProcessClassVisitor(this.context)); + if (!context.inWhiteList(Utils.getClassName(relativePath))) { + chain.connect(new PreProcessClassVisitor(this.context)); + } } @Override public void traverseAndroidJar(@NotNull String relativePath, @NotNull ClassVisitorChain chain) { super.traverseAndroidJar(relativePath, chain); - chain.connect(new PreProcessClassVisitor(this.context, true)); + if (!context.inWhiteList(Utils.getClassName(relativePath))) { + chain.connect(new PreProcessClassVisitor(this.context, true)); + } } @Override diff --git a/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/Context.java b/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/Context.java index 043cd52..dc3476d 100644 --- a/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/Context.java +++ b/access-inline-plugin/src/main/java/com/ss/android/ugc/bytex/access_inline/Context.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; public class Context extends BaseContext { private static final String SEPARATOR = "#"; @@ -39,8 +40,28 @@ private static String getKey(String owner, String name, String desc) { private final Map accessedMembers = new ConcurrentHashMap<>(512); + private final List whiteList = new ArrayList<>(); + private Graph graph; + @Override + public synchronized void init() { + super.init(); + whiteList.clear(); + for (String s : extension.getWhiteList()) { + whiteList.add(Pattern.compile(s)); + } + } + + public boolean inWhiteList(String className) { + for (Pattern pattern : whiteList) { + if (pattern.matcher(className).matches()) { + return true; + } + } + return false; + } + public Access$MethodEntity addAccess$Method(String owner, String name, String desc) { Access$MethodEntity entity = new Access$MethodEntity(owner, name, desc); access$Methods.put(getKey(owner, name, desc), entity); @@ -274,6 +295,7 @@ public void releaseContext() { super.releaseContext(); access$Methods.clear(); accessedMembers.clear(); + whiteList.clear(); graph = null; } } diff --git a/common/build.gradle b/common/build.gradle index 312a1c2..a44a9e3 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -14,6 +14,7 @@ dependencies { compile project(':GradleToolKit') compile "com.google.code.gson:gson:2.8.2" compile group: 'dom4j', name: 'dom4j', version: '1.6.1' + compile("com.bytedance.android.build:gradle-compat-api:$gradle_compat_version") } sourceCompatibility = "1.8" diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/AbsPlugin.java b/common/src/main/java/com/ss/android/ugc/bytex/common/AbsPlugin.java index 7fc336f..e10299e 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/AbsPlugin.java +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/AbsPlugin.java @@ -64,11 +64,11 @@ public boolean isRunningAlone() { @Override public final void apply(@NotNull Project project) { - GlobalByteXBuildListener.INSTANCE.onByteXPluginApply(project, this); this.project = project; this.android = project.getExtensions().getByType(AppExtension.class); ProjectOptions.INSTANCE.init(project); GlobalWhiteListManager.INSTANCE.init(project); + GlobalByteXBuildListener.INSTANCE.onByteXPluginApply(project, this); Class extensionClass = getExtensionClass(); if (extensionClass != null) { Instantiator instantiator = ((DefaultGradle) project.getGradle()).getServices().get(Instantiator.class); diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/Constants.java b/common/src/main/java/com/ss/android/ugc/bytex/common/Constants.java index 8e93098..95d05d9 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/Constants.java +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/Constants.java @@ -1,5 +1,7 @@ package com.ss.android.ugc.bytex.common; +import com.ss.android.ugc.bytex.common.configuration.StringProperty; + import org.objectweb.asm.Opcodes; /** @@ -7,5 +9,31 @@ */ public class Constants { //此处不用常量是因为后期尝试直接升级,内联则没有这个效果 - public static int ASM_API = Opcodes.ASM6; + public static int ASM_API; + + static { + switch (StringProperty.ASM_API.value()) { + case "ASM4": + ASM_API = Opcodes.ASM4; + break; + case "ASM5": + ASM_API = Opcodes.ASM5; + break; + case "ASM6": + ASM_API = Opcodes.ASM6; + break; + case "ASM7": + ASM_API = Opcodes.ASM7; + break; + case "ASM8": + ASM_API = Opcodes.ASM8; + break; + case "ASM9": + ASM_API = Opcodes.ASM9; + break; + default: { + throw new IllegalArgumentException(StringProperty.ASM_API.value() + " is not supported!"); + } + } + } } diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/builder/ByteXBuildListenerManager.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/builder/ByteXBuildListenerManager.kt index a746119..5f12504 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/builder/ByteXBuildListenerManager.kt +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/builder/ByteXBuildListenerManager.kt @@ -1,6 +1,7 @@ package com.ss.android.ugc.bytex.common.builder import com.ss.android.ugc.bytex.common.builder.internal.DefaultByteXBuildListener +import com.ss.android.ugc.bytex.common.configuration.BooleanProperty import com.ss.android.ugc.bytex.common.flow.main.MainProcessHandlerListener import com.ss.android.ugc.bytex.common.flow.main.MainProcessHandlerListenerManager import java.util.* @@ -12,8 +13,10 @@ object ByteXBuildListenerManager { private val listeners: MutableList = LinkedList() init { - registerByteXBuildListener(DefaultByteXBuildListener) - registerMainProcessHandlerListener(DefaultByteXBuildListener) + if (BooleanProperty.ENABLE_BUILD_RECORDER.value()) { + registerByteXBuildListener(DefaultByteXBuildListener) + registerMainProcessHandlerListener(DefaultByteXBuildListener) + } } @Synchronized diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/BooleanProperty.java b/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/BooleanProperty.java index 49b7b47..68b8a50 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/BooleanProperty.java +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/BooleanProperty.java @@ -17,7 +17,11 @@ public enum BooleanProperty implements Property { ENABLE_SEPARATE_PROCESSING_NOTINCREMENTAL("bytex.enableSeparateProcessingNotIncremental", false), USE_FIXED_TIMESTAMP("bytex.useFixedTimestamp", true), FORBID_USE_LENIENT_MUTATION_DURING_GET_ARTIFACT("bytex.forbidUseLenientMutationDuringGetArtifact", false), - ALLOW_REWRITE("bytex.allowRewrite", false); + ALLOW_REWRITE("bytex.allowRewrite", false), + CHECK_TRAVERSE_MODIFY("bytex.check_traverse_modify", true), + CHECK_DEBUG_TRAVERSE_MODIFY("bytex.check_debug_traverse_modify", false), + ENABLE_BUILD_RECORDER("bytex.enable_build_recorder", true), + ENABLE_GRADLE_DAEMON_IGNORE_CLASSLOADER_SINGLETON("bytex.enable_gradle_daemon_ignore_classloader_singleton", true); @NonNull private final String propertyName; diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/StringProperty.java b/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/StringProperty.java index bccd060..5f22e24 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/StringProperty.java +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/configuration/StringProperty.java @@ -7,7 +7,8 @@ */ public enum StringProperty implements Property { EXCEPTION_IGNORE_LIST("bytex.exceptionIgnoreClassList", ""), - GLOBAL_IGNORE_LIST("bytex.globalIgnoreClassList", ""); + GLOBAL_IGNORE_LIST("bytex.globalIgnoreClassList", ""), + ASM_API("bytex.ASM_API", "ASM6"); @Nonnull private final String propertyName; diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/CachedGraphBuilder.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/CachedGraphBuilder.kt index 4786b2f..ec405ee 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/CachedGraphBuilder.kt +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/CachedGraphBuilder.kt @@ -19,13 +19,13 @@ class CachedGraphBuilder(private val graphCacheFile: File?, shouldLoadCache: Boo AsynchronousGraphCacheStorage( DefaultGraphCacheStorage( if (useRamCache && BooleanProperty.ENABLE_RAM_CACHE.value() && BooleanProperty.ENABLE_RAM_NODES_CACHE.value()) { - RamNodeCacheStorage + RamNodeCacheStorage() } else { null }, GsonFileClassCacheStorage( if (useRamCache && BooleanProperty.ENABLE_RAM_CACHE.value() && BooleanProperty.ENABLE_RAM_CLASSES_CACHE.value()) { - RamClassesCacheStorage + RamClassesCacheStorage() } else { null } @@ -36,8 +36,8 @@ class CachedGraphBuilder(private val graphCacheFile: File?, shouldLoadCache: Boo try { shouldLoadCache && graphCache.loadCache(graphCacheFile, this).apply { if (!this) { - RamClassesCacheStorage.clear() - RamNodeCacheStorage.clear() + RamClassesCacheStorage().clear() + RamNodeCacheStorage().clear() graphCacheFile?.delete() throw IllegalStateException("Failed to load cache") @@ -46,8 +46,8 @@ class CachedGraphBuilder(private val graphCacheFile: File?, shouldLoadCache: Boo } finally { if (graphCacheFile != null) { graphCacheFile.delete() - RamClassesCacheStorage.clearCache(graphCacheFile) - RamNodeCacheStorage.clearCache(graphCacheFile) + RamClassesCacheStorage().clearCache(graphCacheFile) + RamNodeCacheStorage().clearCache(graphCacheFile) } } diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamClassesCacheStorage.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamClassesCacheStorage.kt index 8ff6214..ed74836 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamClassesCacheStorage.kt +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamClassesCacheStorage.kt @@ -1,14 +1,23 @@ package com.ss.android.ugc.bytex.common.graph.cache +import com.ss.android.ugc.bytex.common.configuration.BooleanProperty import com.ss.android.ugc.bytex.common.graph.ClassEntity +import com.ss.android.ugc.bytex.common.utils.GradleDaemonIgnoreClassLoaderSingletonManager import java.io.File import java.util.concurrent.ConcurrentHashMap /** * Created by yangzhiqian on 2020-03-04
*/ -internal object RamClassesCacheStorage : ClassCacheStorage { - private val caches = ConcurrentHashMap>() +internal class RamClassesCacheStorage : ClassCacheStorage { + private val caches = + if (BooleanProperty.ENABLE_GRADLE_DAEMON_IGNORE_CLASSLOADER_SINGLETON.value()) { + GradleDaemonIgnoreClassLoaderSingletonManager.computeIfAbsent>>(this, this.javaClass.name) { + ConcurrentHashMap() + } + } else { + ConcurrentHashMap() + } fun clear() { caches.clear() diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamNodeCacheStorage.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamNodeCacheStorage.kt index f09c089..ac9eb8a 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamNodeCacheStorage.kt +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/graph/cache/RamNodeCacheStorage.kt @@ -1,8 +1,10 @@ package com.ss.android.ugc.bytex.common.graph.cache +import com.ss.android.ugc.bytex.common.configuration.BooleanProperty import com.ss.android.ugc.bytex.common.graph.ClassNode import com.ss.android.ugc.bytex.common.graph.InterfaceNode import com.ss.android.ugc.bytex.common.graph.Node +import com.ss.android.ugc.bytex.common.utils.GradleDaemonIgnoreClassLoaderSingletonManager import java.io.File import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -10,8 +12,15 @@ import java.util.concurrent.ConcurrentHashMap /** * Created by yangzhiqian on 2020/9/3
*/ -internal object RamNodeCacheStorage : NodeCacheStorage { - private val caches = ConcurrentHashMap>() +internal class RamNodeCacheStorage : NodeCacheStorage { + private val caches = + if (BooleanProperty.ENABLE_GRADLE_DAEMON_IGNORE_CLASSLOADER_SINGLETON.value()) { + GradleDaemonIgnoreClassLoaderSingletonManager.computeIfAbsent>>(this, this.javaClass.name) { + ConcurrentHashMap() + } + } else { + ConcurrentHashMap() + } fun clear() { caches.clear() diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/hook/HookInjector.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/hook/HookInjector.kt index c8846e7..79a1c8b 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/hook/HookInjector.kt +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/hook/HookInjector.kt @@ -4,6 +4,7 @@ import com.android.build.api.transform.* import com.android.build.gradle.AppExtension import com.android.build.gradle.internal.pipeline.TransformInvocationBuilder import com.android.build.gradle.internal.pipeline.TransformTask +import com.bytedance.gradle.compat.extension.HookContext import com.google.common.collect.Lists import com.ss.android.ugc.bytex.common.IPlugin import com.ss.android.ugc.bytex.common.hook.TransformInputCompatUtil.covertToTransformInput @@ -185,7 +186,10 @@ internal sealed class HookInjector(project: Project) { } } //模拟创建Transform的context对象用于构建TransformInvocation - val context = object : Context { + val context = object : HookContext { + //gradle compat 需要反射字段获取 task ,如果被 hook 了就会报错,这里直接让 compat 库 + //暴露一个接口,让它主动查询这是个 hook 的类,然后直接获取 task 而不是反射 + override val hookTask: Task = task //for context.project val project = task.project override fun getPath(): String = task.path diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/processor/ClassFileAnalyzer.java b/common/src/main/java/com/ss/android/ugc/bytex/common/processor/ClassFileAnalyzer.java index 7ba66c6..ae92f58 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/processor/ClassFileAnalyzer.java +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/processor/ClassFileAnalyzer.java @@ -2,6 +2,7 @@ import com.android.build.api.transform.Status; +import com.ss.android.ugc.bytex.common.configuration.BooleanProperty; import com.ss.android.ugc.bytex.common.exception.ByteXException; import com.ss.android.ugc.bytex.common.exception.GlobalWhiteListManager; import com.ss.android.ugc.bytex.common.flow.main.MainProcessHandler; @@ -16,6 +17,7 @@ import com.ss.android.ugc.bytex.transformer.cache.FileData; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import java.util.List; @@ -67,6 +69,12 @@ public void handle(FileData fileData) { String relativePath = fileData.getRelativePath(); ClassReader cr = new ClassReader(raw); int flag = getFlag(handlers); + ClassNode checkCn = null; + if (BooleanProperty.CHECK_TRAVERSE_MODIFY.value() && (context.isReleaseBuild() || BooleanProperty.CHECK_DEBUG_TRAVERSE_MODIFY.value())) { + //需要检验是否有插件会改指令,我们先accept一下,用一个ClassNode接收一下,后面再check + checkCn = new SafeClassNode(); + cr.accept(checkCn, flag); + } ClassVisitorChain chain = getClassVisitorChain(relativePath); if (this.mGraphBuilder != null) { //do generate class diagram @@ -89,7 +97,15 @@ public void handle(FileData fileData) { }); ClassNode cn = new SafeClassNode(); chain.append(cn); - chain.accept(cr, flag); + if (checkCn != null) { + //之前解析过,这次不解析,直接用之前的ClassNode来传递数据 + chain.accept(checkCn); + if (checkClassModified(cn, checkCn)) { + throw new IllegalStateException("Found plugin modified the class during the traverse process"); + } + } else { + chain.accept(cr, flag); + } pluginList.forEach(plugin -> { switch (process) { case TRAVERSE_INCREMENTAL: @@ -105,6 +121,9 @@ public void handle(FileData fileData) { throw new RuntimeException("Unsupported Process"); } }); + if (checkCn != null && checkClassModified(cn, checkCn)) { + throw new IllegalStateException("Found plugin modified the class during the traverse process"); + } } catch (ByteXException e) { throw new RuntimeException(String.format("%s\n\tFailed to resolve class %s[%s]", e.getMessage(), fileData.getRelativePath(), Utils.getAllFileCachePath(context, fileData.getRelativePath())), e); } catch (Exception e) { @@ -144,4 +163,25 @@ private int getFlag(List handlers) { } return flag; } + + + private static boolean checkClassModified(ClassNode n1, ClassNode n2) { + ClassWriter cw1 = new ClassWriter(ClassWriter.COMPUTE_MAXS); + n1.accept(cw1); + byte[] b1 = cw1.toByteArray(); + + ClassWriter cw2 = new ClassWriter(ClassWriter.COMPUTE_MAXS); + n2.accept(cw2); + byte[] b2 = cw2.toByteArray(); + + if (b1.length != b2.length) { + return true; + } + for (int i = 0; i < b1.length; i++) { + if (b1[i] != b2[i]) { + return true; + } + } + return false; + } } diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/utils/GradleDaemonIgnoreClassLoaderSingletonManager.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/utils/GradleDaemonIgnoreClassLoaderSingletonManager.kt new file mode 100644 index 0000000..9ce097a --- /dev/null +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/utils/GradleDaemonIgnoreClassLoaderSingletonManager.kt @@ -0,0 +1,54 @@ +package com.ss.android.ugc.bytex.common.utils + +import org.gradle.api.invocation.Gradle + +/** + * 用途:全局单例,忽略因为gradle的类加载器变化导致的全局单例泄露问题 + * 问题:每次修改buildSrc内的代码,都将导致gradle顶层的ClassLoader变化,同时导致插件的ClassLoader变化 + * 这将使得插件中的单例类被同一个gradle进程加载两次,如果此时有插件想用单例做daemon进程编译复用,此种状态 + * 单例数据将失效,并造成内存泄露。 + * 这里使用比jre的类加载器来存,利用其中的parallelLockMap对象,按照加上ClassLoader的过滤对单例数据进行 + * 加载,同时在ClassLoader变化的情况下,将之前的缓存失效,做到真正的全局单例唯一。 + */ +object GradleDaemonIgnoreClassLoaderSingletonManager { + private val map = Gradle::class.java.classLoader.let { + val field = ClassLoader::class.java.getDeclaredField("parallelLockMap") + field.isAccessible = true + field.get(it) as MutableMap + } + + @Synchronized + fun put(context: Any, key: String, value: Any) { + map.put(key, Pair(context.javaClass, value)) + } + + @Synchronized + fun get(context: Any, key: String, clearIfClassLoaderChanged: Boolean = true): T? { + val pair = (map.get(key) as? Pair<*, *>) ?: return null + if (pair.first == context.javaClass) { + //classloader可用 + return pair.second as T + } else { + if (clearIfClassLoaderChanged) { + map.remove(key) + } + return null + } + } + + @Synchronized + fun computeIfAbsent(context: Any, key: String, function: (Any) -> T): T { + val get = get(context, key) + if (get == null) { + put(context, key, function.invoke(key) as Any) + return get(context, key) as T + } else { + return get + } + } + + @Synchronized + fun remove(key: String): Any? { + return map.remove(key) + } +} \ No newline at end of file diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/utils/MethodMatcher.kt b/common/src/main/java/com/ss/android/ugc/bytex/common/utils/MethodMatcher.kt index d00378a..c945e22 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/utils/MethodMatcher.kt +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/utils/MethodMatcher.kt @@ -13,6 +13,7 @@ fun main() { println(MethodMatcher("!com/Clas").match("com/Class", "ab", "()V")) println(MethodMatcher("(com/Clas||com/Class)&&!(com/Clas&&com/Class)").match("com/Class", "ab", "()V")) println(MethodMatcher("(com/Clas||com/Class||(com/Clas||com/Class)&&!(!com/Clas&&com/Class))&&!(!com/Clas&&com/Class)").match("com/Class", "ab", "()V")) + println(MethodMatcher("android.content.res.Resources#openRawResourceFd").match("android.content.res.Resources", "openRawResourceFd", "()V")) } interface IMethodMatcher { diff --git a/common/src/main/java/com/ss/android/ugc/bytex/common/visitor/ClassVisitorChain.java b/common/src/main/java/com/ss/android/ugc/bytex/common/visitor/ClassVisitorChain.java index 9ca4cec..2ebcfa3 100644 --- a/common/src/main/java/com/ss/android/ugc/bytex/common/visitor/ClassVisitorChain.java +++ b/common/src/main/java/com/ss/android/ugc/bytex/common/visitor/ClassVisitorChain.java @@ -59,6 +59,16 @@ public byte[] accept(ClassReader classReader, int flag) { } else return null; } + + public byte[] accept(ClassNode classNode) { + if (head != null) { + classNode.accept(head); + } + if (classWriter != null) { + return classWriter.toByteArray(); + } else return null; + } + public boolean isEmpty() { return head == null; } diff --git a/coverage/README-zh.md b/coverage/README-zh.md index e7acc40..fe032af 100644 --- a/coverage/README-zh.md +++ b/coverage/README-zh.md @@ -15,8 +15,8 @@ #### 1. 添加classpath ```groovy -classpath "com.bytedance.android.byteX:coverage_lib:${Versions.BYTEX_VERSION}" -classpath "com.bytedance.android.byteX:coverage_plugin:${Versions.BYTEX_VERSION}" +classpath "com.bytedance.android.byteX:coverage-lib:${Versions.BYTEX_VERSION}" +classpath "com.bytedance.android.byteX:coverage-plugin:${Versions.BYTEX_VERSION}" ``` #### 2. 应用插件 diff --git a/coverage/README.md b/coverage/README.md index dbce27a..9711766 100644 --- a/coverage/README.md +++ b/coverage/README.md @@ -15,8 +15,8 @@ In Tiktok, we found 1/6 of the useless classes, excluding the resources they ref #### 1. Add classpath ```groovy -classpath "com.bytedance.android.byteX:coverage_lib:${Versions.BYTEX_VERSION}" -classpath "com.bytedance.android.byteX:coverage_plugin:${Versions.BYTEX_VERSION}" +classpath "com.bytedance.android.byteX:coverage-lib:${Versions.BYTEX_VERSION}" +classpath "com.bytedance.android.byteX:coverage-plugin:${Versions.BYTEX_VERSION}" ``` #### 2. Apply plugin diff --git a/gradle/ext.gradle b/gradle/ext.gradle index 86919c9..b676f5f 100644 --- a/gradle/ext.gradle +++ b/gradle/ext.gradle @@ -36,9 +36,10 @@ ext { upload_dir = project.rootProject.file('gradle_plugins').path gradle_version = '3.5.3' gson_version = '2.8.2' - asm_version = '6.2.1' + asm_version = '9.2' guava_version = '23.0' ByteX_Version = upload_version + gradle_compat_version = "1.1.0.2" bintray_key = BINTRAY_KEY } println("upload2Maven:$upload2Maven") diff --git a/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/cli/QuickReferCheckTask.kt b/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/cli/QuickReferCheckTask.kt index 8c3b6fa..f07c54b 100644 --- a/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/cli/QuickReferCheckTask.kt +++ b/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/cli/QuickReferCheckTask.kt @@ -4,6 +4,7 @@ import com.android.build.api.transform.Context import com.android.build.gradle.AppExtension import com.android.build.gradle.api.BaseVariant import com.android.build.gradle.internal.pipeline.TransformInvocationBuilder +import com.bytedance.gradle.compat.extension.HookContext import com.ss.android.ugc.bytex.common.configuration.BooleanProperty import com.ss.android.ugc.bytex.common.utils.Utils import com.ss.android.ugc.bytex.common.white_list.WhiteList @@ -16,13 +17,18 @@ import com.ss.android.ugc.bytex.transformer.TransformContext import com.ss.android.ugc.bytex.transformer.TransformOptions import org.gradle.api.DefaultTask import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.internal.project.ProjectInternal import org.gradle.api.tasks.TaskAction import org.gradle.workers.WorkerExecutor import java.util.* import javax.inject.Inject -open class QuickReferCheckTask @Inject constructor(val pj: Project, val android: AppExtension, val extension: ReferCheckExtension, val variant: BaseVariant) : DefaultTask(), Context { +open class QuickReferCheckTask @Inject constructor(val pj: Project, val android: AppExtension, val extension: ReferCheckExtension, val variant: BaseVariant) : DefaultTask(), HookContext { + + override val hookTask: Task + get() = this + private val whiteList by lazy { WhiteList().apply { LinkedList().let { diff --git a/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/visitor/ReferCheckMethodVisitor.java b/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/visitor/ReferCheckMethodVisitor.java index 723da3f..3c06a66 100644 --- a/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/visitor/ReferCheckMethodVisitor.java +++ b/refer-check-plugin/src/main/java/com/ss/android/ugc/bytex/refercheck/visitor/ReferCheckMethodVisitor.java @@ -86,12 +86,14 @@ private void checkMethod(final int opcode, final String owner, final String name throw new RuntimeException(String.format("%s should be a interface, but it's a class now. It was referred at class [%s], method [%s].", ownerNode.entity.name, this.className, this.methodName)); } graph.traverseChildren((InterfaceNode) ownerNode, child -> { - if (child instanceof ClassNode && !TypeUtil.isAbstract(child.entity.access) - && TypeUtil.isAbstract(child.confirmOriginMethod(name, desc).access())) { - //非抽象子类没有实现这个抽象类 - checkIssueReceiver.addNotAccessMember( - className, methodName, methodDesc, methodAccess, sourceFile, processingLineNumber, - child.entity.name, name, desc, originMethod.access(), InaccessibleNode.TYPE_NOT_IMPLEMENT); + if (child instanceof ClassNode && !TypeUtil.isAbstract(child.entity.access)) { + MethodEntity childImpl = child.confirmOriginMethod(name, desc); + if (childImpl == null || TypeUtil.isAbstract(childImpl.access())) { + //非抽象子类没有实现这个抽象类 + checkIssueReceiver.addNotAccessMember( + className, methodName, methodDesc, methodAccess, sourceFile, processingLineNumber, + child.entity.name, name, desc, originMethod.access(), InaccessibleNode.TYPE_NOT_IMPLEMENT); + } } return false; }); @@ -100,11 +102,14 @@ private void checkMethod(final int opcode, final String owner, final String name throw new RuntimeException(String.format("%s should be a class, but it's an interface now. It was referred at class [%s], method [%s].", ownerNode.entity.name, this.className, this.methodName)); } graph.traverseChildren((ClassNode) ownerNode, child -> { - if (!TypeUtil.isAbstract(child.entity.access) && TypeUtil.isAbstract(child.confirmOriginMethod(name, desc).access())) { - //非抽象子类没有实现这个抽象方法 - checkIssueReceiver.addNotAccessMember( - className, methodName, methodDesc, methodAccess, sourceFile, processingLineNumber, - child.entity.name, name, desc, originMethod.access(), InaccessibleNode.TYPE_NOT_IMPLEMENT); + if (!TypeUtil.isAbstract(child.entity.access)) { + MethodEntity childImpl = child.confirmOriginMethod(name, desc); + if (childImpl == null || TypeUtil.isAbstract(childImpl.access())) { + //非抽象子类没有实现这个抽象方法 + checkIssueReceiver.addNotAccessMember( + className, methodName, methodDesc, methodAccess, sourceFile, processingLineNumber, + child.entity.name, name, desc, originMethod.access(), InaccessibleNode.TYPE_NOT_IMPLEMENT); + } } return false; }); diff --git a/wiki/ByteX-Developer-API-en.md b/wiki/ByteX-Developer-API-en.md index 5685e79..9cb2928 100644 --- a/wiki/ByteX-Developer-API-en.md +++ b/wiki/ByteX-Developer-API-en.md @@ -583,6 +583,8 @@ ByteXBuildListenerManager.INSTANCE.registerMainProcessHandlerListener(yourMainPr - bytex.${extension.getName()}.alone:Whether to run the plugin independently,boolean , false by default. - bytex.useFixedTimestamp:Whether to use the fixed timestamp (0) of the entity in the output jar, this has a great benefit for incremental compilation because the entity is same and the timestamp is unchanged,and tasks after bytex could hit the cache (such as DexBuilder).boolean , true by default. - bytex.forbidUseLenientMutationDuringGetArtifact: Whether resolving configuration with lenient off when calling GradleEnv.getArtifact .This can fix the deadlock while calling FileCollection.getFiles() in some projects.boolean , false by default. +- bytex.ASM_API:The API value passed to ASM library, currently it can be ASM4, ASM5, ASM6, ASM7, ASM8, ASM9. ASM6 by default. +- bytex.enable_gradle_daemon_ignore_classloader_singleton:Whether to handle the memory leak of ByteX Graph singleton Class caused by the change of Gradle ClassLoader.true by default. ## Development Considerations diff --git a/wiki/ByteX-Developer-API-zh.md b/wiki/ByteX-Developer-API-zh.md index 0c7daed..98978e9 100644 --- a/wiki/ByteX-Developer-API-zh.md +++ b/wiki/ByteX-Developer-API-zh.md @@ -589,6 +589,8 @@ ByteXBuildListenerManager.INSTANCE.registerMainProcessHandlerListener(yourMainPr - bytex.${extension.getName()}.alone:是否独立运行某个插件,boolean类型,默认false。 - bytex.useFixedTimestamp:是否固定一个输出文件jar中entity的时间戳(0),这个对增量编译有比较大收益,因为输出内容不变+时间戳不变,后续task可以命中cache(比如DexBuilder)。boolean类型,默认true。 - bytex.forbidUseLenientMutationDuringGetArtifact:在调用GradleEnv.getArtifact时是否禁止使用Lenient方式获取,这个可以规避部分工程因为FileCollection.getFiles()死锁问题.boolean类型,默认false。 +- bytex.ASM_API:设置使用ASM传递的API值,目前可以是ASM4、ASM5、ASM6、ASM7、ASM8、ASM9.默认ASM6. +- bytex.enable_gradle_daemon_ignore_classloader_singleton:是否兜底因为Gradle ClassLoader改变导致的ByteX Graph单例内存泄露问题.默认true. ## 开发注意事项 ### 分支管理 diff --git a/wiki/Class.png b/wiki/Class.png new file mode 100644 index 0000000..8e14e5f Binary files /dev/null and b/wiki/Class.png differ diff --git a/wiki/Class.xmind b/wiki/Class.xmind new file mode 100644 index 0000000..0f2ed38 Binary files /dev/null and b/wiki/Class.xmind differ diff --git a/wiki/flows.png b/wiki/flows.png new file mode 100644 index 0000000..439b9aa Binary files /dev/null and b/wiki/flows.png differ diff --git a/wiki/structure.png b/wiki/structure.png index 0bf1f5b..2c8b390 100644 Binary files a/wiki/structure.png and b/wiki/structure.png differ