com.xml.guard.tasks.DeclarationChangeTask.kt Maven / Gradle / Ivy
package com.xml.guard.tasks
import com.xml.guard.data.ResDefType
import com.xml.guard.entensions.GuardExtension
import com.xml.guard.model.MappingParser
import com.xml.guard.utils.allDependencyAndroidProjects
import com.xml.guard.utils.findClassByLayoutXml
import com.xml.guard.utils.findLayoutDirs
import com.xml.guard.utils.findPackage
import com.xml.guard.utils.getClassName
import com.xml.guard.utils.isJava
import com.xml.guard.utils.isKt
import com.xml.guard.utils.isXml
import com.xml.guard.utils.javaDirs
import com.xml.guard.utils.replaceWords
import com.xml.guard.utils.replaceWordsByPattern
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import java.io.File
import javax.inject.Inject
open class DeclarationChangeTask @Inject constructor(
private val guardExtension: GuardExtension,
private val variantName: String,
) : DefaultTask() {
init {
group = "guard"
}
private val mappingFile by lazy { project.file(MappingParser.MAPPING_DECLARATION) }
private val mapping by lazy {
MappingParser.parseDeclaration(
project,
guardExtension,
mappingFile,
isSelfParse = true
)
}
@Suppress("RegExpUnnecessaryNonCapturingGroup")
private val resDefNameRegex by lazy {
val defTypes = ResDefType.entries.joinToString("|") { it.defType }
val pattern = "R\\.(?:${defTypes})\\.([a-zA-Z0-9_]+)"
Regex(pattern)
}
@TaskAction
fun execute() {
val androidProjects = allDependencyAndroidProjects(project)
// 1、字符串混淆
val mappingInfoMap = mapping.findAllDeclaration(androidProjects, variantName)
// 2、替换 Java/kotlin 文件里引用到的声明字符
if (mappingInfoMap.isNotEmpty()) {
androidProjects.forEach { project ->
val fileDirs = mutableListOf()
fileDirs.addAll(project.javaDirs(variantName))
fileDirs.addAll(project.findLayoutDirs(variantName))
val packageName = project.findPackage()
project.files(fileDirs).asFileTree.forEach asFileTree@{ file ->
when {
file.isJava || file.isKt -> replaceJavaText(project, file, mappingInfoMap)
file.isXml -> replaceLayoutText(file, packageName, mappingInfoMap)
}
}
}
}
// 3、混淆映射写出到文件
mapping.writeMappingToFile(mappingFile)
}
private fun replaceLayoutText(
layoutFile: File,
packageName: String,
mappingInfoList: Map
) {
var xmlText = layoutFile.readText()
val classInfos = findClassByLayoutXml(xmlText, packageName)
val classNames = classInfos.map { it.classPath.getClassName() }
mappingInfoList.forEach { (oldName, newName) ->
var oldValue = """
android:onClick="$oldName"
""".trimIndent()
var newValue = """
android:onClick="$newName"
""".trimIndent()
xmlText = xmlText.replaceWords(oldValue, newValue)
classNames.forEach { clsName ->
oldValue = """
${clsName}.${oldName}
""".trimIndent()
newValue = """
${clsName}.${newName}
""".trimIndent()
xmlText = xmlText.replaceWords(oldValue, newValue)
}
}
layoutFile.writeText(xmlText)
}
private fun replaceJavaText(
project: Project,
file: File,
mappingInfoList: Map
) {
// 白名单
if (mapping.inClassWhiteList(project, file)) {
return
}
val builder = StringBuilder()
file.forEachLine { line ->
if (line.trim().startsWith("package")) {
builder.append(line).append("\n")
return@forEachLine
}
var newLine = line
mappingInfoList.forEach { (oldName, newName) ->
newLine = newLine.replaceWordsByPattern(oldName, newName)
if (newLine == line) {
return@forEach
}
extractResStrings(line).forEach extractResStrings@{ (fullName, defName) ->
if (defName != oldName) return@extractResStrings
// 若资源相关名称被混淆,需还原;例如:R.id.ivIcon 被混淆为 R.id.xsgf
val oldValue = fullName.replace(oldName, newName)
newLine = newLine.replaceWordsByPattern(oldValue, fullName)
}
}
builder.append(newLine).append("\n")
}
if (builder.isNotEmpty()) {
builder.deleteCharAt(builder.length - 1)
}
file.writeText(builder.toString())
}
private fun extractResStrings(input: String): Map {
val map = mutableMapOf()
val matches = resDefNameRegex.findAll(input)
matches.forEach { match ->
val result = match.groupValues.getOrNull(1)
if (!result.isNullOrBlank()) {
map[match.value] = result
}
}
return map
}
}