XuqmGroup-RNSDK/packages/update/android/xuqm-bundles.gradle
XuqmGroup 507ce3d2ee feat(update): release-set 事务制品与原生层职责拆分
- 插件包改为 .xuqm.zip 事务制品,manifest/bundle/资源/SHA-256 统一校验
- 原生层拆分为桥接编排、插件包暂存、底层存储三个单一职责类
- 状态查询改为纯读取,不再误触发安装;新增 NATIVE_BASELINE_INCOMPATIBLE 拒绝
- SemVer 解析拆分为独立原生类并补原生测试
- XWebView 收口唯一内核,错误态统一中文层与重试
- common 增加请求头/运行时契约收口与 http 测试
2026-07-20 19:31:43 +08:00

190 行
6.5 KiB
Groovy

import javax.inject.Inject
import groovy.json.JsonSlurper
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.gradle.work.DisableCachingByDefault
/**
* Generates every configured RN plugin bundle before Android packages assets.
*
* Apply once from the host application module:
* apply from: file("../../node_modules/@xuqm/rn-update/android/xuqm-bundles.gradle")
*/
@DisableCachingByDefault(because = "Metro maintains its own transform cache")
abstract class GenerateXuqmBundlesTask extends DefaultTask {
@Internal
abstract DirectoryProperty getProjectRootDirectory()
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getCliFile()
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getConfigFile()
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getPackageFile()
/**
* RN/原生源码与打包配置共同决定 bundle、资源和 native baseline。
* 显式建模后 Gradle 才能安全复用上次输出,禁止用永久 out-of-date 代替依赖声明。
*/
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
abstract ConfigurableFileCollection getSourceFiles()
@Input
abstract Property<String> getNodeExecutable()
@Input
abstract Property<String> getAppVersion()
@OutputDirectory
abstract DirectoryProperty getAssetsOutputDirectory()
@OutputDirectory
abstract DirectoryProperty getResourcesOutputDirectory()
@Inject
abstract ExecOperations getExecOperations()
@TaskAction
void generate() {
def embeddedBundles = assetsOutputDirectory.get().dir("rn-bundles").asFile
def resources = resourcesOutputDirectory.get().asFile
execOperations.exec {
workingDir(projectRootDirectory.get().asFile)
commandLine(
nodeExecutable.get(),
cliFile.get().asFile.absolutePath,
"embed",
"android",
"--output",
embeddedBundles.absolutePath,
"--app-version",
appVersion.get(),
)
}.assertNormalExitValue()
// Android 内嵌 Bundle 通过 AssetManager 读取,但 require() 图片必须同时进入
// aapt2 的 drawable 资源表。只把 drawable-* 放进 assets 会导致图片静默为空。
project.delete(resources)
embeddedBundles.listFiles()?.findAll {
it.isDirectory() && it.name.startsWith("drawable-")
}?.each { drawableDirectory ->
project.copy {
from(drawableDirectory)
into(new File(resources, drawableDirectory.name))
}
}
}
}
if (!plugins.hasPlugin("com.android.application")) {
throw new GradleException("xuqm-bundles.gradle must be applied to an Android application module")
}
def xuqmProjectRoot = rootProject.projectDir.parentFile
def xuqmHostPackage = new JsonSlurper().parse(new File(xuqmProjectRoot, "package.json"))
def xuqmLocalDependencyDirectories = (xuqmHostPackage.dependencies ?: [:])
.findAll { _, requested -> requested instanceof String && requested.startsWith("file:") }
.collect { _, requested -> new File(xuqmProjectRoot, requested.substring("file:".length())).canonicalFile }
def xuqmUseMetro = providers.gradleProperty("USE_METRO")
.orElse(providers.environmentVariable("USE_METRO"))
.map { it.toBoolean() }
.orElse(false)
def prepareXuqmBundles = tasks.register(
"prepareXuqmEmbeddedBundles",
GenerateXuqmBundlesTask,
) {
group = "xuqm"
description = "Builds and embeds all configured Xuqm RN plugin bundles."
projectRootDirectory.set(xuqmProjectRoot)
cliFile.set(new File(xuqmProjectRoot, "node_modules/@xuqm/rn-update/scripts/xuqm-rn.mjs"))
configFile.set(new File(xuqmProjectRoot, "xuqm.config.json"))
packageFile.set(new File(xuqmProjectRoot, "package.json"))
sourceFiles.from(project.fileTree(xuqmProjectRoot) {
include("src/**")
include("assets/**")
include("android/**")
include("index.*")
include("babel.config.*")
include("metro.config.*")
include("react-native.config.*")
include("tsconfig*.json")
include("pnpm-lock.yaml")
include("yarn.lock")
exclude("android/**/build/**")
exclude("android/**/.cxx/**")
exclude("android/**/.gradle/**")
exclude("android/app/src/**/generated/**")
})
sourceFiles.from(project.fileTree(new File(
xuqmProjectRoot,
"node_modules/@xuqm/rn-update/scripts",
)) {
include("*.mjs")
})
// registry 依赖由不可变版本号进入 baseline;file: 依赖在版本不变时也可能修改
// 原生源码,必须同时成为 Gradle 输入和 native baseline 内容。
xuqmLocalDependencyDirectories.each { dependencyDirectory ->
sourceFiles.from(project.fileTree(dependencyDirectory) {
include("android/**")
include("ios/**")
include("*.podspec")
include("package.json")
exclude("**/build/**")
exclude("**/.cxx/**")
exclude("**/.gradle/**")
exclude("**/generated/**")
})
}
nodeExecutable.set(System.getenv("NODE_BINARY") ?: "node")
appVersion.set(providers.provider {
def version = android.defaultConfig.versionName
if (version == null || version.toString().trim().isEmpty()) {
throw new GradleException("Android defaultConfig.versionName is required")
}
version.toString()
})
assetsOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/assets"))
resourcesOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/res"))
}
androidComponents.onVariants(androidComponents.selector().all()) { variant ->
if (variant.buildType == "release" || !xuqmUseMetro.get()) {
variant.sources.assets?.addGeneratedSourceDirectory(
prepareXuqmBundles,
{ task -> task.assetsOutputDirectory },
)
variant.sources.res?.addGeneratedSourceDirectory(
prepareXuqmBundles,
{ task -> task.resourcesOutputDirectory },
)
}
}
tasks.configureEach { task ->
if (task.name ==~ /^createBundle.*ReleaseJsAndAssets$/) {
// Xuqm hosts load startup/common/app/plugin bundles explicitly. A second
// monolithic index.android.bundle is both redundant and unsafe.
task.enabled = false
}
}