#if UNITY_ANDROID using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using UnityEditor.Android; using UnityEngine; namespace Topon_Adapter.Editor { public sealed class ToponAndroidDebuggerDependencyPostProcessor : IPostGenerateGradleAndroidProject { private const string Tag = "[TopOn Build]"; private const string DebuggerDependency = "com.anythink.sdk:debugger-ui:+"; private const string DebuggerDependencyMarker = "com.anythink.sdk:debugger-ui"; private const string VerbtoDependencyMarker = "com.verbto.tools:util"; private const string DebuggerRepositoryUrl = "https://jfrog.anythinktech.com/artifactory/debugger"; private const string DepsStart = "// TopOn Debugger UI Dependencies Start"; private const string DepsEnd = "// TopOn Debugger UI Dependencies End"; private const string VerbtoDepsStart = "// TopOn Verbto Util Dependency Start"; private const string VerbtoDepsEnd = "// TopOn Verbto Util Dependency End"; private const string ReposStart = "// TopOn Debugger UI Repository Start"; private const string ReposEnd = "// TopOn Debugger UI Repository End"; public int callbackOrder => int.MaxValue; public void OnPostGenerateGradleAndroidProject(string path) { var settings = ToponBuildSettingsStore.GetActiveForCurrentBuild(); try { ProcessGeneratedProject(path, settings); } finally { ToponBuildSettingsStore.ClearActiveBuildSettings(); } } public static void ProcessGeneratedProject(string path, ToponBuildSettings settings) { if (settings == null) { settings = ToponBuildSettingsStore.CreateDefault(); } var gradleRoot = GetGradleRoot(path); var gradleFiles = new[] { Path.Combine(path, "build.gradle"), Path.Combine(gradleRoot, "launcher", "build.gradle"), Path.Combine(gradleRoot, "build.gradle") }; var forceVerbtoUtilVersion = settings.forceVerbtoUtilVersion; var verbtoDependency = ToponBuildSettingsStore.GetVerbtoUtilDependency(settings); foreach (var gradleFile in gradleFiles) { StripManagedDependenciesFromGradleFile(gradleFile, forceVerbtoUtilVersion); } StripManagedDependenciesFromGradleFile(Path.Combine(gradleRoot, "settings.gradle"), forceVerbtoUtilVersion); if (settings.enableDebuggerUI || forceVerbtoUtilVersion) { InjectRepository(Path.Combine(gradleRoot, "settings.gradle")); InjectRepository(Path.Combine(gradleRoot, "build.gradle")); } if (settings.enableDebuggerUI) { InjectDebuggerDependency(Path.Combine(path, "build.gradle")); Debug.Log($"{Tag} DebugUI dependency enabled for this build."); } else { if (settings.stripResolvedDebuggerArtifacts) { RemoveGeneratedDebuggerArtifacts(gradleRoot); } Debug.Log($"{Tag} DebugUI dependency disabled for this build."); } if (forceVerbtoUtilVersion) { InjectVerbtoUtilDependency(Path.Combine(path, "build.gradle"), verbtoDependency); RemoveGeneratedStaleVerbtoUtilArtifacts(gradleRoot, verbtoDependency); Debug.Log($"{Tag} Verbto util dependency forced to {verbtoDependency}."); } else { Debug.Log($"{Tag} Verbto util dependency is not modified by TopOn build settings."); } } private static string GetGradleRoot(string unityLibraryPath) { #if UNITY_2019_3_OR_NEWER return Path.GetFullPath(Path.Combine(unityLibraryPath, "..")); #else return Path.GetFullPath(unityLibraryPath); #endif } private static void StripManagedDependenciesFromGradleFile(string filePath, bool forceVerbtoUtilVersion) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return; } var content = File.ReadAllText(filePath); var original = content; content = RemoveMarkedBlock(content, DepsStart, DepsEnd); content = RemoveMarkedBlock(content, VerbtoDepsStart, VerbtoDepsEnd); content = RemoveMarkedBlock(content, ReposStart, ReposEnd); content = RemoveLinesContaining(content, DebuggerDependencyMarker); if (forceVerbtoUtilVersion) { content = RemoveLinesContaining(content, VerbtoDependencyMarker); } if (!string.Equals(original, content, StringComparison.Ordinal)) { File.WriteAllText(filePath, content); } } private static void InjectDebuggerDependency(string buildGradlePath) { InjectDependencyBlock(buildGradlePath, DepsStart, DepsEnd, DebuggerDependency); } private static void InjectVerbtoUtilDependency(string buildGradlePath, string dependency) { InjectDependencyBlock(buildGradlePath, VerbtoDepsStart, VerbtoDepsEnd, dependency); } private static void InjectDependencyBlock(string buildGradlePath, string startMarker, string endMarker, string dependency) { if (!File.Exists(buildGradlePath)) { Debug.LogWarning($"{Tag} build.gradle not found: {buildGradlePath}"); return; } if (string.IsNullOrWhiteSpace(dependency)) { Debug.LogWarning($"{Tag} dependency is empty, skip injecting {startMarker}."); return; } var content = File.ReadAllText(buildGradlePath); var block = new StringBuilder(); block.AppendLine(startMarker); block.AppendLine($" implementation '{dependency.Trim()}'"); block.AppendLine($" {endMarker}"); var pattern = new Regex(@"(dependencies\s*\{)"); if (!pattern.IsMatch(content)) { Debug.LogWarning($"{Tag} dependencies block not found: {buildGradlePath}"); return; } content = pattern.Replace(content, match => match.Groups[1].Value + "\n " + block, 1); File.WriteAllText(buildGradlePath, content); } private static void InjectRepository(string gradlePath) { if (!File.Exists(gradlePath)) { return; } var content = File.ReadAllText(gradlePath); if (content.Contains(DebuggerRepositoryUrl)) { return; } var block = $"{ReposStart}\n maven {{ url '{DebuggerRepositoryUrl}' }}\n {ReposEnd}"; Regex pattern; if (Path.GetFileName(gradlePath).Equals("settings.gradle", StringComparison.OrdinalIgnoreCase)) { pattern = new Regex(@"(dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{)"); } else { block = $"{ReposStart}\n maven {{ url '{DebuggerRepositoryUrl}' }}\n {ReposEnd}"; pattern = new Regex(@"(repositories\s*\{)"); } if (!pattern.IsMatch(content)) { return; } content = pattern.Replace(content, match => match.Groups[1].Value + "\n " + block, 1); File.WriteAllText(gradlePath, content); } private static void RemoveGeneratedDebuggerArtifacts(string gradleRoot) { if (string.IsNullOrWhiteSpace(gradleRoot) || !Directory.Exists(gradleRoot)) { return; } var root = Path.GetFullPath(gradleRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); foreach (var path in Directory.GetFiles(root, "*", SearchOption.AllDirectories)) { if (!ToponBuildSettingsStore.IsDebuggerArtifactFileName(Path.GetFileName(path))) { continue; } var fullPath = Path.GetFullPath(path); if (!fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase)) { continue; } try { File.Delete(fullPath); Debug.Log($"{Tag} Removed generated debugger artifact: {fullPath}"); } catch (Exception exception) { Debug.LogWarning($"{Tag} Failed to remove generated debugger artifact: {exception.Message}"); } } } private static void RemoveGeneratedStaleVerbtoUtilArtifacts(string gradleRoot, string expectedDependency) { if (string.IsNullOrWhiteSpace(gradleRoot) || !Directory.Exists(gradleRoot)) { return; } var root = Path.GetFullPath(gradleRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); foreach (var path in Directory.GetFiles(root, "*", SearchOption.AllDirectories)) { var fileName = Path.GetFileName(path); if (!ToponBuildSettingsStore.IsVerbtoUtilArtifactFileName(fileName) || ToponBuildSettingsStore.IsExpectedVerbtoUtilArtifactFileName(fileName, expectedDependency)) { continue; } var fullPath = Path.GetFullPath(path); if (!fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase)) { continue; } try { File.Delete(fullPath); Debug.Log($"{Tag} Removed stale verbto util artifact: {fullPath}"); } catch (Exception exception) { Debug.LogWarning($"{Tag} Failed to remove stale verbto util artifact: {exception.Message}"); } } } private static string RemoveMarkedBlock(string content, string startMarker, string endMarker) { var pattern = new Regex($@"\s*{Regex.Escape(startMarker)}[\s\S]*?{Regex.Escape(endMarker)}\s*"); return pattern.Replace(content, "\n"); } private static string RemoveLinesContaining(string content, params string[] markers) { var lines = content.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'); var kept = new List(); foreach (var line in lines) { var remove = false; foreach (var marker in markers) { if (line.Contains(marker)) { remove = true; break; } } if (!remove) { kept.Add(line); } } return string.Join("\n", kept); } } } #endif