123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- using System;
- using System.Diagnostics;
- using System.IO;
- using System.Threading.Tasks;
- using SingularityGroup.HotReload.Editor.Semver;
- using Debug = UnityEngine.Debug;
- namespace SingularityGroup.HotReload.Editor.Cli {
- class OsxCliController : ICliController {
- Process process;
- public string BinaryFileName => "HotReload.app.zip";
- public string PlatformName => "osx-x64";
- public bool CanOpenInBackground => false;
- /// In MacOS 13 Ventura, our app cannot launch a terminal window.
- /// We use a custom app that launches HotReload server and shows it's output (just like a terminal would).
- // Including MacOS 12 Monterey as well so I can dogfood it -Troy
- private static bool UseCustomConsoleApp() => MacOSVersion.Value.Major >= 12;
- // dont use static because null comparison on SemVersion is broken
- private static readonly Lazy<SemVersion> MacOSVersion = new Lazy<SemVersion>(() => {
- //UnityHelper.OperatingSystem; // in Unity 2018 it returns 10.16 on monterey (no idea why)
- //Environment.OSVersion returns unix version like 21.x
- var startinfo = new ProcessStartInfo {
- FileName = "/usr/bin/sw_vers",
- Arguments = "-productVersion",
- UseShellExecute = false,
- RedirectStandardOutput = true,
- CreateNoWindow = true,
- };
- var process = Process.Start(startinfo);
- string osVersion = process.StandardOutput.ReadToEnd().Trim();
- SemVersion macosVersion;
- if (SemVersion.TryParse(osVersion, out macosVersion)) {
- return macosVersion;
- }
- // should never happen
- Log.Warning("Failed to detect MacOS version, if Hot Reload fails to start, please contact support.");
- return SemVersion.None;
- });
- public async Task Start(StartArgs args) {
- // Unzip the .app.zip to temp folder .app
- var appExecutablePath = $"{args.executableTargetDir}/HotReload.app/Contents/MacOS/HotReload";
- var cliExecutablePath = $"{args.executableTargetDir}/HotReload.app/Contents/Resources/CodePatcherCLI";
-
- // ensure running on threadpool
- await ThreadUtility.SwitchToThreadPool();
- // executableTargetDir is versioned, so only need to extract once.
- if (!File.Exists(appExecutablePath)) {
- try {
- // delete only the extracted app folder (must not delete downloaded zip which is in same folder)
- Directory.Delete(args.executableTargetDir + "/HotReload.app", true);
- } catch (IOException) {
- // ignore directory not found
- }
- Directory.CreateDirectory(args.executableTargetDir);
- UnzipMacOsPackage($"{args.executableTargetDir}/{BinaryFileName}", args.executableTargetDir + "/");
- }
- try {
- // Always stop first because rarely it has happened that the server process was still running after custom console closed.
- // Note: this will also stop Hot Reload started by other Unity projects.
- await Stop();
- } catch {
- // ignored
- }
- if (UseCustomConsoleApp()) {
- await StartCustomConsole(args, appExecutablePath);
- } else {
- await StartTerminal(args, cliExecutablePath);
- }
- }
- public Task StartCustomConsole(StartArgs args, string executablePath) {
- process = Process.Start(new ProcessStartInfo {
- // Path to the HotReload.app
- FileName = executablePath,
- Arguments = args.cliArguments,
- UseShellExecute = false,
- });
- return Task.CompletedTask;
- }
- public Task StartTerminal(StartArgs args, string executablePath) {
- var pidFilePath = CliUtils.GetPidFilePath(args.hotreloadTempDir);
- // To run in a Terminal window (so you can see compiler logs), we must put the arguments into a script file
- // and run the script in Terminal. Terminal.app does not forward the arguments passed to it via `open --args`.
- // *.command files are opened with the user's default terminal app.
- var executableScriptPath = Path.Combine(Path.GetTempPath(), "Start_HotReloadServer.command");
- // You don't need to copy the cli executable on mac
- // omit hashbang line, let shell use the default interpreter (easier than detecting your default shell beforehand)
- File.WriteAllText(executableScriptPath, $"echo $$ > \"{pidFilePath}\"" +
- $"\ncd \"{Environment.CurrentDirectory}\"" + // set cwd because 'open' launches script with $HOME as cwd.
- $"\n\"{executablePath}\" {args.cliArguments} || read");
- CliUtils.Chmod(executableScriptPath); // make it executable
- CliUtils.Chmod(executablePath); // make it executable
- Directory.CreateDirectory(args.hotreloadTempDir);
- Directory.CreateDirectory(args.executableTargetDir);
- Directory.CreateDirectory(args.cliTempDir);
-
- process = Process.Start(new ProcessStartInfo {
- FileName = "open",
- Arguments = $"{(args.createNoWindow ? "-gj" : "")} '{executableScriptPath}'",
- UseShellExecute = true,
- });
- if (process.WaitForExit(1000)) {
- if (process.ExitCode != 0) {
- Log.Warning("Failed to the run the start server command. ExitCode={0}\nFilepath: {1}", process.ExitCode, executableScriptPath);
- }
- }
- else {
- process.EnableRaisingEvents = true;
- process.Exited += (_, __) => {
- if (process.ExitCode != 0) {
- Log.Warning("Failed to the run the start server command. ExitCode={0}\nFilepath: {1}", process.ExitCode, executableScriptPath);
- }
- };
- }
- return Task.CompletedTask;
- }
- public async Task Stop() {
- // kill HotReload server process (on mac it has different pid to the window which started it)
- await RequestHelper.KillServer();
- // process.CloseMainWindow throws if proc already exited.
- // We rely on the pid file for killing the trampoline script (in-case script is just starting and HotReload server not running yet)
- process = null;
- CliUtils.KillLastKnownHotReloadProcess();
- }
- static void UnzipMacOsPackage(string zipPath, string unzippedFolderPath) {
- //Log.Info("UnzipMacOsPackage called with {0}\n workingDirectory = {1}", zipPath, unzippedFolderPath);
- if (!zipPath.EndsWith(".zip")) {
- throw new ArgumentException($"Expected to end with .zip, but it was: {zipPath}", nameof(zipPath));
- }
- if (!File.Exists(zipPath)) {
- throw new ArgumentException($"zip file not found {zipPath}", nameof(zipPath));
- }
- var processStartInfo = new ProcessStartInfo {
- FileName = "unzip",
- Arguments = $"-o \"{zipPath}\"",
- WorkingDirectory = unzippedFolderPath, // unzip extracts to working directory by default
- UseShellExecute = true,
- CreateNoWindow = true
- };
- Process process = Process.Start(processStartInfo);
- process.WaitForExit();
- if (process.ExitCode != 0) {
- throw new Exception($"unzip failed with ExitCode {process.ExitCode}");
- }
- //Log.Info($"did unzip to {unzippedFolderPath}");
- // Move the .app folder to unzippedFolderPath
-
- // find the .app directory which is now inside unzippedFolderPath directory
- var foundDirs = Directory.GetDirectories(unzippedFolderPath, "*.app", SearchOption.AllDirectories);
- var done = false;
- var destDir = unzippedFolderPath + "HotReload.app";
- foreach (var dir in foundDirs) {
- if (dir.EndsWith(".app")) {
- done = true;
- if (dir == destDir) {
- // already in the right place
- break;
- }
- Directory.Move(dir, destDir);
- //Log.Info("Moved to " + destDir);
- break;
- }
- }
- if (!done) {
- throw new Exception("Failed to find .app directory and move it to " + destDir);
- }
- //Log.Info($"did unzip to {unzippedFolderPath}");
- }
- }
- }
|