refactor(push): unify file modification approach and improve dependency resolution
Some checks failed
Frontend CI / build-and-push-web (push) Has been cancelled
Frontend CI / ota (android) (push) Has been cancelled
Frontend CI / ota (ios) (push) Has been cancelled
Frontend CI / build-android-apk (push) Failing after 12m49s

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.
This commit is contained in:
lafay
2026-06-18 13:53:36 +08:00
parent 3c452cfbd3
commit 9cba25da00
3 changed files with 104 additions and 183 deletions

View File

@@ -129,134 +129,28 @@ jobs:
- name: Configure Gradle with signing - name: Configure Gradle with signing
run: | run: |
cd android cd android
# Decode Android signing keystore # Decode Android signing keystore
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > app/withyou-release-key.keystore echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > app/withyou-release-key.keystore
# Update settings.gradle # Append signing properties to gradle.properties (secrets appended after prebuild for better cache hit rate)
cat > settings.gradle << 'SETTINGS_EOF' cat >> gradle.properties << 'SIGNING_PROPS'
pluginManagement { MYAPP_UPLOAD_STORE_FILE=withyou-release-key.keystore
repositories { MYAPP_UPLOAD_STORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
maven { url 'https://developer.huawei.com/repo/' } MYAPP_UPLOAD_KEY_ALIAS=withyou-key
google() MYAPP_UPLOAD_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}
mavenCentral() SIGNING_PROPS
gradlePluginPortal()
} # CI build tuning (low-memory container): force single-worker, reduce JVM heap
def reactNativeGradlePlugin = new File( cat >> gradle.properties << 'CI_PROPS'
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'
org.gradle.daemon=false org.gradle.daemon=false
org.gradle.parallel=false org.gradle.parallel=false
org.gradle.configureondemand=false org.gradle.configureondemand=false
org.gradle.workers.max=1 org.gradle.workers.max=1
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError 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 kotlin.daemon.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:ReservedCodeCacheSize=256m
android.enableJetifier=false CI_PROPS
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
# Verify signing config in app/build.gradle # Verify signing config in app/build.gradle
echo "=== app/build.gradle signing section ===" echo "=== app/build.gradle signing section ==="
grep -A 20 'signingConfigs' app/build.gradle || echo "signingConfigs not found" grep -A 20 'signingConfigs' app/build.gradle || echo "signingConfigs not found"

View File

@@ -1,7 +1,4 @@
const { const { withDangerousMod } = require('@expo/config-plugins');
withSettingsGradle,
withDangerousMod,
} = require('@expo/config-plugins');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -11,13 +8,14 @@ const path = require('path');
// 1) 添加 cn.jiguang.sdk.plugin:honor 依赖 // 1) 添加 cn.jiguang.sdk.plugin:honor 依赖
// 2) 注入 manifestPlaceholders: HONOR_APPID // 2) 注入 manifestPlaceholders: HONOR_APPID
// 3) 添加荣耀 Maven 仓库 https://developer.hihonor.com/repo 到 settings.gradle 和根 build.gradle // 3) 添加荣耀 Maven 仓库 https://developer.hihonor.com/repo 到 settings.gradle 和根 build.gradle
// 注意: 必须加到根 build.gradle 的 allprojects.repositories因为 honor:6.1.0 的传递依赖 // 关键: 必须同时加到 settings.gradle 的 dependencyResolutionManagement 块CI build 走这条路径),
// com.hihonor.mcs:push 是运行时依赖(通过 allprojects 解析,不走 buildscript)。 // 和根 build.gradle 的 buildscript+allprojects本地 build 兜底)。
// 4) 添加 Proguard 规则 (com.hihonor.push.** 保留 + 5 条 -keepattributes) // 4) 添加 Proguard 规则 (com.hihonor.push.** 保留 + 5 条 -keepattributes)
// //
// 实现说明:build.gradle 改动统一用 withDangerousMod 直接读写磁盘文件 // 实现说明:所有文件改动统一用 withDangerousMod 直接读写磁盘。
// 原因Expo SDK 56+ 的 withProjectBuildGradle / withAppBuildGradle hook 链中,第二个以后的 // 原因Expo SDK 56+ 的 withSettingsGradle / withProjectBuildGradle / withAppBuildGradle hook 链中,
// plugin 写回 modResults.contents 会被静默忽略,导致多个 vendor plugin 只能写入第一个的修改。 // 第二个以后的 plugin 写回 modResults.contents 会被静默忽略。withDangerousMod 走文件系统串行执行,
// 多 vendor plugin 共存可靠。
const withHonorPush = (config, options = {}) => { const withHonorPush = (config, options = {}) => {
const { const {
jpushVersion = '6.1.0', jpushVersion = '6.1.0',
@@ -26,53 +24,13 @@ const withHonorPush = (config, options = {}) => {
const honorRepoUrl = 'https://developer.hihonor.com/repo'; 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, [ config = withDangerousMod(config, [
'android', 'android',
async (config) => { async (config) => {
const settingsGradlePath = path.join(
config.modRequest.platformProjectRoot,
'settings.gradle'
);
const rootBuildGradlePath = path.join( const rootBuildGradlePath = path.join(
config.modRequest.platformProjectRoot, config.modRequest.platformProjectRoot,
'build.gradle' 'build.gradle'
@@ -88,13 +46,68 @@ const withHonorPush = (config, options = {}) => {
'proguard-rules.pro' '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)) { if (fs.existsSync(rootBuildGradlePath)) {
let gradle = fs.readFileSync(rootBuildGradlePath, 'utf-8'); let gradle = fs.readFileSync(rootBuildGradlePath, 'utf-8');
let changed = false; let changed = false;
const repoLine = ` maven { url '${honorRepoUrl}' }`; const repoLine = ` maven { url '${honorRepoUrl}' }`;
// 注入到 buildscript.repositories用于解析 buildscript classpath
if (!gradle.match(/buildscript\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { if (!gradle.match(/buildscript\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) {
const bsRepoPattern = /(buildscript\s*\{[\s\S]*?repositories\s*\{)/; const bsRepoPattern = /(buildscript\s*\{[\s\S]*?repositories\s*\{)/;
if (bsRepoPattern.test(gradle)) { 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/)) { if (!gradle.match(/allprojects\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) {
const allProjectsPattern = /(allprojects\s*\{[\s\S]*?repositories\s*\{)/; const allProjectsPattern = /(allprojects\s*\{[\s\S]*?repositories\s*\{)/;
if (allProjectsPattern.test(gradle)) { if (allProjectsPattern.test(gradle)) {
gradle = gradle.replace(allProjectsPattern, `$1\n${repoLine}`); gradle = gradle.replace(allProjectsPattern, `$1\n${repoLine}`);
changed = true; changed = true;
} else { } else {
// 没有 allprojects 块时新建
const allProjectsBlock = `\nallprojects {\n repositories {${repoLine}\n }\n}\n`; const allProjectsBlock = `\nallprojects {\n repositories {${repoLine}\n }\n}\n`;
gradle = gradle + allProjectsBlock; gradle = gradle + allProjectsBlock;
changed = true; 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)) { if (fs.existsSync(appBuildGradlePath)) {
let gradle = fs.readFileSync(appBuildGradlePath, 'utf-8'); let gradle = fs.readFileSync(appBuildGradlePath, 'utf-8');
let changed = false; let changed = false;
// 注入 cn.jiguang.sdk.plugin:honor 依赖
if (!gradle.includes('cn.jiguang.sdk.plugin:honor')) { if (!gradle.includes('cn.jiguang.sdk.plugin:honor')) {
const depsPattern = /dependencies\s*\{/; const depsPattern = /dependencies\s*\{/;
if (depsPattern.test(gradle)) { if (depsPattern.test(gradle)) {
@@ -140,7 +150,6 @@ const withHonorPush = (config, options = {}) => {
} }
} }
// 注入 manifestPlaceholders: HONOR_APPID
const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/; const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/;
const match = gradle.match(manifestPlaceholderRegex); const match = gradle.match(manifestPlaceholderRegex);
@@ -174,7 +183,6 @@ const withHonorPush = (config, options = {}) => {
changed = true; changed = true;
} }
} else if (gradle.includes('REACT_NATIVE_RELEASE_LEVEL')) { } else if (gradle.includes('REACT_NATIVE_RELEASE_LEVEL')) {
// 没有 manifestPlaceholders block 时新建
const entriesStr = Object.entries(requiredPlaceholders) const entriesStr = Object.entries(requiredPlaceholders)
.map(([k, v]) => `${k}: "${v}"`) .map(([k, v]) => `${k}: "${v}"`)
.join(',\n '); .join(',\n ');
@@ -196,7 +204,7 @@ const withHonorPush = (config, options = {}) => {
} }
} }
// --- 2c. proguard-rules.pro --- // --- 4. proguard-rules.pro ---
if (fs.existsSync(proguardFilePath)) { if (fs.existsSync(proguardFilePath)) {
let proguard = fs.readFileSync(proguardFilePath, 'utf-8'); let proguard = fs.readFileSync(proguardFilePath, 'utf-8');
if (!proguard.includes('-keep class com.hihonor.push.**')) { if (!proguard.includes('-keep class com.hihonor.push.**')) {

View File

@@ -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/)) { if (!config.modResults.contents.match(/dependencyResolutionManagement[\s\S]*developer\.huawei\.com\/repo/)) {
const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/;
if (depMgmtPattern.test(config.modResults.contents)) { if (depMgmtPattern.test(config.modResults.contents)) {
// Block exists — append Huawei to its repositories
config.modResults.contents = config.modResults.contents.replace( config.modResults.contents = config.modResults.contents.replace(
depMgmtPattern, depMgmtPattern,
`$&\n maven { url 'https://developer.huawei.com/repo/' }` `$&\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;
}
} }
} }