Modify change module package name generated way and fix package name not found bug

This commit is contained in:
2022-08-18 03:32:29 +08:00
parent 6d59ff9369
commit 99c64ec96d
4 changed files with 182 additions and 113 deletions

View File

@@ -27,13 +27,13 @@ annotation class InjectYukiHookWithXposed(
!> `@InjectYukiHookWithXposed` 注解的 `Class` 必须实现 `IYukiHookXposedInit` 接口。
!> 在你当前项目中的所有 `Class` 标记中**只能存在一次**,若存在多个声明自动处理程序<u>会在编译时抛出异常</u>,你可以自定义其相关参数。
!> 在你当前项目中的所有 `Class` 标记中**只能存在一次**,若存在多个声明自动处理程序<u>**会在编译时抛出异常**</u>,你可以自定义其相关参数。
#### sourcePath 参数
`sourcePath` 参数决定了自动处理程序自动查找并匹配你当前项目路径的重要标识,此参数的内容为相对路径匹配,默认参数为 `src/main`
!> 如果你的项目不在 `...app/src/main...` 或你手动使用 `sourceSets` 设置了项目路径,你就需要手动设置 `sourcePath` 参数,否则自动处理程序将无法识别你的项目路径并<u>会在编译时抛出异常</u>
!> 如果你的项目不在 `../src/main..` 或你手动使用 `sourceSets` 设置了项目路径,你就需要手动设置 `sourcePath` 参数,否则自动处理程序将无法识别你的项目路径并<u>**会在编译时抛出异常**</u>
> 示例如下
@@ -45,17 +45,38 @@ annotation class InjectYukiHookWithXposed(
#### modulePackageName 参数
`modulePackageName` 是你当前项目的包名,也就是你的模块包名,默认留空自动处理程序将自动根据你注解的 `Class` 入口类的路径进行生成。
`modulePackageName` 是你当前项目的 `applicationId`,也就是你的模块包名 (最终生成的应用包名),留空或不填时自动处理程序将对当前项目文件进行分析并生成。
!> 若你想使用包名自动生成,你的 Hook 入口类 `HookEntryClass` 就要遵守包名的命名规范,格式为 `包名.hook.HookEntryClass``包名.hook.子包名.HookEntryClass`
!> 若你想使用模块包名自动生成,你需要确保你的项目命名空间在 `AndroidManifest.xml``build.gradle``build.gradle.kts` 中定义
示例模块包名 `com.example.demo`
示例命名空间 `com.example.demo`,以下定义方式任选其一。
示例 1 `com.example.demo.hook.MainHook`
以下定义方式仅供参考,通常情况下**只要你的项目能够正常生成 `BuildConfig.java` 文件,就不需要做额外操作**。
示例 2 `com.example.demo.hook.custom.CustomClass`
- `AndroidManifest.xml` 示例
若你不想使用此格式定义入口类的包名,例如你的包名动态的,类似使用 `productFlavors` 进行多渠道打包,你可以直接设置 `modulePackageName` 的参数。
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demo">
```
- `build.gradle` 示例
```groovy
android {
namespace 'com.example.demo'
}
```
- `build.gradle.kts` 示例
```kotlin
android {
namespace = "com.example.demo"
}
```
若你的模块包名是非常规手段进行自动生成的,或你认为有必要手动定义模块包名,那么你可以直接设置 `modulePackageName` 的参数。
> 示例如下
@@ -63,15 +84,7 @@ annotation class InjectYukiHookWithXposed(
@InjectYukiHookWithXposed(modulePackageName = "com.example.demo")
```
你也可以直接设置为你的 `BuildConfig.APPLICATION_ID`
> 示例如下
```kotlin
@InjectYukiHookWithXposed(modulePackageName = BuildConfig.APPLICATION_ID)
```
!> 只要你自定义了 `modulePackageName` 的参数,你就会在编译时收到警告。
只要你自定义了 `modulePackageName` 的参数,你就会在编译时收到警告
> 示例如下
@@ -79,6 +92,8 @@ annotation class InjectYukiHookWithXposed(
You set the customize module package name to "com.example.demo", please check for yourself if it is correct
```
!> 手动定义的模块包名除了格式之外,自动处理程序将不会再检查模块包名是否正确,需要你自行确认其有效性。
#### entryClassName 参数
`entryClassName` 决定了自动处理程序如何生成 `xposed_init` 中的入口类名,默认会使用你的入口类包名插入 `_YukiHookXposedInit` 后缀进行生成。

View File

@@ -35,9 +35,12 @@ import com.google.devtools.ksp.symbol.FileLocation
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.highcapable.yukihookapi_ksp_xposed.sources.CodeSourceFileTemplate
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.File
import java.util.*
import java.util.regex.Pattern
import javax.xml.parsers.DocumentBuilderFactory
/**
* 这是 [YukiHookAPI] 的自动生成处理类 - 核心基于 KSP
@@ -49,16 +52,19 @@ import java.util.regex.Pattern
@AutoService(SymbolProcessorProvider::class)
class YukiHookXposedProcessor : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment) = object : SymbolProcessor {
private companion object {
/** 自动处理程序的 TAG */
private val TAG = "YukiHookAPI"
private const val TAG = "YukiHookAPI"
/** 查找的注解名称 */
private val annotationName = "com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed"
private const val annotationName = "com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed"
/** 插入 Xposed 尾部的名称 */
private val xposedClassShortName = "_YukiHookXposedInit"
private const val xposedClassShortName = "_YukiHookXposedInit"
}
override fun create(environment: SymbolProcessorEnvironment) = object : SymbolProcessor {
/**
* 创建一个环境方法体方便调用
@@ -86,8 +92,14 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
*/
private fun SymbolProcessorEnvironment.warn(msg: String) = logger.warn(message = "[$TAG] $msg")
/**
* 移除字符串中的空格与换行符并将双引号替换为单引号
* @return [String]
*/
private fun String.removeSpecialChars() = replace("\\s*|\t|\r|\n".toRegex(), replacement = "").replace(oldValue = "\"", newValue = "'")
override fun process(resolver: Resolver) = emptyList<KSAnnotated>().let {
injectProcess(resolver)
startProcess(resolver)
it
}
@@ -95,35 +107,36 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
* 开始作业入口
* @param resolver [Resolver]
*/
private fun injectProcess(resolver: Resolver) = environment {
private fun startProcess(resolver: Resolver) = environment {
var isInjectOnce = true
resolver.getSymbolsWithAnnotation(annotationName).apply {
/**
* 检索需要注入的类
* @param sourcePath 指定的 source 路径
* @param modulePackageName 模块包名
* @param custMPackageName 自定义模块包名
* @param xInitClassName xposed_init 入口类名
* @param isUsingResourcesHook 是否启用 Resources Hook
*/
fun fetchKSClassDeclaration(
sourcePath: String,
modulePackageName: String,
custMPackageName: String,
xInitClassName: String,
isUsingResourcesHook: Boolean
) {
asSequence().filterIsInstance<KSClassDeclaration>().forEach {
if (isInjectOnce) when {
it.superTypes.any { type -> type.element.toString() == "IYukiHookXposedInit" } -> {
val xcName = xInitClassName.ifBlank { "${it.simpleName.asString()}$xposedClassShortName" }
val xInitPatchName = xInitClassName.ifBlank { "${it.simpleName.asString()}$xposedClassShortName" }
if (xInitClassName == it.simpleName.asString()) problem(msg = "Duplicate entryClassName \"$xInitClassName\"")
injectAssets(
generateAssetsFile(
codePath = (it.location as? FileLocation?)?.filePath ?: "",
sourcePath = sourcePath,
packageName = it.packageName.asString(),
custMPackageName = custMPackageName,
entryClassName = it.simpleName.asString(),
xInitClassName = xcName
xInitClassName = xInitPatchName,
isUsingResourcesHook = isUsingResourcesHook
)
injectClass(it.packageName.asString(), modulePackageName, it.simpleName.asString(), xcName, isUsingResourcesHook)
}
it.superTypes.any { type -> type.element.toString() == "YukiHookXposedInitProxy" } ->
problem(msg = "\"YukiHookXposedInitProxy\" was deprecated, please replace to \"IYukiHookXposedInit\"")
@@ -135,32 +148,32 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
}
}
forEach {
it.annotations.forEach { e ->
it.annotations.forEach { annotation ->
var sourcePath = "" // 项目相对路径
var modulePackageName = "" // 模块包名
var custMPackageName = "" // 自定义模块包名
var entryClassName = "" // xposed_init 入口类名
var isUsingResourcesHook = false // 是否启用 Resources Hook
e.arguments.forEach { pease ->
if (pease.name?.asString() == "sourcePath")
sourcePath = pease.value.toString().trim()
if (pease.name?.asString() == "modulePackageName")
modulePackageName = pease.value.toString().trim()
if (pease.name?.asString() == "entryClassName")
entryClassName = pease.value.toString().trim()
if (pease.name?.asString() == "isUsingResourcesHook")
isUsingResourcesHook = pease.value as Boolean
annotation.arguments.forEach { args ->
if (args.name?.asString() == "sourcePath")
sourcePath = args.value.toString().trim()
if (args.name?.asString() == "modulePackageName")
custMPackageName = args.value.toString().trim()
if (args.name?.asString() == "entryClassName")
entryClassName = args.value.toString().trim()
if (args.name?.asString() == "isUsingResourcesHook")
isUsingResourcesHook = args.value as? Boolean ?: true
}
if ((modulePackageName.startsWith(".") ||
modulePackageName.endsWith(".") ||
modulePackageName.contains(".").not() ||
modulePackageName.contains("..")) &&
modulePackageName.isNotEmpty()
) problem(msg = "Invalid modulePackageName \"$modulePackageName\"")
if ((custMPackageName.startsWith(".") ||
custMPackageName.endsWith(".") ||
custMPackageName.contains(".").not() ||
custMPackageName.contains("..")) &&
custMPackageName.isNotEmpty()
) problem(msg = "Invalid modulePackageName \"$custMPackageName\"")
if ((Pattern.compile("[*,.:~`'\"|/\\\\?!^()\\[\\]{}%@#$&\\-+=<>]").matcher(entryClassName).find() ||
true.let { for (i in 0..9) if (entryClassName.startsWith(i.toString())) return@let true;false })
&& entryClassName.isNotEmpty()
) problem(msg = "Invalid entryClassName \"$entryClassName\"")
else fetchKSClassDeclaration(sourcePath, modulePackageName, entryClassName, isUsingResourcesHook)
else fetchKSClassDeclaration(sourcePath, custMPackageName, entryClassName, isUsingResourcesHook)
}
}
}
@@ -171,67 +184,78 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
* @param codePath 注解类的完整代码文件路径
* @param sourcePath 指定的 source 路径
* @param packageName 包名
* @param entryClassName 入口类
* @param xInitClassName xposed_init 入口类名
*/
private fun injectAssets(codePath: String, sourcePath: String, packageName: String, entryClassName: String, xInitClassName: String) =
environment {
if (codePath.isBlank()) problem(msg = "Project CodePath not available")
if (sourcePath.isBlank()) problem(msg = "Project SourcePath not available")
/**
* Gradle 在这里自动处理了 Windows 和 Unix 下的反斜杠路径问题
* 为了防止万一还是做了一下反斜杠处理防止旧版本不支持此用法
*/
val separator = when {
codePath.contains("\\") -> "\\"
codePath.contains("/") -> "/"
else -> error("Unix File Separator unknown")
}
val projectPath = when {
codePath.contains("\\") -> sourcePath.replace("/", "\\")
codePath.contains("/") -> sourcePath.replace("\\", "/")
else -> error("Unix File Separator unknown")
}.let {
if (codePath.contains(it))
codePath.split(it)[0] + it
else problem(msg = "Project Source Path \"$it\" not matched")
}
File("$projectPath${separator}assets").also { assFile ->
if (File("$projectPath${separator}AndroidManifest.xml").exists()) {
if (assFile.exists().not() || assFile.isDirectory.not()) {
assFile.delete()
assFile.mkdirs()
}
File("${assFile.absolutePath}${separator}xposed_init")
.writeText(text = "$packageName.$xInitClassName")
File("${assFile.absolutePath}${separator}yukihookapi_init")
.writeText(text = "$packageName.$entryClassName")
} else problem(msg = "Project Source Path \"$sourcePath\" verify failed! Is this an Android Project?")
}
}
/**
* 注入并生成指定类
* @param packageName 包名
* @param modulePackageName 模块包名
* @param custMPackageName 自定义模块包
* @param entryClassName 入口类名
* @param xInitClassName xposed_init 入口类名
* @param isUsingResourcesHook 是否启用 Resources Hook
*/
private fun injectClass(
private fun generateAssetsFile(
codePath: String,
sourcePath: String,
packageName: String,
custMPackageName: String,
entryClassName: String,
xInitClassName: String,
isUsingResourcesHook: Boolean
) = environment {
if (codePath.isBlank()) problem(msg = "Project CodePath not available")
if (sourcePath.isBlank()) problem(msg = "Project SourcePath not available")
/**
* Gradle 在这里自动处理了 Windows 和 Unix 下的反斜杠路径问题
*
* 为了防止万一还是做了一下反斜杠处理防止旧版本不支持此用法
*/
val separator = when {
codePath.contains("\\") -> "\\"
codePath.contains("/") -> "/"
else -> error("Unix File Separator unknown")
}
var rootPath = ""
val projectPath = when {
codePath.contains("\\") -> sourcePath.replace("/", "\\")
codePath.contains("/") -> sourcePath.replace("\\", "/")
else -> error("Unix File Separator unknown")
}.let {
if (codePath.contains(it))
codePath.split(it)[0].apply { rootPath = this } + it
else problem(msg = "Project Source Path \"$it\" not matched")
}
val gradleFile = File("$rootPath${separator}build.gradle")
val gradleKtsFile = File("$rootPath${separator}build.gradle.kts")
val assetsFile = File("$projectPath${separator}assets")
val manifestFile = File("$projectPath${separator}AndroidManifest.xml")
if (manifestFile.exists()) {
if (assetsFile.exists().not() || assetsFile.isDirectory.not()) assetsFile.apply { delete(); mkdirs() }
val modulePackageName = parseModulePackageName(manifestFile, gradleFile, gradleKtsFile)
if (modulePackageName.isBlank() && custMPackageName.isBlank())
problem(msg = "Cannot identify your Module App's package name, tried AndroidManifest.xml, build.gradle and build.gradle.kts")
File("${assetsFile.absolutePath}${separator}xposed_init")
.writeText(text = "$packageName.$xInitClassName")
File("${assetsFile.absolutePath}${separator}yukihookapi_init")
.writeText(text = "$packageName.$entryClassName")
generateClassFile(packageName, modulePackageName, custMPackageName, entryClassName, xInitClassName, isUsingResourcesHook)
} else problem(msg = "Project Source Path \"$sourcePath\" verify failed! Is this an Android Project?")
}
/**
* 自动生成指定类文件
* @param packageName 包名
* @param modulePackageName 模块包名
* @param customMPackageName 自定义模块包名
* @param entryClassName 入口类名
* @param xInitClassName xposed_init 入口类名
* @param isUsingResourcesHook 是否启用 Resources Hook
*/
private fun generateClassFile(
packageName: String,
modulePackageName: String,
customMPackageName: String,
entryClassName: String,
xInitClassName: String,
isUsingResourcesHook: Boolean
) = environment(ignoredError = true) {
if (modulePackageName.isNotBlank())
warn(msg = "You set the customize module package name to \"$modulePackageName\", please check for yourself if it is correct")
val fModulePackageName = modulePackageName.ifBlank {
if (packageName.contains(other = ".hook.") || packageName.endsWith(suffix = ".hook"))
packageName.split(".hook")[0]
else error("Cannot identify your Module App's package name, please manually configure the package name")
}
if (customMPackageName.isNotBlank())
warn(msg = "You set the customize module package name to \"$customMPackageName\", please check for yourself if it is correct")
val mdAppInjectPackageName = "com.highcapable.yukihookapi.hook.xposed.application.inject"
val ykBridgeInjectPackageName = "com.highcapable.yukihookapi.hook.xposed.bridge.inject"
/** 插入 ModuleApplication_Injector 代码 */
@@ -270,10 +294,45 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
packageName = packageName,
fileName = "${entryClassName}_Impl"
).apply {
write(CodeSourceFileTemplate.getXposedInitImplFileByteArray(packageName, fModulePackageName, entryClassName))
write(CodeSourceFileTemplate.getXposedInitImplFileByteArray(packageName, modulePackageName, customMPackageName, entryClassName))
flush()
close()
}
}
/**
* 解析模块包名
* @param manifestFile AndroidManifest.xml 文件
* @param gradleFile build.gradle 文件
* @param gradleKtsFile build.gradle.kts 文件
* @return [String] 模块包名
*/
private fun parseModulePackageName(manifestFile: File, gradleFile: File, gradleKtsFile: File) = when {
gradleFile.exists() -> runCatching {
gradleFile.readText()
.removeSpecialChars()
.split("namespace'")[1]
.split("'")[0]
}.getOrNull()
gradleKtsFile.exists() -> runCatching {
gradleKtsFile.readText()
.removeSpecialChars()
.replace(oldValue = "varnamespace", newValue = "")
.replace(oldValue = "valnamespace", newValue = "")
.split("namespace='")[1]
.split("'")[0]
}.getOrNull()
else -> null
} ?: runCatching {
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(manifestFile).let { document ->
document.getElementsByTagName("manifest").let { nodeList ->
nodeList.item(0).let { node ->
if (node.nodeType == Node.ELEMENT_NODE)
(node as? Element?)?.getAttribute("package") ?: ""
else ""
}
}
}
}.getOrNull() ?: ""
}
}

View File

@@ -144,10 +144,11 @@ object CodeSourceFileTemplate {
* 获得 xposed_init_Impl 注入文件
* @param packageName 包名
* @param modulePackageName 模块包名
* @param customMPackageName 自定义模块包名
* @param entryClassName 入口类名
* @return [ByteArray]
*/
fun getXposedInitImplFileByteArray(packageName: String, modulePackageName: String, entryClassName: String) =
fun getXposedInitImplFileByteArray(packageName: String, modulePackageName: String, customMPackageName: String, entryClassName: String) =
("@file:Suppress(\"ClassName\")\n" +
"\n" +
"package $packageName\n" +
@@ -158,12 +159,14 @@ object CodeSourceFileTemplate {
"import de.robv.android.xposed.IXposedHookZygoteInit\n" +
"import de.robv.android.xposed.callbacks.XC_InitPackageResources\n" +
"import de.robv.android.xposed.callbacks.XC_LoadPackage\n" +
(if (customMPackageName.isBlank()) "import $modulePackageName.BuildConfig\n" else "") +
"\n" +
getCommentContent(entryClassName, currrentClassTag = "Xposed Init Impl") +
"@YukiGenerateApi\n" +
"object ${entryClassName}_Impl {\n" +
"\n" +
" private const val modulePackageName = \"$modulePackageName\"\n" +
" private const val modulePackageName = " +
(if (customMPackageName.isNotBlank()) "\"$customMPackageName\"" else "BuildConfig.APPLICATION_ID") + "\n" +
"\n" +
" private val hookEntry = $entryClassName()\n" +
"\n" +

View File

@@ -34,21 +34,13 @@ import de.robv.android.xposed.IXposedHookInitPackageResources
/**
* 标识 [YukiHookAPI] 注入 Xposed 入口的类注解
*
* - 你的项目 source 目录默认为 "src/main/" 可在 [sourcePath] 中进行自定义 - 自动处理程序将只检查 ..app/[sourcePath]/java.. 中间部分
* - 你的项目 source 目录默认为 "src/main/" 可在 [sourcePath] 中进行自定义 - 自动处理程序将只检查 ../[sourcePath]/java.. 中间部分
*
* - 自动处理程序将自动在 ../[sourcePath]/assets/ 下建立 xposed_init 文件
*
* - 你的 Hook 入口类(HookEntryClass) 需要按照此格式创建 --> 你的模块 APP 包名/hook/...可允许子包名存在.../你的入口类
* 你的 xposed_init 入口将被自动生成为 --> 你的入口类完整包名/你的入口类名_YukiHookXposedInit 或自定义 [entryClassName]
*
* 例子com.example.module.hook.MainHook、com.example.module.hook.inject.MainInject、com.example.module.hook.custom.CustomClass
*
* 你的 xposed_init 入口将被自动生成为 --> 你的模块 APP 包名/hook/...可允许子包名存在.../你的入口类_YukiHookXposedInit 或自定义 [entryClassName]
*
* 例子com.example.module.hook.MainHook_YukiHookXposedInit
*
* - 你的模块包名将被这样识别:|com.example.module|.hook...
*
* - 若你不喜欢这样创建类 - 没问题 - 请在 [modulePackageName] 填写你的模块包名即可 - 默认会按照标准识别 - 失败编译会报错
* - 你可以在 [modulePackageName] 自定义你的模块包名 - 未定义的情况下会使用 AndroidManifest.xml 与 build.gradle/kts 进行分析 - 失败编译会报错
*
* - 为了防止模块包名无法正常被识别 - 自定义 [modulePackageName] 会在编译时产生警告
*
@@ -60,7 +52,7 @@ import de.robv.android.xposed.IXposedHookInitPackageResources
*
* 详情请参考 [InjectYukiHookWithXposed 注解](https://fankes.github.io/YukiHookAPI/#/config/xposed-using?id=injectyukihookwithxposed-%e6%b3%a8%e8%a7%a3)
* @param sourcePath 你的项目 source 相对路径 - 默认为 ..src/main..
* @param modulePackageName 模块包名 - 使用标准路径可不填会自动生成
* @param modulePackageName 模块包名 - 不填默认自动生成
* @param entryClassName 定义 [YukiHookAPI] 自动生成 Xposed 模块入口类的名称 - 不填默认使用 HookEntryClass_YukiHookXposedInit 进行生成
* @param isUsingResourcesHook 是否启用 Resources Hook (资源钩子) - 启用后将自动注入 [IXposedHookInitPackageResources] - 默认是
*/