build.gradle.kts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import org.gradle.api.DefaultTask
  2. import org.gradle.api.file.DirectoryProperty
  3. import org.gradle.api.provider.MapProperty
  4. import org.gradle.api.tasks.Input
  5. import org.gradle.api.tasks.InputDirectory
  6. import org.gradle.api.tasks.PathSensitive
  7. import org.gradle.api.tasks.PathSensitivity
  8. import org.gradle.api.tasks.TaskAction
  9. import org.gradle.work.DisableCachingByDefault
  10. @DisableCachingByDefault(because = "架构验证任务没有输出文件")
  11. abstract class VerifyConfigArchitectureTask : DefaultTask() {
  12. @get:InputDirectory
  13. @get:PathSensitive(PathSensitivity.RELATIVE)
  14. abstract val sourceRoot: DirectoryProperty
  15. @get:Input
  16. abstract val legacyPreferenceCallBaseline: MapProperty<String, Int>
  17. @TaskAction
  18. fun verify() {
  19. val sourceRootDir = sourceRoot.get().asFile
  20. val kotlinFiles = sourceRootDir.walkTopDown()
  21. .filter { it.isFile && it.extension == "kt" }
  22. .toList()
  23. val preferenceBaseline = legacyPreferenceCallBaseline.get()
  24. val violations = mutableListOf<String>()
  25. val forbiddenConfigImport = Regex(
  26. """^import io\.legado\.app\.(?:help\.config\.AppConfig|ui\.config\..*Config)$""",
  27. RegexOption.MULTILINE,
  28. )
  29. val preferenceCall = Regex("""\b(?:getPref|putPref)[A-Za-z0-9_]*\s*\(""")
  30. val readBookConfigWrite = Regex(
  31. """\bReadBookConfig\.[a-z_][A-Za-z0-9_]*(?:\.[a-z_][A-Za-z0-9_]*)?\s*="""
  32. )
  33. val readBookConfigMutationCall = Regex(
  34. """\bReadBookConfig\.durConfig\.set[A-Za-z0-9_]*\s*\("""
  35. )
  36. val injectedConfigFiles = setOf(
  37. "io/legado/app/help/config/AppConfig.kt",
  38. "io/legado/app/help/config/ReadBookConfig.kt",
  39. "io/legado/app/help/config/ThemePackageManager.kt",
  40. )
  41. kotlinFiles.forEach { file ->
  42. val text = file.readText()
  43. val relativePath = file.relativeTo(sourceRootDir).invariantSeparatorsPath
  44. val displayPath = "app/src/main/java/$relativePath"
  45. if ("prefDelegate" in text || "prefStateDelegate" in text ||
  46. "Snapshot.withMutableSnapshot" in text
  47. ) {
  48. violations += "$displayPath: 禁止 Snapshot 配置桥"
  49. }
  50. if ((relativePath.startsWith("io/legado/app/data/") ||
  51. relativePath.startsWith("io/legado/app/domain/")) &&
  52. forbiddenConfigImport.containsMatchIn(text)
  53. ) {
  54. violations += "$displayPath: data/domain 禁止导入全局 Config"
  55. }
  56. if (("@Composable" in text || "import androidx.compose" in text) &&
  57. forbiddenConfigImport.containsMatchIn(text)
  58. ) {
  59. violations += "$displayPath: Composable 禁止读取兼容 Config"
  60. }
  61. if (file.name.endsWith("Config.kt") &&
  62. ("mutableStateOf(" in text || "Snapshot.withMutableSnapshot" in text ||
  63. "import androidx.compose.runtime.State" in text ||
  64. "import androidx.compose.runtime.MutableState" in text)
  65. ) {
  66. violations += "$displayPath: 配置门面禁止持有 Compose State"
  67. }
  68. if (relativePath !=
  69. "io/legado/app/data/repository/ReadBookStyleConfigRepository.kt" &&
  70. (readBookConfigWrite.containsMatchIn(text) ||
  71. readBookConfigMutationCall.containsMatchIn(text))
  72. ) {
  73. violations += "$displayPath: ReadBookConfig 写入必须经过 ReadStyleGateway"
  74. }
  75. if (relativePath in injectedConfigFiles && "GlobalContext" in text) {
  76. violations += "$displayPath: 配置所有者必须显式注入依赖,禁止 GlobalContext"
  77. }
  78. val preferenceCalls = preferenceCall.findAll(text).count()
  79. val allowedCalls = preferenceBaseline[relativePath] ?: 0
  80. if (preferenceCalls > allowedCalls) {
  81. violations += "$displayPath: 新增了 ${preferenceCalls - allowedCalls} 个旧偏好调用"
  82. }
  83. }
  84. check(violations.isEmpty()) {
  85. violations.joinToString(prefix = "配置架构护栏失败:\n", separator = "\n")
  86. }
  87. }
  88. }
  89. buildscript {
  90. extra.apply {
  91. set("compile_sdk_version", 36)
  92. set("build_tool_version", "34.0.0")
  93. }
  94. }
  95. plugins {
  96. alias(libs.plugins.compose.compiler) apply false
  97. alias(libs.plugins.android.application) apply false
  98. alias(libs.plugins.android.library) apply false
  99. alias(libs.plugins.kotlin.parcelize) apply false
  100. alias(libs.plugins.kotlin.serialization) apply false
  101. alias(libs.plugins.ksp) apply false
  102. alias(libs.plugins.google.services) apply false
  103. alias(libs.plugins.room) apply false
  104. alias(libs.plugins.download) apply false
  105. alias(libs.plugins.android.test) apply false
  106. alias(libs.plugins.baselineprofile) apply false
  107. }
  108. tasks.register<Delete>("clean") {
  109. delete(rootProject.layout.buildDirectory)
  110. }
  111. val verifyConfigArchitecture = tasks.register<VerifyConfigArchitectureTask>(
  112. "verifyConfigArchitecture"
  113. ) {
  114. group = "verification"
  115. description = "禁止配置 Snapshot 桥、跨层全局 Config 直读和新增旧偏好调用"
  116. sourceRoot.set(layout.projectDirectory.dir("app/src/main/java"))
  117. legacyPreferenceCallBaseline.set(
  118. mapOf(
  119. "io/legado/app/App.kt" to 3,
  120. "io/legado/app/base/BaseActivity.kt" to 2,
  121. "io/legado/app/base/BaseService.kt" to 1,
  122. "io/legado/app/data/repository/CoverAlbumRepository.kt" to 4,
  123. "io/legado/app/data/repository/HighlightRuleRepository.kt" to 9,
  124. "io/legado/app/data/repository/HomeDashboardRepository.kt" to 3,
  125. "io/legado/app/data/repository/ReadRecordRepository.kt" to 1,
  126. "io/legado/app/data/repository/SettingsRepository.kt" to 7,
  127. "io/legado/app/help/config/LocalConfig.kt" to 3,
  128. "io/legado/app/help/config/ThemeConfigStore.kt" to 8,
  129. "io/legado/app/help/storage/Restore.kt" to 2,
  130. "io/legado/app/receiver/MediaButtonReceiver.kt" to 2,
  131. "io/legado/app/service/WebService.kt" to 2,
  132. "io/legado/app/ui/association/ImportReplaceRuleDialog.kt" to 1,
  133. "io/legado/app/ui/book/explore/ExploreShowViewModel.kt" to 2,
  134. "io/legado/app/ui/book/read/ReadBookViewModel.kt" to 2,
  135. "io/legado/app/ui/book/readRecord/ReadRecordViewModel.kt" to 1,
  136. "io/legado/app/ui/book/search/SearchViewModel.kt" to 3,
  137. "io/legado/app/ui/config/CheckSourceConfig.kt" to 1,
  138. "io/legado/app/ui/config/otherConfig/OtherConfigViewModel.kt" to 1,
  139. "io/legado/app/ui/replace/ReplaceRuleViewModel.kt" to 2,
  140. "io/legado/app/utils/ContextExtensions.kt" to 12,
  141. "io/legado/app/web/socket/BookSearchWebSocket.kt" to 2,
  142. )
  143. )
  144. }
  145. subprojects {
  146. tasks.configureEach {
  147. if (name.startsWith("assemble") || name.startsWith("compile")) {
  148. dependsOn(verifyConfigArchitecture)
  149. }
  150. }
  151. }