You've already forked Commercialization.topon
611 lines
20 KiB
C#
611 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEditor;
|
|
using UnityEditor.Callbacks;
|
|
using UnityEngine;
|
|
|
|
namespace Topon_Adapter.Editor
|
|
{
|
|
[Serializable]
|
|
public sealed class ToponBuildSettings
|
|
{
|
|
public bool enableDebuggerUI = false;
|
|
public bool forceVerbtoUtilVersion = true;
|
|
public string verbtoUtilDependency = ToponBuildSettingsStore.DefaultVerbtoUtilDependency;
|
|
public bool stripResolvedDebuggerArtifacts = true;
|
|
public bool enableIOSDebuggerUI = false;
|
|
public string iosDebuggerPodVersion = ToponBuildSettingsStore.DefaultIOSDebuggerPodVersion;
|
|
}
|
|
|
|
internal static class ToponBuildSettingsStore
|
|
{
|
|
public const string DefaultVerbtoUtilDependency = "com.verbto.tools:util:1.1.3";
|
|
public const string IOSDebuggerPodName = "AnyThinkDebugUISDK";
|
|
public const string DefaultIOSDebuggerPodVersion = "1.0.7";
|
|
|
|
private const string ActiveBuildSessionKey = "Commercialization.Topon.ActiveBuildSettings";
|
|
private const string SettingsFileSuffix = "_topon_build_settings.json";
|
|
private const string BuildConfigsFolder = "BuildConfigs";
|
|
private const string DebuggerPackageMarker = "com.anythink.sdk:debugger-ui";
|
|
private const string VerbtoPackageMarker = "com.verbto.tools:util";
|
|
|
|
[Serializable]
|
|
private sealed class ActiveBuildSettings
|
|
{
|
|
public ToponBuildSettings settings;
|
|
public long utcTicks;
|
|
}
|
|
|
|
public static ToponBuildSettings CreateDefault()
|
|
{
|
|
return new ToponBuildSettings();
|
|
}
|
|
|
|
public static string GetVerbtoUtilDependency(ToponBuildSettings settings)
|
|
{
|
|
if (settings == null || string.IsNullOrWhiteSpace(settings.verbtoUtilDependency))
|
|
{
|
|
return DefaultVerbtoUtilDependency;
|
|
}
|
|
|
|
return settings.verbtoUtilDependency.Trim();
|
|
}
|
|
|
|
public static string GetIOSDebuggerPodVersion(ToponBuildSettings settings)
|
|
{
|
|
if (settings == null || string.IsNullOrWhiteSpace(settings.iosDebuggerPodVersion))
|
|
{
|
|
return DefaultIOSDebuggerPodVersion;
|
|
}
|
|
|
|
return settings.iosDebuggerPodVersion.Trim();
|
|
}
|
|
|
|
public static ToponBuildSettings LoadForProfileName(string profileName, string repositoryRoot)
|
|
{
|
|
var settings = CreateDefault();
|
|
var path = GetSettingsPath(profileName, repositoryRoot);
|
|
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
|
{
|
|
return settings;
|
|
}
|
|
|
|
try
|
|
{
|
|
JsonUtility.FromJsonOverwrite(File.ReadAllText(path), settings);
|
|
Normalize(settings);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogWarning($"[TopOn Build] Failed to read build settings: {exception.Message}");
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
public static void SaveForProfileName(string profileName, string repositoryRoot, ToponBuildSettings settings)
|
|
{
|
|
if (settings == null)
|
|
{
|
|
settings = CreateDefault();
|
|
}
|
|
else
|
|
{
|
|
Normalize(settings);
|
|
}
|
|
|
|
var path = GetSettingsPath(profileName, repositoryRoot);
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var directory = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
File.WriteAllText(path, JsonUtility.ToJson(settings, true));
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogWarning($"[TopOn Build] Failed to save build settings: {exception.Message}");
|
|
}
|
|
}
|
|
|
|
public static void SetActiveForCurrentBuild(ToponBuildSettings settings)
|
|
{
|
|
var activeSettings = new ActiveBuildSettings
|
|
{
|
|
settings = Clone(settings),
|
|
utcTicks = DateTime.UtcNow.Ticks
|
|
};
|
|
|
|
SessionState.SetString(ActiveBuildSessionKey, JsonUtility.ToJson(activeSettings));
|
|
}
|
|
|
|
public static ToponBuildSettings GetActiveForCurrentBuild()
|
|
{
|
|
var json = SessionState.GetString(ActiveBuildSessionKey, string.Empty);
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
{
|
|
return CreateDefault();
|
|
}
|
|
|
|
try
|
|
{
|
|
var activeSettings = JsonUtility.FromJson<ActiveBuildSettings>(json);
|
|
if (activeSettings == null || activeSettings.settings == null)
|
|
{
|
|
return CreateDefault();
|
|
}
|
|
Normalize(activeSettings.settings);
|
|
|
|
var activatedAt = new DateTime(activeSettings.utcTicks, DateTimeKind.Utc);
|
|
if (DateTime.UtcNow - activatedAt > TimeSpan.FromHours(6))
|
|
{
|
|
ClearActiveBuildSettings();
|
|
return CreateDefault();
|
|
}
|
|
|
|
return Clone(activeSettings.settings);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogWarning($"[TopOn Build] Failed to read active build settings: {exception.Message}");
|
|
return CreateDefault();
|
|
}
|
|
}
|
|
|
|
public static void ClearActiveBuildSettings()
|
|
{
|
|
SessionState.EraseString(ActiveBuildSessionKey);
|
|
}
|
|
|
|
public static bool HasStaleDebuggerResolverOutput(string repositoryRoot)
|
|
{
|
|
foreach (var path in GetResolverOutputPaths(repositoryRoot))
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var content = File.ReadAllText(path);
|
|
if (content.Contains(DebuggerPackageMarker))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool HasUnforcedVerbtoUtilOutput(string repositoryRoot, string expectedDependency)
|
|
{
|
|
expectedDependency = NormalizeDependency(expectedDependency);
|
|
foreach (var path in GetResolverOutputPaths(repositoryRoot))
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var content = File.ReadAllText(path);
|
|
if (content.Contains(VerbtoPackageMarker) && !content.Contains(expectedDependency))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
foreach (var path in FindResolvedVerbtoUtilArtifacts(repositoryRoot))
|
|
{
|
|
if (!IsExpectedVerbtoUtilArtifactFileName(Path.GetFileName(path), expectedDependency))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static IReadOnlyList<string> FindResolvedDebuggerArtifacts(string repositoryRoot)
|
|
{
|
|
var result = new List<string>();
|
|
var root = ResolveRepositoryRoot(repositoryRoot);
|
|
if (string.IsNullOrEmpty(root))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
var androidPluginPath = Path.Combine(root, "Assets", "Plugins", "Android");
|
|
if (!Directory.Exists(androidPluginPath))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
foreach (var path in Directory.GetFiles(androidPluginPath, "*", SearchOption.AllDirectories))
|
|
{
|
|
if (IsDebuggerArtifactFileName(Path.GetFileName(path)))
|
|
{
|
|
result.Add(path);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static IReadOnlyList<string> FindResolvedVerbtoUtilArtifacts(string repositoryRoot)
|
|
{
|
|
var result = new List<string>();
|
|
var root = ResolveRepositoryRoot(repositoryRoot);
|
|
if (string.IsNullOrEmpty(root))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
var androidPluginPath = Path.Combine(root, "Assets", "Plugins", "Android");
|
|
if (!Directory.Exists(androidPluginPath))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
foreach (var path in Directory.GetFiles(androidPluginPath, "*", SearchOption.AllDirectories))
|
|
{
|
|
if (IsVerbtoUtilArtifactFileName(Path.GetFileName(path)))
|
|
{
|
|
result.Add(path);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal static bool IsDebuggerArtifactFileName(string fileName)
|
|
{
|
|
if (string.IsNullOrEmpty(fileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return fileName.StartsWith("com.anythink.sdk.debugger-ui-", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
internal static bool IsVerbtoUtilArtifactFileName(string fileName)
|
|
{
|
|
if (string.IsNullOrEmpty(fileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return fileName.StartsWith("com.verbto.tools.util-", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
internal static bool IsExpectedVerbtoUtilArtifactFileName(string fileName, string expectedDependency)
|
|
{
|
|
if (!IsVerbtoUtilArtifactFileName(fileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var version = GetVersionFromDependency(expectedDependency);
|
|
if (string.IsNullOrEmpty(version))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return fileName.StartsWith($"com.verbto.tools.util-{version}", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static ToponBuildSettings Clone(ToponBuildSettings settings)
|
|
{
|
|
if (settings == null)
|
|
{
|
|
return CreateDefault();
|
|
}
|
|
|
|
return new ToponBuildSettings
|
|
{
|
|
enableDebuggerUI = settings.enableDebuggerUI,
|
|
forceVerbtoUtilVersion = settings.forceVerbtoUtilVersion,
|
|
verbtoUtilDependency = GetVerbtoUtilDependency(settings),
|
|
stripResolvedDebuggerArtifacts = settings.stripResolvedDebuggerArtifacts,
|
|
enableIOSDebuggerUI = settings.enableIOSDebuggerUI,
|
|
iosDebuggerPodVersion = GetIOSDebuggerPodVersion(settings)
|
|
};
|
|
}
|
|
|
|
private static void Normalize(ToponBuildSettings settings)
|
|
{
|
|
if (settings == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
settings.verbtoUtilDependency = NormalizeDependency(settings.verbtoUtilDependency);
|
|
settings.iosDebuggerPodVersion = NormalizeIOSDebuggerPodVersion(settings.iosDebuggerPodVersion);
|
|
}
|
|
|
|
private static string NormalizeDependency(string dependency)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(dependency))
|
|
{
|
|
return DefaultVerbtoUtilDependency;
|
|
}
|
|
|
|
return dependency.Trim();
|
|
}
|
|
|
|
private static string NormalizeIOSDebuggerPodVersion(string version)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(version))
|
|
{
|
|
return DefaultIOSDebuggerPodVersion;
|
|
}
|
|
|
|
return version.Trim();
|
|
}
|
|
|
|
private static string GetVersionFromDependency(string dependency)
|
|
{
|
|
dependency = NormalizeDependency(dependency);
|
|
var parts = dependency.Split(':');
|
|
return parts.Length >= 3 ? parts[parts.Length - 1] : string.Empty;
|
|
}
|
|
|
|
private static IEnumerable<string> GetResolverOutputPaths(string repositoryRoot)
|
|
{
|
|
var root = ResolveRepositoryRoot(repositoryRoot);
|
|
if (string.IsNullOrEmpty(root))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
yield return Path.Combine(root, "ProjectSettings", "AndroidResolverDependencies.xml");
|
|
yield return Path.Combine(root, "Assets", "Plugins", "Android", "mainTemplate.gradle");
|
|
yield return Path.Combine(root, "Assets", "Plugins", "Android", "mainTemplate.gradle.backup");
|
|
yield return Path.Combine(root, "Assets", "Plugins", "Android", "settingsTemplate.gradle");
|
|
}
|
|
|
|
private static string GetSettingsPath(string profileName, string repositoryRoot)
|
|
{
|
|
var root = ResolveRepositoryRoot(repositoryRoot);
|
|
if (string.IsNullOrEmpty(root))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return Path.Combine(root, BuildConfigsFolder, $"{SanitizeProfileName(profileName)}{SettingsFileSuffix}");
|
|
}
|
|
|
|
private static string ResolveRepositoryRoot(string repositoryRoot)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(repositoryRoot))
|
|
{
|
|
return Path.GetFullPath(repositoryRoot);
|
|
}
|
|
|
|
var dataPath = Application.dataPath;
|
|
var parent = Directory.GetParent(dataPath);
|
|
return parent == null ? string.Empty : parent.FullName;
|
|
}
|
|
|
|
private static string SanitizeProfileName(string profileName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(profileName))
|
|
{
|
|
return "default";
|
|
}
|
|
|
|
var invalidChars = Path.GetInvalidFileNameChars();
|
|
var chars = profileName.ToCharArray();
|
|
for (var i = 0; i < chars.Length; i++)
|
|
{
|
|
if (Array.IndexOf(invalidChars, chars[i]) >= 0)
|
|
{
|
|
chars[i] = '_';
|
|
}
|
|
}
|
|
|
|
return new string(chars);
|
|
}
|
|
}
|
|
|
|
#if UNITY_IOS || UNITY_IPHONE
|
|
internal static class ToponIOSDebuggerDependencyPostProcessor
|
|
{
|
|
private const string Tag = "[TopOn Build]";
|
|
|
|
[PostProcessBuild(int.MaxValue)]
|
|
public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
|
|
{
|
|
if (buildTarget != BuildTarget.iOS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var settings = ToponBuildSettingsStore.GetActiveForCurrentBuild();
|
|
try
|
|
{
|
|
ProcessExportedProject(buildPath, settings);
|
|
}
|
|
finally
|
|
{
|
|
ToponBuildSettingsStore.ClearActiveBuildSettings();
|
|
}
|
|
}
|
|
|
|
public static void ProcessExportedProject(string buildPath, ToponBuildSettings settings)
|
|
{
|
|
if (settings == null)
|
|
{
|
|
settings = ToponBuildSettingsStore.CreateDefault();
|
|
}
|
|
|
|
var podfilePath = Path.Combine(buildPath, "Podfile");
|
|
if (!File.Exists(podfilePath))
|
|
{
|
|
if (settings.enableIOSDebuggerUI)
|
|
{
|
|
Debug.LogWarning($"{Tag} Podfile not found, cannot add {ToponBuildSettingsStore.IOSDebuggerPodName}: {podfilePath}");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var content = File.ReadAllText(podfilePath);
|
|
var updated = RewritePodfile(content, settings);
|
|
if (!string.Equals(content, updated, StringComparison.Ordinal))
|
|
{
|
|
File.WriteAllText(podfilePath, updated);
|
|
}
|
|
|
|
if (settings.enableIOSDebuggerUI)
|
|
{
|
|
Debug.Log($"{Tag} iOS DebugUI pod enabled: {ToponBuildSettingsStore.IOSDebuggerPodName} {ToponBuildSettingsStore.GetIOSDebuggerPodVersion(settings)}.");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"{Tag} iOS DebugUI pod disabled for this build.");
|
|
}
|
|
}
|
|
|
|
private static string RewritePodfile(string content, ToponBuildSettings settings)
|
|
{
|
|
var newline = content.Contains("\r\n") ? "\r\n" : "\n";
|
|
var lines = new List<string>(content.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'));
|
|
|
|
for (var i = lines.Count - 1; i >= 0; i--)
|
|
{
|
|
if (IsDebuggerPodLine(lines[i]))
|
|
{
|
|
lines.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
if (settings.enableIOSDebuggerUI)
|
|
{
|
|
InsertDebuggerPod(lines, ToponBuildSettingsStore.GetIOSDebuggerPodVersion(settings));
|
|
}
|
|
|
|
return string.Join(newline, lines);
|
|
}
|
|
|
|
private static bool IsDebuggerPodLine(string line)
|
|
{
|
|
return Regex.IsMatch(line ?? string.Empty,
|
|
@"^\s*pod\s+['""]" + ToponBuildSettingsStore.IOSDebuggerPodName + @"['""]",
|
|
RegexOptions.CultureInvariant);
|
|
}
|
|
|
|
private static void InsertDebuggerPod(List<string> lines, string version)
|
|
{
|
|
var targetRange = FindTargetRange(lines, "UnityFramework");
|
|
if (targetRange.start < 0)
|
|
{
|
|
targetRange = FindTargetRange(lines, "Unity-iPhone");
|
|
}
|
|
|
|
if (targetRange.start >= 0)
|
|
{
|
|
var indent = ResolvePodIndent(lines, targetRange);
|
|
var podLine = $"{indent}pod '{ToponBuildSettingsStore.IOSDebuggerPodName}', '{version}'";
|
|
lines.Insert(ResolvePodInsertIndex(lines, targetRange), podLine);
|
|
return;
|
|
}
|
|
|
|
if (lines.Count > 0 && !string.IsNullOrWhiteSpace(lines[lines.Count - 1]))
|
|
{
|
|
lines.Add(string.Empty);
|
|
}
|
|
|
|
lines.Add("target 'UnityFramework' do");
|
|
lines.Add($" pod '{ToponBuildSettingsStore.IOSDebuggerPodName}', '{version}'");
|
|
lines.Add("end");
|
|
}
|
|
|
|
private static (int start, int end) FindTargetRange(List<string> lines, string targetName)
|
|
{
|
|
var targetPattern = new Regex(@"^\s*target\s+['""]" + Regex.Escape(targetName) + @"['""]\s+do\b",
|
|
RegexOptions.CultureInvariant);
|
|
|
|
for (var i = 0; i < lines.Count; i++)
|
|
{
|
|
if (!targetPattern.IsMatch(lines[i] ?? string.Empty))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var depth = 0;
|
|
for (var j = i; j < lines.Count; j++)
|
|
{
|
|
var line = StripComment(lines[j]);
|
|
if (Regex.IsMatch(line, @"\bdo\b", RegexOptions.CultureInvariant))
|
|
{
|
|
depth++;
|
|
}
|
|
|
|
if (Regex.IsMatch(line, @"^\s*end\s*$", RegexOptions.CultureInvariant))
|
|
{
|
|
depth--;
|
|
if (depth <= 0)
|
|
{
|
|
return (i, j);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (i, lines.Count);
|
|
}
|
|
|
|
return (-1, -1);
|
|
}
|
|
|
|
private static int ResolvePodInsertIndex(List<string> lines, (int start, int end) targetRange)
|
|
{
|
|
var insertIndex = targetRange.start + 1;
|
|
for (var i = targetRange.start + 1; i < targetRange.end && i < lines.Count; i++)
|
|
{
|
|
if (Regex.IsMatch(lines[i] ?? string.Empty, @"^\s*pod\s+", RegexOptions.CultureInvariant))
|
|
{
|
|
insertIndex = i + 1;
|
|
}
|
|
}
|
|
|
|
return insertIndex;
|
|
}
|
|
|
|
private static string ResolvePodIndent(List<string> lines, (int start, int end) targetRange)
|
|
{
|
|
for (var i = targetRange.start + 1; i < targetRange.end && i < lines.Count; i++)
|
|
{
|
|
var match = Regex.Match(lines[i] ?? string.Empty, @"^(\s*)pod\s+", RegexOptions.CultureInvariant);
|
|
if (match.Success)
|
|
{
|
|
return match.Groups[1].Value;
|
|
}
|
|
}
|
|
|
|
var targetIndent = Regex.Match(lines[targetRange.start] ?? string.Empty, @"^(\s*)", RegexOptions.CultureInvariant).Value;
|
|
return targetIndent + " ";
|
|
}
|
|
|
|
private static string StripComment(string line)
|
|
{
|
|
if (string.IsNullOrEmpty(line))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var commentIndex = line.IndexOf('#');
|
|
return commentIndex >= 0 ? line.Substring(0, commentIndex) : line;
|
|
}
|
|
}
|
|
#endif
|
|
}
|