Skip to content

Commit

Permalink
feat(core.manifest-parser): 实现编译期解析插件Manifest生成java类
Browse files Browse the repository at this point in the history
通过PluginClassLoader新增方法loadPluginManifest可加载该类。

#696
  • Loading branch information
shifujun committed Dec 31, 2021
1 parent 7b366f5 commit 8a99c4f
Show file tree
Hide file tree
Showing 26 changed files with 1,063 additions and 0 deletions.
1 change: 1 addition & 0 deletions projects/sdk/core/gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
implementation "com.android.tools.build:gradle:$build_gradle_version"
implementation 'com.googlecode.json-simple:json-simple:1.1'
implementation project(':transform')
implementation project(':manifest-parser')
testImplementation 'junit:junit:4.12'
testImplementation gradleTestKit()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@

package com.tencent.shadow.core.gradle

import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.tasks.ProcessMultiApkApplicationManifest
import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension
import com.tencent.shadow.core.manifest_parser.generatePluginManifest
import com.tencent.shadow.core.transform.ShadowTransform
import com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder
import com.tencent.shadow.core.transform_kit.ClassPoolBuilder
import org.gradle.api.*
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.provider.Property
import java.io.File
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.jvm.isAccessible
Expand Down Expand Up @@ -61,6 +65,8 @@ class ShadowPlugin : Plugin<Project> {
initAndroidClassPoolBuilder(baseExtension, project)

createPackagePluginTasks(project)

createGeneratePluginManifestTasks(project)
}
}

Expand All @@ -83,6 +89,42 @@ class ShadowPlugin : Plugin<Project> {
}
}

private fun createGeneratePluginManifestTasks(project: Project) {
val appExtension: AppExtension = project.extensions.getByType(AppExtension::class.java)
appExtension.applicationVariants.filter { variant ->
variant.productFlavors.any { flavor ->
flavor.dimension == ShadowTransform.DimensionName &&
flavor.name == ShadowTransform.ApplyShadowTransformFlavorName
}
}.forEach { pluginVariant ->
val output = pluginVariant.outputs.first()
val processManifestTask = output.processManifestProvider.get()
val manifestFile = (processManifestTask as ProcessMultiApkApplicationManifest).mainMergedManifest.get().asFile
val variantName = manifestFile.parentFile.name
val outputDir = File(project.buildDir, "generated/source/pluginManifest/$variantName")

// 添加生成PluginManifest.java任务
val task = project.tasks.register("generate${variantName.capitalize()}PluginManifest") {
it.dependsOn(processManifestTask)
it.inputs.file(manifestFile)
it.outputs.dir(outputDir).withPropertyName("outputDir")
val packageForR = (project.tasks.getByName("processPluginDebugResources").property("namespace") as Property<String>).get()
it.doLast {
generatePluginManifest(manifestFile,
outputDir,
"com.tencent.shadow.core.manifest_parser",
packageForR)
}
}
project.tasks.getByName("compile${variantName.capitalize()}JavaWithJavac").dependsOn(task)

// 把PluginManifest.java添加为源码
appExtension.sourceSets.getByName(variantName).java {
srcDir(outputDir)
}
}
}

private fun addFlavorForTransform(baseExtension: BaseExtension) {
baseExtension.flavorDimensionList.add(ShadowTransform.DimensionName)
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class PackageMultiPluginTest {
.withProjectDir(ROOT_PROJECT_DIR)
.withPluginClasspath()
.withArguments(listOf(
"-xgeneratePluginDebugPluginManifest",
"-Pdisable_shadow_transform=true",
":plugin1:PackageMultiPlugin"
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class PackageOnlyPluginTest {
.withProjectDir(PLUGIN1_PROJECT_DIR)
.withPluginClasspath()
.withArguments(listOf(
"-xgeneratePluginDebugPluginManifest",
"-Pdisable_shadow_transform=true",
":plugin1:packageOnlyApkPlugin"
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class PackagePluginTaskTest {
.withProjectDir(ROOT_PROJECT_DIR)
.withPluginClasspath()
.withArguments(listOf(
"-xgeneratePluginDebugPluginManifest",
"-Pdisable_shadow_transform=true",
":plugin1:packageDebugPlugin"
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.tencent.shadow.core.loader.classloaders

import android.os.Build
import com.tencent.shadow.core.runtime.PluginManifest
import dalvik.system.BaseDexClassLoader
import org.jetbrains.annotations.TestOnly
import java.io.File
Expand Down Expand Up @@ -111,6 +112,11 @@ class PluginClassLoader(
return clazz
}

internal fun loadPluginManifest(): PluginManifest {
val clazz = findClass("com.tencent.shadow.core.manifest_parser.PluginManifest")
return PluginManifest::class.java.cast(clazz.newInstance())
}

}

private fun String.subStringBeforeDot() = substringBeforeLast('.', "")
Expand Down
1 change: 1 addition & 0 deletions projects/sdk/core/manifest-parser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
22 changes: 22 additions & 0 deletions projects/sdk/core/manifest-parser/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apply plugin: 'kotlin'

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.squareup:javapoet:1.11.1'
implementation project(':runtime')
testImplementation 'junit:junit:4.12'
testImplementation 'commons-io:commons-io:2.9.0'
testImplementation 'com.tencent.shadow.coding:java-build-config'
}

compileKotlin {
kotlinOptions {
jvmTarget = "1.6"
}
}

compileTestKotlin {
kotlinOptions {
jvmTarget = "1.6"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.tencent.shadow.core.manifest_parser

sealed class AndroidManifestKeys {
companion object {
const val appComponentFactory = "android:appComponentFactory"
const val `package` = "package"
const val name = "android:name"
const val theme = "android:theme"
const val configChanges = "android:configChanges"
const val windowSoftInputMode = "android:windowSoftInputMode"
const val authorities = "android:authorities"
const val `intent-filter` = "intent-filter"
const val action = "action"
const val manifest = "manifest"
const val application = "application"
const val activity = "activity"
const val service = "service"
const val provider = "provider"
const val receiver = "receiver"
}
}
typealias ComponentMapKey = String
typealias ComponentMapValue = Any
typealias ComponentMap = Map<ComponentMapKey, ComponentMapValue>
typealias MutableComponentMap = MutableMap<ComponentMapKey, ComponentMapValue>
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.tencent.shadow.core.manifest_parser

import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory

typealias ManifestMap = Map<String, Any>

/**
* 读取xml格式的Manifest到内存Map中
*/
class AndroidManifestReader {
/**
* 读取入口方法
*
* @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件,
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
*/
fun read(xmlFile: File): ManifestMap {
val manifest = readXml(xmlFile).documentElement
val application = readApplication(manifest)
val globalAttributes = readGlobalAttributes(manifest, application)
val components = readComponents(application)
return globalAttributes.plus(components)
}

private fun readXml(xmlFile: File): Document {
try {
val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance()
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
return documentBuilder.parse(xmlFile)!!
} catch (e: Exception) {
throw RuntimeException("xml应该是AGP生成的合法文件,所以不兼容任何xml读取错误", e)
}
}

private fun readApplication(manifest: Element): Element? {
val elements = manifest.getElementsByTagName(AndroidManifestKeys.application)
return if (elements.length == 1) {
val node = elements.item(0)
assert(node.nodeType == Node.ELEMENT_NODE)
elements.item(0) as Element
} else {
null
}
}

/**
* 读取Manifest中那些唯一的属性
*/
private fun readGlobalAttributes(manifest: Element, application: Element?): Map<String, Any> {
val globalAttributes = mutableMapOf<String, Any>()

fun manifestAttribute(name: String) {
globalAttributes[name] = manifest.getAttribute(name)
}

fun applicationAttribute(name: String) {
if (application != null) {
val attribute = application.getAttribute(name)
if (attribute.isNotEmpty()) {
globalAttributes[name] = attribute
}
}
}

manifestAttribute(AndroidManifestKeys.`package`)
listOf(
AndroidManifestKeys.name,
AndroidManifestKeys.theme,
AndroidManifestKeys.appComponentFactory,
).forEach(::applicationAttribute)
return globalAttributes
}

private fun readComponents(application: Element?) =
listOf(
AndroidManifestKeys.activity to ::parseActivity,
AndroidManifestKeys.service to ::parseService,
AndroidManifestKeys.receiver to ::parseReceiver,
AndroidManifestKeys.provider to ::parseProvider,
).map { (componentKey, parseMethod) ->
val componentArray = parseComponents(application, componentKey, parseMethod)
componentKey to componentArray
}


private fun parseComponents(
application: Element?,
componentKey: String,
parseFunction: (Element) -> ComponentMap
): Array<ComponentMap> {
if (application == null) {
return emptyArray()
}
val nodeList = application.getElementsByTagName(componentKey)
val length = nodeList.length
val collectionList = mutableListOf<ComponentMap>()
for (i in 0 until length) {
val node = nodeList.item(i)
assert(node.nodeType == Node.ELEMENT_NODE)
val map = parseFunction(node as Element)
collectionList.add(map)
}
return collectionList.toTypedArray()
}

private fun parseActivity(element: Element): ComponentMap {
val activityMap = parseComponent(element).toMutableMap()

listOf(
AndroidManifestKeys.theme,
AndroidManifestKeys.configChanges,
AndroidManifestKeys.windowSoftInputMode,
).forEach { attributeKey ->
activityMap.putAttributeIfNotNull(element, attributeKey)
}
return activityMap
}

private fun parseService(element: Element): ComponentMap {
return parseComponent(element)
}

private fun parseReceiver(element: Element): ComponentMap {
val receiverMap = parseComponent(element).toMutableMap()

val receiverActions = parseReceiverActions(element)
if (receiverActions.isNotEmpty()) {
receiverMap[AndroidManifestKeys.action] = receiverActions
}

return receiverMap
}

private fun parseReceiverActions(receiverElement: Element): List<String> {
val intentFilters = receiverElement.getElementsByTagName(AndroidManifestKeys.`intent-filter`)
val collectionList = mutableListOf<String>()
for (i in 0 until intentFilters.length) {
val intentFilter = intentFilters.item(i)
assert(intentFilter.nodeType == Node.ELEMENT_NODE)
val actions = (intentFilter as Element).getElementsByTagName(AndroidManifestKeys.action)
for (j in 0 until actions.length) {
val action = actions.item(j)
assert(action.nodeType == Node.ELEMENT_NODE)
val actionName = (action as Element).getAttribute(AndroidManifestKeys.name)
collectionList.add(actionName)
}
}
return collectionList
}

private fun parseProvider(element: Element): ComponentMap {
val providerMap = parseComponent(element).toMutableMap()

providerMap.putAttributeIfNotNull(element, AndroidManifestKeys.authorities)

return providerMap
}

private fun parseComponent(element: Element): ComponentMap {
val componentName = element.getAttribute(AndroidManifestKeys.name)
return mapOf(
AndroidManifestKeys.name to componentName
)
}

private fun MutableComponentMap.putAttributeIfNotNull(
componentElement: Element,
attributeKey: String) {
if (componentElement.hasAttribute(attributeKey)) {
this[attributeKey] = componentElement.getAttribute(attributeKey)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.tencent.shadow.core.manifest_parser

import java.io.File

/**
* manifest-parser的入口方法
*
* @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件,
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
* @param outputDir 生成文件的输出目录
* @param packageName 生成类的包名
* @param packageForR 生成对R.java引用时需要的R文件的包名
*/
fun generatePluginManifest(xmlFile: File, outputDir: File, packageName: String, packageForR: String) {
val androidManifest = AndroidManifestReader().read(xmlFile)
val generator = PluginManifestGenerator(packageForR)
generator.generate(androidManifest, outputDir, packageName)
}
Loading

0 comments on commit 8a99c4f

Please sign in to comment.