XuqmGroup-AndroidSDK/build.gradle.kts

178 行
7.2 KiB
Plaintext

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.kotlin.serialization) apply false
}
group = "com.xuqm"
// Global fallback — individual modules override via their own SDK_<NAME>_VERSION property.
// Publish all at once: ./gradlew publish -PPUBLISH_VERSION=1.0.2
// Publish one module: ./gradlew :sdk-update:publish -PSDK_UPDATE_VERSION=2.0.0-SNAPSHOT
version = providers.gradleProperty("PUBLISH_VERSION").getOrElse("0.1.0-SNAPSHOT")
ext["nexusUrl"] = "https://nexus.xuqinmin.com/repository/android-hosted/"
ext["nexusUser"] = providers.gradleProperty("NEXUS_USER").getOrElse("")
ext["nexusPassword"] = providers.gradleProperty("NEXUS_PASSWORD").getOrElse("")
val verifyNoSensitiveLogging by tasks.registering {
group = "verification"
description = "阻止日志、标准输出和异常文案包含凭据或用户标识实际值。"
doLast {
val sensitiveIdentifiers = Regex(
"""(?i)\b(accessToken|token|userSig|pushToken|password|apiKey|signingKey|""" +
"""userId|groupId|targetId|messageId|requestId|toId|fromId|phone|email|regId)\b""",
)
val sensitiveInterpolation = Regex(
"""(?i)\$(?:\{[^}]*\b(accessToken|token|userSig|pushToken|password|apiKey|""" +
"""signingKey|userId|groupId|targetId|messageId|requestId|toId|fromId|""" +
"""phone|email|regId)\b[^}]*}|(accessToken|token|userSig|pushToken|password|""" +
"""apiKey|signingKey|userId|groupId|targetId|messageId|requestId|toId|""" +
"""fromId|phone|email|regId)\b)""",
)
val sinkStart = Regex(
"""(?:(?:android\.util\.)?Log\.[vdiew]|println|System\.out\.print(?:ln)?|""" +
"""[A-Za-z_][A-Za-z0-9_]*Exception|error)""" +
"""\s*\(""",
)
fun invocationAt(source: String, start: Int): String {
val open = source.indexOf('(', start)
if (open < 0) return source.substring(start)
var depth = 0
var quote: Char? = null
var escaped = false
for (index in open until source.length) {
val char = source[index]
if (quote != null) {
if (escaped) {
escaped = false
} else if (char == '\\') {
escaped = true
} else if (char == quote) {
quote = null
}
continue
}
if (char == '"' || char == '\'') {
quote = char
continue
}
if (char == '(') depth++
if (char == ')' && --depth == 0) return source.substring(start, index + 1)
}
return source.substring(start)
}
fun withoutStringLiterals(value: String): String {
val result = StringBuilder(value.length)
var quote: Char? = null
var escaped = false
value.forEach { char ->
if (quote != null) {
if (escaped) {
escaped = false
} else if (char == '\\') {
escaped = true
} else if (char == quote) {
quote = null
}
result.append(' ')
} else if (char == '"' || char == '\'') {
quote = char
result.append(' ')
} else {
result.append(char)
}
}
return result.toString()
}
val violations = mutableListOf<String>()
fileTree(rootDir) {
include("**/*.kt", "**/*.java", "**/*.kts", "**/*.gradle")
exclude("**/build/**", "**/.gradle/**")
}.files.sortedBy { it.path }.forEach { sourceFile ->
val source = sourceFile.readText(Charsets.UTF_8)
sinkStart.findAll(source).forEach { match ->
val invocation = invocationAt(source, match.range.first)
if (
sensitiveInterpolation.containsMatchIn(invocation) ||
sensitiveIdentifiers.containsMatchIn(withoutStringLiterals(invocation))
) {
val line = source.take(match.range.first).count { it == '\n' } + 1
violations += "${sourceFile.relativeTo(rootDir)}:$line"
}
}
}
check(violations.isEmpty()) {
"Sensitive value may reach a log, stdout, or exception message:\n" +
violations.joinToString("\n")
}
}
}
subprojects {
tasks.matching {
it.name == "testDebugUnitTest" || it.name == "lintRelease" || it.name == "check"
}.configureEach {
dependsOn(rootProject.tasks.named("verifyNoSensitiveLogging"))
}
}
val verifySdkManifestOwnership by tasks.registering {
group = "verification"
description = "验证 common 的文件共享 Provider 与扩展初始化组件归属。"
dependsOn(
":sdk-core:processDebugManifest",
":sdk-update:processDebugManifest",
":sdk-push:processDebugManifest",
":sdk-im:processDebugManifest",
":sdk-webview:processDebugManifest",
":sdk-bugcollect:processDebugManifest",
)
doLast {
fun mergedManifest(module: String): String =
file("$module/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml")
.readText()
val core = mergedManifest("sdk-core")
check("XuqmMergedProvider" !in core)
check("POST_NOTIFICATIONS" !in core)
check("com.xuqm.sdk.file.XuqmFileProvider" in core)
check("androidx.core.content.FileProvider" !in core)
val update = mergedManifest("sdk-update")
check("XuqmMergedProvider" in update)
check("androidx.core.content.FileProvider" !in update)
val push = mergedManifest("sdk-push")
check("XuqmMergedProvider" in push)
check("POST_NOTIFICATIONS" in push)
listOf("sdk-im", "sdk-webview", "sdk-bugcollect").forEach { module ->
check("XuqmMergedProvider" in mergedManifest(module))
}
}
}
val verifySampleManifestMerge by tasks.registering {
group = "verification"
description = "验证多个扩展合并后只存在一个初始化 Provider。"
dependsOn(":sample-app:processDebugMainManifest")
doLast {
val manifest = file(
"sample-app/build/intermediates/merged_manifest/debug/" +
"processDebugMainManifest/AndroidManifest.xml",
).readText()
check(Regex("com\\.xuqm\\.sdk\\.internal\\.XuqmMergedProvider")
.findAll(manifest).count() == 1)
check(Regex("com\\.xuqm\\.sdk\\.file\\.XuqmFileProvider")
.findAll(manifest).count() == 1)
check(Regex("com\\.xuqm\\.common_plugin_applied")
.findAll(manifest).count() == 1)
check("com.xuqm.bugcollect_build_enabled" in manifest)
check("com.xuqm.bugcollect_automatic_enabled" in manifest)
}
}