From 78721ee2f0d8c802091fc1348c5a4acb7f20770f Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Mon, 27 Jul 2026 11:23:14 +0800 Subject: [PATCH] fix(update): embed signed config in Metro debug --- docs/IMPLEMENTATION_HANDOFF.md | 14 +++++ packages/update/android/xuqm-bundles.gradle | 58 +++++++++++++++------ packages/update/scripts/xuqm-rn.test.mjs | 10 ++++ 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/docs/IMPLEMENTATION_HANDOFF.md b/docs/IMPLEMENTATION_HANDOFF.md index 5cd9fe7..cab16e5 100644 --- a/docs/IMPLEMENTATION_HANDOFF.md +++ b/docs/IMPLEMENTATION_HANDOFF.md @@ -449,3 +449,17 @@ pnpm --dir packages/update test - App4 按 RN 0.86 官方模板切换到 Gradle 9.3.1 后,真实执行 `:xuqm_rn-update:testDebugUnitTest` 成功,并完成 451 tasks 六模块 Debug APK 构建;原生存储、暂存、SemVer 与 Bridge 拆分均进入宿主编译,不只是独立源码测试。 - 本轮 SDK 定向复核结果:common 21 tests、update CLI/Metro/native-baseline 14 tests、release-set/plugin-registry 9 tests、bugcollect 5 tests、xwebview 4 tests;根工作区及各 package TypeScript、Prettier 和 update 发布包 35 文件校验均通过。App4 完整门禁同步达到 20 suites / 87 tests。 - Gradle 9 构建仍报告的弃用项来自 React Native/三方插件,不通过 SDK 关闭 warning。后续升级必须继续以宿主完整构建和原生单测为门禁,不能只验证 TypeScript 或 npm 打包。 + +### 2026-07-27 / Metro Debug 与原生配置统一 + +- App4 使用 Nexus 精确版本执行 `pnpm start` + `pnpm android` 时,JS 能通过 Metro + 虚拟模块解析 V2 配置,但 Android 原生 common 报告未找到配置。根因是 + `USE_METRO=true` 同时排除了插件 Bundle 和签名配置 assets,破坏了“一份配置、 + JS/原生一次初始化”的契约。 +- `xuqm-bundles.gradle` 将签名配置复制拆成独立 `CopyXuqmConfigTask`,所有 Android + variant 都从宿主唯一 `src/assets/config/*.xuqmconfig` 生成 + `assets/config/config.xuqmconfig`。Metro Debug 不生成插件 Bundle;Release 或 + `USE_METRO=false` 才继续执行完整 Bundle/资源任务。 +- update 定向测试 46 项通过(CLI/打包 28,领域 18),Prettier 通过。尚需 Jenkins + 发布新的 update alpha,并在 App4 改用该 Nexus 版本后执行 Android Debug/Release + 宿主构建和模拟器日志复核。 diff --git a/packages/update/android/xuqm-bundles.gradle b/packages/update/android/xuqm-bundles.gradle index 6e3f7ef..b4b1c82 100644 --- a/packages/update/android/xuqm-bundles.gradle +++ b/packages/update/android/xuqm-bundles.gradle @@ -40,10 +40,6 @@ abstract class GenerateXuqmBundlesTask extends DefaultTask { @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getPackageFile() - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - abstract RegularFileProperty getSignedConfigFile() - /** * RN/原生源码与打包配置共同决定 bundle、资源和 native baseline。 * 显式建模后 Gradle 才能安全复用上次输出,禁止用永久 out-of-date 代替依赖声明。 @@ -92,12 +88,6 @@ abstract class GenerateXuqmBundlesTask extends DefaultTask { appVersion.get(), ) }.assertNormalExitValue() - project.copy { - from(signedConfigFile.get().asFile) - into(assetsOutputDirectory.get().dir("config")) - rename { "config.xuqmconfig" } - } - // Android 内嵌 Bundle 通过 AssetManager 读取,但 require() 图片必须同时进入 // aapt2 的 drawable 资源表。只把 drawable-* 放进 assets 会导致图片静默为空。 project.delete(resources) @@ -112,6 +102,31 @@ abstract class GenerateXuqmBundlesTask extends DefaultTask { } } +/** + * 所有变体都只复制同一份签名配置。Debug 使用 Metro 时不得生成插件 Bundle, + * 但 Android 原生 SDK 仍必须从 assets 完成与 JS 相同的一次自动初始化。 + */ +@DisableCachingByDefault(because = "Signed config is a small immutable build input") +abstract class CopyXuqmConfigTask extends DefaultTask { + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + abstract RegularFileProperty getSignedConfigFile() + + @OutputDirectory + abstract DirectoryProperty getAssetsOutputDirectory() + + @TaskAction + void copyConfig() { + def output = assetsOutputDirectory.get().asFile + project.delete(output) + project.copy { + from(signedConfigFile.get().asFile) + into(new File(output, "config")) + rename { "config.xuqmconfig" } + } + } +} + if (!plugins.hasPlugin("com.android.application")) { throw new GradleException("xuqm-bundles.gradle must be applied to an Android application module") } @@ -165,11 +180,6 @@ def prepareXuqmBundles = tasks.register( cliFile.set(new File(xuqmProjectRoot, "node_modules/@xuqm/rn-update/scripts/xuqm-rn.mjs")) configFile.set(new File(xuqmProjectRoot, "xuqm.modules.json")) packageFile.set(new File(xuqmProjectRoot, "package.json")) - signedConfigFile.set( - xuqmSignedConfigCandidates - ? xuqmSignedConfigCandidates.first() - : new File(xuqmSignedConfigDirectory, "config.xuqmconfig") - ) sourceFiles.from(project.fileTree(xuqmProjectRoot) { include("src/**") include("assets/**") @@ -223,7 +233,25 @@ def prepareXuqmBundles = tasks.register( resourcesOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/res")) } +def prepareXuqmConfig = tasks.register( + "prepareXuqmConfigAssets", + CopyXuqmConfigTask, +) { + group = "xuqm" + description = "Copies the single signed Xuqm config into Android assets." + signedConfigFile.set( + xuqmSignedConfigCandidates + ? xuqmSignedConfigCandidates.first() + : new File(xuqmSignedConfigDirectory, "config.xuqmconfig") + ) + assetsOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/config-assets")) +} + androidComponents.onVariants(androidComponents.selector().all()) { variant -> + variant.sources.assets?.addGeneratedSourceDirectory( + prepareXuqmConfig, + { task -> task.assetsOutputDirectory }, + ) if (variant.buildType == "release" || !xuqmUseMetro.get()) { variant.sources.assets?.addGeneratedSourceDirectory( prepareXuqmBundles, diff --git a/packages/update/scripts/xuqm-rn.test.mjs b/packages/update/scripts/xuqm-rn.test.mjs index 20533e2..be8ad7b 100644 --- a/packages/update/scripts/xuqm-rn.test.mjs +++ b/packages/update/scripts/xuqm-rn.test.mjs @@ -31,6 +31,16 @@ test('Gradle models and forwards the exact embedded release identity', () => { assert.match(gradle, /@Input\s+abstract Property getBugCollectMode\(\)/) assert.match(gradle, /environment\("XUQM_BUILD_ID", buildId\.get\(\)\)/) assert.match(gradle, /environment\("XUQM_BUGCOLLECT_MODE", bugCollectMode\.get\(\)\)/) + assert.match(gradle, /abstract class CopyXuqmConfigTask extends DefaultTask/) + assert.match( + gradle, + /variant\.sources\.assets\?\.addGeneratedSourceDirectory\(\s*prepareXuqmConfig/, + ) + const bundleTask = gradle.slice( + gradle.indexOf('abstract class GenerateXuqmBundlesTask'), + gradle.indexOf('abstract class CopyXuqmConfigTask'), + ) + assert.doesNotMatch(bundleTask, /getSignedConfigFile\(\)/) }) test('embed creates a complete versioned manifest from a host fixture', () => {