From 9cba25da00e1a388983bfa28b34992e9e1074a17 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Thu, 18 Jun 2026 13:53:36 +0800 Subject: [PATCH] refactor(push): unify file modification approach and improve dependency resolution Simplify Honor push plugin to use withDangerousMod consistently for all file modifications, replacing the withSettingsGradle approach that silently drops writes in Expo SDK 56+ multi-vendor plugin scenarios. Enhance Huawei push plugin to create missing dependencyResolutionManagement block instead of only appending to existing ones, ensuring CI build paths work reliably when Gradle's dependencyResolutionManagement takes precedence. Remove inline settings.gradle/build.gradle generation from CI workflow, relying on expo prebuild for Gradle file generation. --- .gitea/workflows/build.yml | 134 ++++--------------------------------- plugins/withHonorPush.js | 132 +++++++++++++++++++----------------- plugins/withHuaweiPush.js | 21 +++++- 3 files changed, 104 insertions(+), 183 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 7824862..ca10a20 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -129,134 +129,28 @@ jobs: - name: Configure Gradle with signing run: | cd android - + # Decode Android signing keystore echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > app/withyou-release-key.keystore - - # Update settings.gradle - cat > settings.gradle << 'SETTINGS_EOF' - pluginManagement { - repositories { - maven { url 'https://developer.huawei.com/repo/' } - google() - mavenCentral() - gradlePluginPortal() - } - def reactNativeGradlePlugin = new File( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })") - }.standardOutput.asText.get().trim() - ).getParentFile().absolutePath - includeBuild(reactNativeGradlePlugin) - - def expoPluginsPath = new File( - providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })") - }.standardOutput.asText.get().trim(), - "../android/expo-gradle-plugin" - ).absolutePath - includeBuild(expoPluginsPath) - } - - plugins { - id("com.facebook.react.settings") - id("expo-autolinking-settings") - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" - } - - dependencyResolutionManagement { - repositories { - maven { url 'https://developer.huawei.com/repo/' } - google() - mavenCentral() - maven { url 'https://www.jitpack.io' } - } - } - - extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> - if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { - ex.autolinkLibrariesFromCommand() - } else { - ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand) - } - } - expoAutolinking.useExpoModules() - - rootProject.name = '威友' - - expoAutolinking.useExpoVersionCatalog() - - include ':app' - includeBuild(expoAutolinking.reactNativeGradlePlugin) - SETTINGS_EOF - - # Update build.gradle - cat > build.gradle << 'BUILD_EOF' - // Top-level build file where you can add configuration options common to all sub-projects/modules. - - buildscript { - repositories { - maven { url 'https://developer.huawei.com/repo/' } - google() - mavenCentral() - } - dependencies { - classpath('com.huawei.agconnect:agcp:1.9.1.301') { - exclude group: 'com.huawei.agconnect', module: 'agconnect-apms-plugin' - } - classpath('com.google.gms:google-services:4.4.4') - classpath('com.android.tools.build:gradle:8.12.0') - classpath('com.facebook.react:react-native-gradle-plugin') - classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') - } - } - - allprojects { - repositories { - maven { url 'https://developer.huawei.com/repo/' } - google() - mavenCentral() - maven { url 'https://www.jitpack.io' } - } - } - - apply plugin: "expo-root-project" - apply plugin: "com.facebook.react.rootproject" - BUILD_EOF - - # Update gradle.properties (without secrets for better cache hit rate) - cat > gradle.properties << 'PROPS_EOF' + + # Append signing properties to gradle.properties (secrets appended after prebuild for better cache hit rate) + cat >> gradle.properties << 'SIGNING_PROPS' + MYAPP_UPLOAD_STORE_FILE=withyou-release-key.keystore + MYAPP_UPLOAD_STORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + MYAPP_UPLOAD_KEY_ALIAS=withyou-key + MYAPP_UPLOAD_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }} + SIGNING_PROPS + + # CI build tuning (low-memory container): force single-worker, reduce JVM heap + cat >> gradle.properties << 'CI_PROPS' org.gradle.daemon=false org.gradle.parallel=false org.gradle.configureondemand=false org.gradle.workers.max=1 org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError kotlin.daemon.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:ReservedCodeCacheSize=256m - android.enableJetifier=false - android.useAndroidX=true - hermesEnabled=true - reactNativeArchitectures=arm64-v8a - newArchEnabled=true - edgeToEdgeEnabled=true - expo.gif.enabled=true - expo.webp.enabled=true - expo.webp.animated=false - ndkVersion=27.1.12297006 - expo.useLegacyPackaging=false - systemProp.org.gradle.internal.http.connectionTimeout=30000 - systemProp.org.gradle.internal.http.socketTimeout=30000 - PROPS_EOF - - # Append signing properties (secrets appended, not cached) - cat >> gradle.properties << SIGNING_PROPS - MYAPP_UPLOAD_STORE_FILE=withyou-release-key.keystore - MYAPP_UPLOAD_STORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - MYAPP_UPLOAD_KEY_ALIAS=withyou-key - MYAPP_UPLOAD_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }} - SIGNING_PROPS - + CI_PROPS + # Verify signing config in app/build.gradle echo "=== app/build.gradle signing section ===" grep -A 20 'signingConfigs' app/build.gradle || echo "signingConfigs not found" diff --git a/plugins/withHonorPush.js b/plugins/withHonorPush.js index c443884..5a38f4e 100644 --- a/plugins/withHonorPush.js +++ b/plugins/withHonorPush.js @@ -1,7 +1,4 @@ -const { - withSettingsGradle, - withDangerousMod, -} = require('@expo/config-plugins'); +const { withDangerousMod } = require('@expo/config-plugins'); const fs = require('fs'); const path = require('path'); @@ -11,13 +8,14 @@ const path = require('path'); // 1) 添加 cn.jiguang.sdk.plugin:honor 依赖 // 2) 注入 manifestPlaceholders: HONOR_APPID // 3) 添加荣耀 Maven 仓库 https://developer.hihonor.com/repo 到 settings.gradle 和根 build.gradle -// 注意: 必须加到根 build.gradle 的 allprojects.repositories,因为 honor:6.1.0 的传递依赖 -// com.hihonor.mcs:push 是运行时依赖(通过 allprojects 解析,不走 buildscript)。 +// 关键: 必须同时加到 settings.gradle 的 dependencyResolutionManagement 块(CI build 走这条路径), +// 和根 build.gradle 的 buildscript+allprojects(本地 build 兜底)。 // 4) 添加 Proguard 规则 (com.hihonor.push.** 保留 + 5 条 -keepattributes) // -// 实现说明:build.gradle 改动统一用 withDangerousMod 直接读写磁盘文件。 -// 原因:Expo SDK 56+ 的 withProjectBuildGradle / withAppBuildGradle hook 链中,第二个以后的 -// plugin 写回 modResults.contents 会被静默忽略,导致多个 vendor plugin 只能写入第一个的修改。 +// 实现说明:所有文件改动统一用 withDangerousMod 直接读写磁盘。 +// 原因:Expo SDK 56+ 的 withSettingsGradle / withProjectBuildGradle / withAppBuildGradle hook 链中, +// 第二个以后的 plugin 写回 modResults.contents 会被静默忽略。withDangerousMod 走文件系统串行执行, +// 多 vendor plugin 共存可靠。 const withHonorPush = (config, options = {}) => { const { jpushVersion = '6.1.0', @@ -26,53 +24,13 @@ const withHonorPush = (config, options = {}) => { const honorRepoUrl = 'https://developer.hihonor.com/repo'; - // 1. settings.gradle: 添加荣耀 Maven 仓库 - config = withSettingsGradle(config, (config) => { - let contents = config.modResults.contents; - - if (!contents.includes('developer.hihonor.com/repo')) { - const repoLine = ` maven { url '${honorRepoUrl}' }`; - const hasRepositoriesInPM = /pluginManagement\s*\{[\s\S]*?repositories\s*\{/.test(contents); - - if (hasRepositoriesInPM) { - config.modResults.contents = contents.replace( - /(pluginManagement\s*\{[\s\S]*?repositories\s*\{)/, - `$1\n ${repoLine}` - ); - } else if (contents.includes('pluginManagement')) { - config.modResults.contents = contents.replace( - /(pluginManagement\s*\{)/, - `$1\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n ${repoLine}\n }` - ); - } else { - config.modResults.contents = - `pluginManagement {\n repositories {\n ${repoLine}\n }\n}\n\n` + - config.modResults.contents; - } - } - - // Gradle 9+ layout - if ( - !config.modResults.contents.match( - /dependencyResolutionManagement[\s\S]*developer\.hihonor\.com\/repo/ - ) - ) { - const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; - if (depMgmtPattern.test(config.modResults.contents)) { - config.modResults.contents = config.modResults.contents.replace( - depMgmtPattern, - `$&\n maven { url '${honorRepoUrl}' }` - ); - } - } - - return config; - }); - - // 2. 根 build.gradle: 添加荣耀 Maven 仓库到 buildscript + allprojects (用 withDangerousMod 直接改文件) config = withDangerousMod(config, [ 'android', async (config) => { + const settingsGradlePath = path.join( + config.modRequest.platformProjectRoot, + 'settings.gradle' + ); const rootBuildGradlePath = path.join( config.modRequest.platformProjectRoot, 'build.gradle' @@ -88,13 +46,68 @@ const withHonorPush = (config, options = {}) => { 'proguard-rules.pro' ); - // --- 2a. 根 build.gradle: 仓库注入 --- + // --- 1. settings.gradle: pluginManagement + dependencyResolutionManagement --- + if (fs.existsSync(settingsGradlePath)) { + let settings = fs.readFileSync(settingsGradlePath, 'utf-8'); + let settingsChanged = false; + const repoLine = ` maven { url '${honorRepoUrl}' }`; + + // pluginManagement.repositories + if (!settings.match(/pluginManagement\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { + const hasRepositoriesInPM = /pluginManagement\s*\{[\s\S]*?repositories\s*\{/.test(settings); + if (hasRepositoriesInPM) { + settings = settings.replace( + /(pluginManagement\s*\{[\s\S]*?repositories\s*\{)/, + `$1\n ${repoLine}` + ); + settingsChanged = true; + } else if (settings.includes('pluginManagement')) { + settings = settings.replace( + /(pluginManagement\s*\{)/, + `$1\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n ${repoLine}\n }` + ); + settingsChanged = true; + } else { + settings = + `pluginManagement {\n repositories {\n ${repoLine}\n }\n}\n\n` + settings; + settingsChanged = true; + } + } + + // dependencyResolutionManagement (Gradle 7+, takes precedence over allprojects in CI) + if (!settings.match(/dependencyResolutionManagement[\s\S]*developer\.hihonor\.com\/repo/)) { + const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; + if (depMgmtPattern.test(settings)) { + settings = settings.replace( + depMgmtPattern, + `$&\n maven { url '${honorRepoUrl}' }` + ); + settingsChanged = true; + } else { + // Block doesn't exist — fallback: create with Huawei + Honor + standard repos. + const block = `\ndependencyResolutionManagement {\n repositories {\n maven { url 'https://developer.huawei.com/repo/' }\n maven { url '${honorRepoUrl}' }\n google()\n mavenCentral()\n maven { url 'https://www.jitpack.io' }\n }\n}\n`; + const pluginsBlockEnd = /(\nplugins\s*\{[\s\S]*?\n\})/; + if (pluginsBlockEnd.test(settings)) { + settings = settings.replace(pluginsBlockEnd, `$1\n${block}`); + } else { + settings = settings + block; + } + settingsChanged = true; + } + } + + if (settingsChanged) { + fs.writeFileSync(settingsGradlePath, settings); + console.log('[withHonorPush] updated settings.gradle (pluginManagement + dependencyResolutionManagement)'); + } + } + + // --- 2. 根 build.gradle: buildscript + allprojects repositories --- if (fs.existsSync(rootBuildGradlePath)) { let gradle = fs.readFileSync(rootBuildGradlePath, 'utf-8'); let changed = false; const repoLine = ` maven { url '${honorRepoUrl}' }`; - // 注入到 buildscript.repositories(用于解析 buildscript classpath) if (!gradle.match(/buildscript\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { const bsRepoPattern = /(buildscript\s*\{[\s\S]*?repositories\s*\{)/; if (bsRepoPattern.test(gradle)) { @@ -103,14 +116,12 @@ const withHonorPush = (config, options = {}) => { } } - // 注入到 allprojects.repositories(用于解析运行时依赖如 com.hihonor.mcs:push) if (!gradle.match(/allprojects\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { const allProjectsPattern = /(allprojects\s*\{[\s\S]*?repositories\s*\{)/; if (allProjectsPattern.test(gradle)) { gradle = gradle.replace(allProjectsPattern, `$1\n${repoLine}`); changed = true; } else { - // 没有 allprojects 块时新建 const allProjectsBlock = `\nallprojects {\n repositories {${repoLine}\n }\n}\n`; gradle = gradle + allProjectsBlock; changed = true; @@ -123,12 +134,11 @@ const withHonorPush = (config, options = {}) => { } } - // --- 2b. app/build.gradle: AAR 依赖 + manifestPlaceholders --- + // --- 3. app/build.gradle: AAR 依赖 + manifestPlaceholders --- if (fs.existsSync(appBuildGradlePath)) { let gradle = fs.readFileSync(appBuildGradlePath, 'utf-8'); let changed = false; - // 注入 cn.jiguang.sdk.plugin:honor 依赖 if (!gradle.includes('cn.jiguang.sdk.plugin:honor')) { const depsPattern = /dependencies\s*\{/; if (depsPattern.test(gradle)) { @@ -140,7 +150,6 @@ const withHonorPush = (config, options = {}) => { } } - // 注入 manifestPlaceholders: HONOR_APPID const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/; const match = gradle.match(manifestPlaceholderRegex); @@ -174,7 +183,6 @@ const withHonorPush = (config, options = {}) => { changed = true; } } else if (gradle.includes('REACT_NATIVE_RELEASE_LEVEL')) { - // 没有 manifestPlaceholders block 时新建 const entriesStr = Object.entries(requiredPlaceholders) .map(([k, v]) => `${k}: "${v}"`) .join(',\n '); @@ -196,7 +204,7 @@ const withHonorPush = (config, options = {}) => { } } - // --- 2c. proguard-rules.pro --- + // --- 4. proguard-rules.pro --- if (fs.existsSync(proguardFilePath)) { let proguard = fs.readFileSync(proguardFilePath, 'utf-8'); if (!proguard.includes('-keep class com.hihonor.push.**')) { diff --git a/plugins/withHuaweiPush.js b/plugins/withHuaweiPush.js index 367389e..a213350 100644 --- a/plugins/withHuaweiPush.js +++ b/plugins/withHuaweiPush.js @@ -42,14 +42,33 @@ const withHuaweiPush = (config, options = {}) => { } } - // Ensure Huawei Maven repo in dependencyResolutionManagement (Gradle 9+) + // Ensure Huawei Maven repo in dependencyResolutionManagement (Gradle 7+). + // CI build paths require this block (Gradle dependencyResolutionManagement + // takes precedence over allprojects.repositories when configured). + // Create the block if it doesn't exist, otherwise inject Huawei into it. if (!config.modResults.contents.match(/dependencyResolutionManagement[\s\S]*developer\.huawei\.com\/repo/)) { const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; if (depMgmtPattern.test(config.modResults.contents)) { + // Block exists — append Huawei to its repositories config.modResults.contents = config.modResults.contents.replace( depMgmtPattern, `$&\n maven { url 'https://developer.huawei.com/repo/' }` ); + } else { + // Block doesn't exist — create it after the `plugins { ... }` block + // (or after pluginManagement if no plugins block) so it sits where + // Gradle expects it. + const block = `\ndependencyResolutionManagement {\n repositories {\n maven { url 'https://developer.huawei.com/repo/' }\n google()\n mavenCentral()\n maven { url 'https://www.jitpack.io' }\n }\n}\n`; + const pluginsBlockEnd = /(\nplugins\s*\{[\s\S]*?\n\})/; + if (pluginsBlockEnd.test(config.modResults.contents)) { + config.modResults.contents = config.modResults.contents.replace( + pluginsBlockEnd, + `$1\n${block}` + ); + } else { + // Fallback: append to end + config.modResults.contents = config.modResults.contents + block; + } } }