diff --git a/Assets/Editor/PostBuildAdbInstall.cs b/Assets/Editor/PostBuildAdbInstall.cs new file mode 100644 index 0000000000..5569cadbee --- /dev/null +++ b/Assets/Editor/PostBuildAdbInstall.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using Debug = UnityEngine.Debug; + +public class PostBuildAdbInstall : IPostprocessBuildWithReport +{ + private const string PrefKey = "PerfectWorld/AutoAdbInstallAfterAndroidBuild"; + + public int callbackOrder => 200; + + [MenuItem("Tools/Perfect World/Auto install APK after build (toggle)")] + private static void ToggleAutoInstall() + { + bool v = !EditorPrefs.GetBool(PrefKey, true); + EditorPrefs.SetBool(PrefKey, v); + Debug.Log("[AdbInstall] Auto install after Android build: " + (v ? "ON" : "OFF")); + } + + [MenuItem("Tools/Perfect World/Auto install APK after build (toggle)", true)] + private static bool ToggleAutoInstallValidate() + { + UnityEditor.Menu.SetChecked("Tools/Perfect World/Auto install APK after build (toggle)", EditorPrefs.GetBool(PrefKey, true)); + return true; + } + + [MenuItem("Tools/Perfect World/Install APK to device (choose file)")] + private static void InstallApkFromPicker() + { + string p = EditorUtility.OpenFilePanel("Pick APK", "", "apk"); + if (string.IsNullOrEmpty(p)) + return; + RunAdbInstall(p); + } + + public void OnPostprocessBuild(BuildReport report) + { + if (report.summary.platform != BuildTarget.Android) + return; + if (report.summary.result != BuildResult.Succeeded) + return; + if (!EditorPrefs.GetBool(PrefKey, true)) + return; + + string outputPath = report.summary.outputPath; + string apkPath = ResolveApkPath(outputPath); + if (string.IsNullOrEmpty(apkPath)) + { + if (!string.IsNullOrEmpty(outputPath) && + outputPath.EndsWith(".aab", StringComparison.OrdinalIgnoreCase) && + File.Exists(outputPath)) + { + Debug.LogWarning( + "[AdbInstall] Output is .aab; adb install needs an APK. Disable App Bundle for dev, or use menu to install a built APK."); + } + else + { + Debug.LogWarning("[AdbInstall] No .apk found. Build output: " + outputPath); + } + return; + } + + RunAdbInstall(apkPath); + } + + private static string ResolveApkPath(string outputPath) + { + if (string.IsNullOrEmpty(outputPath)) + return null; + + if (File.Exists(outputPath) && outputPath.EndsWith(".apk", StringComparison.OrdinalIgnoreCase)) + return outputPath; + + if (File.Exists(outputPath) && outputPath.EndsWith(".aab", StringComparison.OrdinalIgnoreCase)) + return null; + + if (Directory.Exists(outputPath)) + { + var apks = new List(Directory.GetFiles(outputPath, "*.apk", SearchOption.AllDirectories)); + if (apks.Count == 0) + return null; + apks.Sort((a, b) => File.GetLastWriteTimeUtc(b).CompareTo(File.GetLastWriteTimeUtc(a))); + return apks[0]; + } + + return null; + } + + private static void RunAdbInstall(string apkPath) + { + string adb = FindAdbExecutable(); + if (string.IsNullOrEmpty(adb)) + { + Debug.LogError( + "[AdbInstall] adb.exe not found. Install Android Build Support (SDK) in Unity Hub, or set ANDROID_HOME, or add platform-tools to PATH. Editor: " + (EditorApplication.applicationPath ?? "(null)")); + return; + } + + string serial = GetSingleDeviceOrFirst(adb, out string listErr); + if (serial == null) + { + if (!string.IsNullOrEmpty(listErr)) + Debug.LogWarning("[AdbInstall] " + listErr); + return; + } + + string argPrefix = string.IsNullOrEmpty(serial) ? "" : ("-s " + serial + " "); + string args = argPrefix + "install -r \"" + apkPath + "\""; + + var psi = new ProcessStartInfo(adb, args) + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + + try + { + using (var p = Process.Start(psi)) + { + if (p == null) + { + Debug.LogError("[AdbInstall] Failed to start adb."); + return; + } + string std = p.StandardOutput.ReadToEnd(); + string err = p.StandardError.ReadToEnd(); + p.WaitForExit(120000); + if (p.ExitCode == 0) + Debug.Log("[AdbInstall] Installed: " + apkPath + "\n" + std + err); + else + Debug.LogError("[AdbInstall] Install failed (exit " + p.ExitCode + "): " + apkPath + "\n" + std + err); + } + } + catch (Exception ex) + { + Debug.LogError("[AdbInstall] " + ex.Message); + } + } + + private static string GetSingleDeviceOrFirst(string adb, out string error) + { + error = null; + var psi = new ProcessStartInfo(adb, "devices") + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + string stdout; + try + { + using (var p = Process.Start(psi)) + { + if (p == null) + { + error = "Could not run adb devices."; + return null; + } + stdout = p.StandardOutput.ReadToEnd(); + p.WaitForExit(10000); + } + } + catch (Exception ex) + { + error = ex.Message; + return null; + } + + var onlines = new List(); + var lines = stdout.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < lines.Length; i++) + { + if (i == 0 && lines[i].IndexOf("List of devices", StringComparison.OrdinalIgnoreCase) >= 0) + continue; + int tab = lines[i].IndexOf('\t'); + if (tab < 0) + continue; + string id = lines[i].Substring(0, tab).Trim(); + string st = lines[i].Substring(tab + 1).Trim(); + if (st.Equals("device", StringComparison.OrdinalIgnoreCase)) + onlines.Add(id); + } + + if (onlines.Count == 0) + { + error = "No Android device in state \"device\" (enable USB debugging, cable, allow PC)."; + return null; + } + if (onlines.Count == 1) + return ""; + Debug.LogWarning("[AdbInstall] Multiple devices - installing to: " + onlines[0]); + return onlines[0]; + } + + private static string FindAdbExecutable() + { + foreach (string root in EnumerateAndroidSdkRoots()) + { + string a = TryAdbInSdk(root); + if (!string.IsNullOrEmpty(a)) + return a; + } + + var pathvar = Environment.GetEnvironmentVariable("PATH"); + if (!string.IsNullOrEmpty(pathvar)) + { + foreach (var part in pathvar.Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var t = part.Trim(); + if (t.Length > 1 && t[0] == '"' && t[t.Length - 1] == '"') + t = t.Substring(1, t.Length - 2); + var candidate = Path.Combine(t, "adb.exe"); + if (File.Exists(candidate)) + return candidate; + } + } + + return null; + } + + private static IEnumerable EnumerateAndroidSdkRoots() + { + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + void Add(string p) + { + if (string.IsNullOrEmpty(p)) return; + p = p.Trim(); + if (p.Length == 0) return; + seen.Add(p); + } + + Add(Environment.GetEnvironmentVariable("ANDROID_HOME")); + Add(Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT")); + string ext = GetUnityAndroidSdkRootPathFromSettings(); + if (!string.IsNullOrEmpty(ext)) + Add(ext); + if (EditorPrefs.HasKey("AndroidSdkRoot")) + Add(EditorPrefs.GetString("AndroidSdkRoot", "")); + + try + { + string editorExe = EditorApplication.applicationPath; + if (!string.IsNullOrEmpty(editorExe)) + { + string editorDir = Path.GetDirectoryName(editorExe); + if (!string.IsNullOrEmpty(editorDir)) + Add(Path.Combine(editorDir, "Data", "PlaybackEngines", "AndroidPlayer", "SDK")); + } + } + catch { } + + Add(Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "AppData", "Local", "Android", "Sdk")); + + return seen; + } + + private static string GetUnityAndroidSdkRootPathFromSettings() + { + try + { + var t = Type.GetType("UnityEditor.Android.AndroidExternalToolsSettings, UnityEditor.Android.EModule"); + if (t == null) return null; + var p = t.GetProperty("sdkRootPath", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); + if (p == null) return null; + var s = p.GetValue(null) as string; + if (string.IsNullOrEmpty(s)) return null; + if (Directory.Exists(s)) return s; + } + catch { } + return null; + } + + private static string TryAdbInSdk(string sdkRoot) + { + if (string.IsNullOrEmpty(sdkRoot) || !Directory.Exists(sdkRoot)) + return null; + var adb = Path.Combine(sdkRoot, "platform-tools", "adb.exe"); + if (File.Exists(adb)) + return adb; + adb = Path.Combine(sdkRoot, "adb.exe"); + if (File.Exists(adb)) + return adb; + return null; + } +} \ No newline at end of file diff --git a/Assets/Editor/PostBuildAdbInstall.cs.meta b/Assets/Editor/PostBuildAdbInstall.cs.meta new file mode 100644 index 0000000000..be1f42da7c --- /dev/null +++ b/Assets/Editor/PostBuildAdbInstall.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 44ebaf0d93aaaaa4e82846358850f836 \ No newline at end of file diff --git a/Assets/Unity.VisualScripting.Generated/VisualScripting.Core.meta b/Assets/Unity.VisualScripting.Generated/VisualScripting.Core.meta new file mode 100644 index 0000000000..9591f18de5 --- /dev/null +++ b/Assets/Unity.VisualScripting.Generated/VisualScripting.Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4bc52f1ad066409458b0dd00add32cc1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: