using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using UnityEditor; using UnityEditor.Build.Reporting; // Сборка APK одной кнопкой (05.11): Tools -> Bogatyri -> Собрать APK. // Файл: C:\MVP1\Builds\Bogatyri_<ветка>_v<версия>_<дата>.apk; сцены -- из списка сборки (первая -- стартовая). // Итог сборки (успех, размер, первые ошибки) пишется в Builds\last_build.txt. // Сборку можно запустить и файлом-запросом Builds\build_request.txt (так её запускает Claude): Unity заметит // его в течение пары секунд, удалит и начнёт сборку. [InitializeOnLoad] public static class ApkBuilder { const string Folder = "Builds"; const string RequestFile = "Builds/build_request.txt"; const string ResultFile = "Builds/last_build.txt"; static double nextCheck; static ApkBuilder() { EditorApplication.update += Poll; } static void Poll() { if (EditorApplication.timeSinceStartup < nextCheck) return; nextCheck = EditorApplication.timeSinceStartup + 2; if (!File.Exists(RequestFile) || BuildPipeline.isBuildingPlayer || EditorApplication.isCompiling || EditorApplication.isPlaying) return; File.Delete(RequestFile); EditorApplication.delayCall += Build; } [MenuItem("Tools/Bogatyri/Собрать APK")] public static void Build() { AndroidBuildFixes.EnsureAsciiGradleHome(); Directory.CreateDirectory(Folder); string branch = Git("rev-parse --abbrev-ref HEAD").Replace('/', '-'); if (branch.Length == 0) branch = "local"; string file = $"{Folder}/Bogatyri_{branch}_v{PlayerSettings.bundleVersion}_{DateTime.Now:yyyyMMdd_HHmm}.apk"; var scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(); File.WriteAllText(ResultFile, $"Сборка идёт: {DateTime.Now:dd.MM.yyyy HH:mm}\n{file}\n"); BuildReport report; try { EditorUserBuildSettings.buildAppBundle = false; // обычный APK report = BuildPipeline.BuildPlayer(new BuildPlayerOptions { scenes = scenes, locationPathName = file, target = BuildTarget.Android, options = BuildOptions.None }); } catch (Exception e) { File.WriteAllText(ResultFile, $"ОШИБКА {DateTime.Now:dd.MM.yyyy HH:mm}\n{e}\n"); throw; } var s = report.summary; var text = new StringBuilder(); text.AppendLine($"{(s.result == BuildResult.Succeeded ? "ГОТОВО" : "ОШИБКА")} {DateTime.Now:dd.MM.yyyy HH:mm} результат: {s.result}"); text.AppendLine($"Файл: {Path.GetFullPath(file)}"); if (s.result == BuildResult.Succeeded && File.Exists(file)) text.AppendLine($"Размер: {new FileInfo(file).Length / 1048576.0:0.0} МБ"); text.AppendLine($"Версия: {PlayerSettings.bundleVersion}, код {PlayerSettings.Android.bundleVersionCode}, ветка {branch} {Git("rev-parse --short HEAD")}"); text.AppendLine($"Сцены: {string.Join(", ", scenes)}"); text.AppendLine($"Время: {s.totalTime}, ошибок: {s.totalErrors}, предупреждений: {s.totalWarnings}"); int shown = 0; foreach (var step in report.steps) foreach (var m in step.messages) if (m.type == UnityEngine.LogType.Error || m.type == UnityEngine.LogType.Exception) { if (shown++ >= 15) break; string msg = m.content.Length > 1500 ? m.content.Substring(0, 1500) + " ..." : m.content; text.AppendLine("--- " + msg); } File.WriteAllText(ResultFile, text.ToString()); UnityEngine.Debug.Log("[СБОРКА] " + text.ToString().Split('\n')[0] + " -> " + file); if (s.result == BuildResult.Succeeded) EditorUtility.RevealInFinder(file); } static string Git(string args) { try { var p = Process.Start(new ProcessStartInfo("git", args) { RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, WorkingDirectory = Directory.GetCurrentDirectory() }); string o = p.StandardOutput.ReadToEnd().Trim(); p.WaitForExit(5000); return o; } catch { return ""; } } }