Skip to content

Commit 8a99c4f

Browse files
committed
feat(core.manifest-parser): 实现编译期解析插件Manifest生成java类
通过PluginClassLoader新增方法loadPluginManifest可加载该类。 #696
1 parent 7b366f5 commit 8a99c4f

File tree

26 files changed

+1063
-0
lines changed

26 files changed

+1063
-0
lines changed

projects/sdk/core/gradle-plugin/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
implementation "com.android.tools.build:gradle:$build_gradle_version"
1717
implementation 'com.googlecode.json-simple:json-simple:1.1'
1818
implementation project(':transform')
19+
implementation project(':manifest-parser')
1920
testImplementation 'junit:junit:4.12'
2021
testImplementation gradleTestKit()
2122

projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt

+42
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818

1919
package com.tencent.shadow.core.gradle
2020

21+
import com.android.build.gradle.AppExtension
2122
import com.android.build.gradle.AppPlugin
2223
import com.android.build.gradle.BaseExtension
24+
import com.android.build.gradle.tasks.ProcessMultiApkApplicationManifest
2325
import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension
26+
import com.tencent.shadow.core.manifest_parser.generatePluginManifest
2427
import com.tencent.shadow.core.transform.ShadowTransform
2528
import com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder
2629
import com.tencent.shadow.core.transform_kit.ClassPoolBuilder
2730
import org.gradle.api.*
2831
import org.gradle.api.plugins.BasePlugin
32+
import org.gradle.api.provider.Property
2933
import java.io.File
3034
import kotlin.reflect.full.declaredFunctions
3135
import kotlin.reflect.jvm.isAccessible
@@ -61,6 +65,8 @@ class ShadowPlugin : Plugin<Project> {
6165
initAndroidClassPoolBuilder(baseExtension, project)
6266

6367
createPackagePluginTasks(project)
68+
69+
createGeneratePluginManifestTasks(project)
6470
}
6571
}
6672

@@ -83,6 +89,42 @@ class ShadowPlugin : Plugin<Project> {
8389
}
8490
}
8591

92+
private fun createGeneratePluginManifestTasks(project: Project) {
93+
val appExtension: AppExtension = project.extensions.getByType(AppExtension::class.java)
94+
appExtension.applicationVariants.filter { variant ->
95+
variant.productFlavors.any { flavor ->
96+
flavor.dimension == ShadowTransform.DimensionName &&
97+
flavor.name == ShadowTransform.ApplyShadowTransformFlavorName
98+
}
99+
}.forEach { pluginVariant ->
100+
val output = pluginVariant.outputs.first()
101+
val processManifestTask = output.processManifestProvider.get()
102+
val manifestFile = (processManifestTask as ProcessMultiApkApplicationManifest).mainMergedManifest.get().asFile
103+
val variantName = manifestFile.parentFile.name
104+
val outputDir = File(project.buildDir, "generated/source/pluginManifest/$variantName")
105+
106+
// 添加生成PluginManifest.java任务
107+
val task = project.tasks.register("generate${variantName.capitalize()}PluginManifest") {
108+
it.dependsOn(processManifestTask)
109+
it.inputs.file(manifestFile)
110+
it.outputs.dir(outputDir).withPropertyName("outputDir")
111+
val packageForR = (project.tasks.getByName("processPluginDebugResources").property("namespace") as Property<String>).get()
112+
it.doLast {
113+
generatePluginManifest(manifestFile,
114+
outputDir,
115+
"com.tencent.shadow.core.manifest_parser",
116+
packageForR)
117+
}
118+
}
119+
project.tasks.getByName("compile${variantName.capitalize()}JavaWithJavac").dependsOn(task)
120+
121+
// 把PluginManifest.java添加为源码
122+
appExtension.sourceSets.getByName(variantName).java {
123+
srcDir(outputDir)
124+
}
125+
}
126+
}
127+
86128
private fun addFlavorForTransform(baseExtension: BaseExtension) {
87129
baseExtension.flavorDimensionList.add(ShadowTransform.DimensionName)
88130
try {

projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageMultiPluginTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class PackageMultiPluginTest {
4747
.withProjectDir(ROOT_PROJECT_DIR)
4848
.withPluginClasspath()
4949
.withArguments(listOf(
50+
"-xgeneratePluginDebugPluginManifest",
5051
"-Pdisable_shadow_transform=true",
5152
":plugin1:PackageMultiPlugin"
5253
))

projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageOnlyPluginTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class PackageOnlyPluginTest {
4646
.withProjectDir(PLUGIN1_PROJECT_DIR)
4747
.withPluginClasspath()
4848
.withArguments(listOf(
49+
"-xgeneratePluginDebugPluginManifest",
4950
"-Pdisable_shadow_transform=true",
5051
":plugin1:packageOnlyApkPlugin"
5152
))

projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackagePluginTaskTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class PackagePluginTaskTest {
4848
.withProjectDir(ROOT_PROJECT_DIR)
4949
.withPluginClasspath()
5050
.withArguments(listOf(
51+
"-xgeneratePluginDebugPluginManifest",
5152
"-Pdisable_shadow_transform=true",
5253
":plugin1:packageDebugPlugin"
5354
))

projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoader.kt

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.tencent.shadow.core.loader.classloaders
2020

2121
import android.os.Build
22+
import com.tencent.shadow.core.runtime.PluginManifest
2223
import dalvik.system.BaseDexClassLoader
2324
import org.jetbrains.annotations.TestOnly
2425
import java.io.File
@@ -111,6 +112,11 @@ class PluginClassLoader(
111112
return clazz
112113
}
113114

115+
internal fun loadPluginManifest(): PluginManifest {
116+
val clazz = findClass("com.tencent.shadow.core.manifest_parser.PluginManifest")
117+
return PluginManifest::class.java.cast(clazz.newInstance())
118+
}
119+
114120
}
115121

116122
private fun String.subStringBeforeDot() = substringBeforeLast('.', "")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apply plugin: 'kotlin'
2+
3+
dependencies {
4+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
5+
implementation 'com.squareup:javapoet:1.11.1'
6+
implementation project(':runtime')
7+
testImplementation 'junit:junit:4.12'
8+
testImplementation 'commons-io:commons-io:2.9.0'
9+
testImplementation 'com.tencent.shadow.coding:java-build-config'
10+
}
11+
12+
compileKotlin {
13+
kotlinOptions {
14+
jvmTarget = "1.6"
15+
}
16+
}
17+
18+
compileTestKotlin {
19+
kotlinOptions {
20+
jvmTarget = "1.6"
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.tencent.shadow.core.manifest_parser
2+
3+
sealed class AndroidManifestKeys {
4+
companion object {
5+
const val appComponentFactory = "android:appComponentFactory"
6+
const val `package` = "package"
7+
const val name = "android:name"
8+
const val theme = "android:theme"
9+
const val configChanges = "android:configChanges"
10+
const val windowSoftInputMode = "android:windowSoftInputMode"
11+
const val authorities = "android:authorities"
12+
const val `intent-filter` = "intent-filter"
13+
const val action = "action"
14+
const val manifest = "manifest"
15+
const val application = "application"
16+
const val activity = "activity"
17+
const val service = "service"
18+
const val provider = "provider"
19+
const val receiver = "receiver"
20+
}
21+
}
22+
typealias ComponentMapKey = String
23+
typealias ComponentMapValue = Any
24+
typealias ComponentMap = Map<ComponentMapKey, ComponentMapValue>
25+
typealias MutableComponentMap = MutableMap<ComponentMapKey, ComponentMapValue>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package com.tencent.shadow.core.manifest_parser
2+
3+
import org.w3c.dom.Document
4+
import org.w3c.dom.Element
5+
import org.w3c.dom.Node
6+
import java.io.File
7+
import javax.xml.parsers.DocumentBuilderFactory
8+
9+
typealias ManifestMap = Map<String, Any>
10+
11+
/**
12+
* 读取xml格式的Manifest到内存Map中
13+
*/
14+
class AndroidManifestReader {
15+
/**
16+
* 读取入口方法
17+
*
18+
* @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件,
19+
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
20+
*/
21+
fun read(xmlFile: File): ManifestMap {
22+
val manifest = readXml(xmlFile).documentElement
23+
val application = readApplication(manifest)
24+
val globalAttributes = readGlobalAttributes(manifest, application)
25+
val components = readComponents(application)
26+
return globalAttributes.plus(components)
27+
}
28+
29+
private fun readXml(xmlFile: File): Document {
30+
try {
31+
val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance()
32+
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
33+
return documentBuilder.parse(xmlFile)!!
34+
} catch (e: Exception) {
35+
throw RuntimeException("xml应该是AGP生成的合法文件,所以不兼容任何xml读取错误", e)
36+
}
37+
}
38+
39+
private fun readApplication(manifest: Element): Element? {
40+
val elements = manifest.getElementsByTagName(AndroidManifestKeys.application)
41+
return if (elements.length == 1) {
42+
val node = elements.item(0)
43+
assert(node.nodeType == Node.ELEMENT_NODE)
44+
elements.item(0) as Element
45+
} else {
46+
null
47+
}
48+
}
49+
50+
/**
51+
* 读取Manifest中那些唯一的属性
52+
*/
53+
private fun readGlobalAttributes(manifest: Element, application: Element?): Map<String, Any> {
54+
val globalAttributes = mutableMapOf<String, Any>()
55+
56+
fun manifestAttribute(name: String) {
57+
globalAttributes[name] = manifest.getAttribute(name)
58+
}
59+
60+
fun applicationAttribute(name: String) {
61+
if (application != null) {
62+
val attribute = application.getAttribute(name)
63+
if (attribute.isNotEmpty()) {
64+
globalAttributes[name] = attribute
65+
}
66+
}
67+
}
68+
69+
manifestAttribute(AndroidManifestKeys.`package`)
70+
listOf(
71+
AndroidManifestKeys.name,
72+
AndroidManifestKeys.theme,
73+
AndroidManifestKeys.appComponentFactory,
74+
).forEach(::applicationAttribute)
75+
return globalAttributes
76+
}
77+
78+
private fun readComponents(application: Element?) =
79+
listOf(
80+
AndroidManifestKeys.activity to ::parseActivity,
81+
AndroidManifestKeys.service to ::parseService,
82+
AndroidManifestKeys.receiver to ::parseReceiver,
83+
AndroidManifestKeys.provider to ::parseProvider,
84+
).map { (componentKey, parseMethod) ->
85+
val componentArray = parseComponents(application, componentKey, parseMethod)
86+
componentKey to componentArray
87+
}
88+
89+
90+
private fun parseComponents(
91+
application: Element?,
92+
componentKey: String,
93+
parseFunction: (Element) -> ComponentMap
94+
): Array<ComponentMap> {
95+
if (application == null) {
96+
return emptyArray()
97+
}
98+
val nodeList = application.getElementsByTagName(componentKey)
99+
val length = nodeList.length
100+
val collectionList = mutableListOf<ComponentMap>()
101+
for (i in 0 until length) {
102+
val node = nodeList.item(i)
103+
assert(node.nodeType == Node.ELEMENT_NODE)
104+
val map = parseFunction(node as Element)
105+
collectionList.add(map)
106+
}
107+
return collectionList.toTypedArray()
108+
}
109+
110+
private fun parseActivity(element: Element): ComponentMap {
111+
val activityMap = parseComponent(element).toMutableMap()
112+
113+
listOf(
114+
AndroidManifestKeys.theme,
115+
AndroidManifestKeys.configChanges,
116+
AndroidManifestKeys.windowSoftInputMode,
117+
).forEach { attributeKey ->
118+
activityMap.putAttributeIfNotNull(element, attributeKey)
119+
}
120+
return activityMap
121+
}
122+
123+
private fun parseService(element: Element): ComponentMap {
124+
return parseComponent(element)
125+
}
126+
127+
private fun parseReceiver(element: Element): ComponentMap {
128+
val receiverMap = parseComponent(element).toMutableMap()
129+
130+
val receiverActions = parseReceiverActions(element)
131+
if (receiverActions.isNotEmpty()) {
132+
receiverMap[AndroidManifestKeys.action] = receiverActions
133+
}
134+
135+
return receiverMap
136+
}
137+
138+
private fun parseReceiverActions(receiverElement: Element): List<String> {
139+
val intentFilters = receiverElement.getElementsByTagName(AndroidManifestKeys.`intent-filter`)
140+
val collectionList = mutableListOf<String>()
141+
for (i in 0 until intentFilters.length) {
142+
val intentFilter = intentFilters.item(i)
143+
assert(intentFilter.nodeType == Node.ELEMENT_NODE)
144+
val actions = (intentFilter as Element).getElementsByTagName(AndroidManifestKeys.action)
145+
for (j in 0 until actions.length) {
146+
val action = actions.item(j)
147+
assert(action.nodeType == Node.ELEMENT_NODE)
148+
val actionName = (action as Element).getAttribute(AndroidManifestKeys.name)
149+
collectionList.add(actionName)
150+
}
151+
}
152+
return collectionList
153+
}
154+
155+
private fun parseProvider(element: Element): ComponentMap {
156+
val providerMap = parseComponent(element).toMutableMap()
157+
158+
providerMap.putAttributeIfNotNull(element, AndroidManifestKeys.authorities)
159+
160+
return providerMap
161+
}
162+
163+
private fun parseComponent(element: Element): ComponentMap {
164+
val componentName = element.getAttribute(AndroidManifestKeys.name)
165+
return mapOf(
166+
AndroidManifestKeys.name to componentName
167+
)
168+
}
169+
170+
private fun MutableComponentMap.putAttributeIfNotNull(
171+
componentElement: Element,
172+
attributeKey: String) {
173+
if (componentElement.hasAttribute(attributeKey)) {
174+
this[attributeKey] = componentElement.getAttribute(attributeKey)
175+
}
176+
}
177+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.tencent.shadow.core.manifest_parser
2+
3+
import java.io.File
4+
5+
/**
6+
* manifest-parser的入口方法
7+
*
8+
* @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件,
9+
* 一般位于apk工程的build/intermediates/merged_manifest目录中。
10+
* @param outputDir 生成文件的输出目录
11+
* @param packageName 生成类的包名
12+
* @param packageForR 生成对R.java引用时需要的R文件的包名
13+
*/
14+
fun generatePluginManifest(xmlFile: File, outputDir: File, packageName: String, packageForR: String) {
15+
val androidManifest = AndroidManifestReader().read(xmlFile)
16+
val generator = PluginManifestGenerator(packageForR)
17+
generator.generate(androidManifest, outputDir, packageName)
18+
}

0 commit comments

Comments
 (0)