main.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.findLayoutDirs
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.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,
isComparison = true
)
}
@Suppress("RegExpUnnecessaryNonCapturingGroup")
private val resDefNameRegex by lazy {
val defTypes = ResDefType.values().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()) {
val methodNameMapping = mapping.methodNameMapping
androidProjects.forEach { project ->
project.files(project.javaDirs(variantName)).asFileTree.files.forEach asFileTree@{ file ->
// 白名单
if (mapping.inClassWhiteList(project, file)) {
return@asFileTree
}
replaceText(file, mappingInfoMap)
}
if (methodNameMapping.isNotEmpty()) {
project.files(project.findLayoutDirs(variantName)).asFileTree.files.forEach asFileTree@{ file ->
var fileText = file.readText()
methodNameMapping.forEach { (oldName, newName) ->
var oldValue = """
android:onClick="$oldName"
""".trimIndent()
var newValue = """
android:onClick="$newName"
""".trimIndent()
fileText = fileText.replaceWords(oldValue, newValue)
oldValue = """
.${oldName}(
""".trimIndent()
newValue = """
.${newName}(
""".trimIndent()
fileText = fileText.replaceWords(oldValue, newValue)
}
file.writeText(fileText)
}
}
}
}
// 3、混淆映射写出到文件
mapping.writeMappingToFile(mappingFile)
}
private fun replaceText(
file: File,
mappingInfoList: Map
) {
val builder = StringBuilder()
file.forEachLine { line ->
line.trim().run {
if (startsWith("package")) {
builder.append(line)
} else {
var newLine = line
mappingInfoList.forEach { (oldName, newName) ->
newLine = newLine.replaceWordsByPattern(oldName, newName)
if (newLine != line) {
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)
}
builder.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
}
}