feat(channel): 支持渠道 Manifest 增量合并

This commit is contained in:
2026-07-08 20:20:23 +08:00
parent 833f68f601
commit 2107f437a7
22 changed files with 287 additions and 194 deletions

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEngine;
@@ -16,6 +17,10 @@ public class ChannelBuildTool
private const string AndroidAddressablesContentState = "Assets/AddressableAssetsData/Android/addressables_content_state.bin";
private const string DefaultProductName = "Fishing Travel";
private const string EnjoyPayProductName = "Fishing Travel: Hook & Explore";
private const string ChannelManifestLib = "channel_manifest_lib";
private const string EmptyManifest = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" />\n";
private static readonly XNamespace AndroidNamespace = "http://schemas.android.com/apk/res/android";
private static readonly XNamespace ToolsNamespace = "http://schemas.android.com/tools";
// ==================== Channel Switching ====================
@@ -29,9 +34,10 @@ public class ChannelBuildTool
}
Debug.Log($"[ChannelSwitch] Switching to {channel}...");
CleanChannelManifestLibBuildArtifacts();
// 1. Sync root Android config files
SyncRootAndroidConfigFiles(cfgDir);
SyncRootAndroidConfigFiles(channel, cfgDir);
// 2. Sync channel SDK adapter Java files to tysdk package
SyncChannelAdapterFiles(cfgDir);
@@ -110,6 +116,7 @@ public class ChannelBuildTool
{
try
{
CleanChannelManifestLibBuildArtifacts();
SwitchChannel(channel);
var fileName = debug ? $"tysdkTest_{channel}_debug.apk" : $"tysdkTest_{channel}.apk";
@@ -190,11 +197,12 @@ public class ChannelBuildTool
}
}
private static void SyncRootAndroidConfigFiles(string cfgDir)
private static void SyncRootAndroidConfigFiles(string channel, string cfgDir)
{
CopyRootConfigFiles(cfgDir, "*.gradle");
CopyRootConfigFiles(cfgDir, "*.properties");
CopyRootConfigFile(cfgDir, "AndroidManifest.xml");
SetMainManifestChannel(channel);
SyncChannelManifestLib(channel, cfgDir);
}
private static void SyncChannelAdapterFiles(string cfgDir)
@@ -215,18 +223,134 @@ public class ChannelBuildTool
}
}
private static void CopyRootConfigFiles(string cfgDir, string searchPattern)
private static void SyncChannelManifestLib(string channel, string cfgDir)
{
foreach (var src in Directory.GetFiles(cfgDir, searchPattern, SearchOption.TopDirectoryOnly))
var dest = Path.Combine(AssetsPlugins, ChannelManifestLib, "src");
if (channel == "GooglePlay")
{
CopyRootConfigFile(src);
WriteEmptyChannelManifestLib(dest);
Debug.Log($"[ChannelSwitch] Manifest lib reset to empty default. Channel={channel}");
return;
}
var srcManifest = Path.Combine(cfgDir, "AndroidManifest.xml");
if (!File.Exists(srcManifest))
{
throw new FileNotFoundException($"[ChannelSwitch] Channel manifest not found: {srcManifest}");
}
WriteChannelManifestLib(srcManifest, Path.Combine(cfgDir, "res"), dest);
Debug.Log($"[ChannelSwitch] Manifest: {srcManifest} -> {Path.Combine(dest, "main", "AndroidManifest.xml")}");
}
private static void SetMainManifestChannel(string channel)
{
var manifestPath = Path.Combine(AssetsPlugins, "AndroidManifest.xml");
if (!File.Exists(manifestPath))
{
throw new FileNotFoundException($"[ChannelSwitch] Main AndroidManifest not found: {manifestPath}");
}
var doc = XDocument.Load(manifestPath);
var root = doc.Root;
var application = root?.Elements().FirstOrDefault(x => x.Name.LocalName == "application");
if (application == null)
{
throw new InvalidDataException($"[ChannelSwitch] Main AndroidManifest application node not found: {manifestPath}");
}
var channelMeta = application.Elements()
.FirstOrDefault(x => x.Name.LocalName == "meta-data" &&
(string)x.Attribute(AndroidNamespace + "name") == "com.arkgame.ft.channel");
if (channelMeta == null)
{
channelMeta = new XElement("meta-data",
new XAttribute(AndroidNamespace + "name", "com.arkgame.ft.channel"));
application.Add(channelMeta);
}
channelMeta.SetAttributeValue(AndroidNamespace + "value", channel);
SetMainManifestBackupRules(application, channel);
doc.Save(manifestPath);
AssetDatabase.ImportAsset(manifestPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
Debug.Log($"[ChannelSwitch] Main manifest channel -> {channel}");
}
private static void SetMainManifestBackupRules(XElement application, string channel)
{
const string fullBackupContent = "android:fullBackupContent";
var replaceAttr = application.Attribute(ToolsNamespace + "replace");
var replaceItems = replaceAttr?.Value
.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrEmpty(x))
.ToList() ?? new List<string>();
if (channel == "Flexion")
{
application.SetAttributeValue(AndroidNamespace + "fullBackupContent", "@xml/flexion_backup_rules");
if (!replaceItems.Contains(fullBackupContent))
{
replaceItems.Add(fullBackupContent);
}
}
else
{
application.SetAttributeValue(AndroidNamespace + "fullBackupContent", null);
replaceItems.RemoveAll(x => x == fullBackupContent);
}
application.SetAttributeValue(ToolsNamespace + "replace",
replaceItems.Count > 0 ? string.Join(",", replaceItems) : null);
}
private static void WriteEmptyChannelManifestLib(string dest)
{
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
var manifestDir = Path.Combine(dest, "main");
Directory.CreateDirectory(manifestDir);
File.WriteAllText(Path.Combine(manifestDir, "AndroidManifest.xml"), EmptyManifest);
}
private static void CleanChannelManifestLibBuildArtifacts()
{
var buildDir = Path.Combine(AssetsPlugins, ChannelManifestLib, "build");
if (Directory.Exists(buildDir))
{
Directory.Delete(buildDir, true);
}
var buildMeta = buildDir + ".meta";
if (File.Exists(buildMeta))
{
File.Delete(buildMeta);
}
}
private static void CopyRootConfigFile(string cfgDir, string fileName)
private static void WriteChannelManifestLib(string srcManifest, string srcResDir, string dest)
{
var src = Path.Combine(cfgDir, fileName);
if (File.Exists(src))
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
var manifestDir = Path.Combine(dest, "main");
Directory.CreateDirectory(manifestDir);
File.Copy(srcManifest, Path.Combine(manifestDir, "AndroidManifest.xml"), true);
if (Directory.Exists(srcResDir))
{
CopyDirectoryClean(srcResDir, Path.Combine(manifestDir, "res"));
}
}
private static void CopyRootConfigFiles(string cfgDir, string searchPattern)
{
foreach (var src in Directory.GetFiles(cfgDir, searchPattern, SearchOption.TopDirectoryOnly))
{
CopyRootConfigFile(src);
}
@@ -240,6 +364,27 @@ public class ChannelBuildTool
Debug.Log($"[ChannelSwitch] Config: {src} -> {dest}");
}
private static void CopyDirectoryClean(string src, string dest)
{
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
Directory.CreateDirectory(dest);
foreach (var srcFile in Directory.GetFiles(src, "*", SearchOption.AllDirectories))
{
if (srcFile.EndsWith(".meta")) continue;
var relativePath = srcFile.Substring(src.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var destFile = Path.Combine(dest, relativePath);
var destDir = Path.GetDirectoryName(destFile);
if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
File.Copy(srcFile, destFile, true);
}
}
private static void SetAddressablesProfile(string channel)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;