private void OnOutOfSync(Lifetime lifetime) { if (myPluginInstallations.Contains(mySolution.SolutionFilePath)) { return; } // avoid displaying Notification multiple times on each AppDomain.Reload in Unity myPluginInstallations.Add(mySolution.SolutionFilePath); var appVersion = myUnityVersion.ActualVersionForSolution.Value; if (appVersion < new Version(2019, 2)) { var entry = myBoundSettingsStore.Schema.GetScalarEntry((UnitySettings s) => s.InstallUnity3DRiderPlugin); var isEnabled = myBoundSettingsStore.GetValueProperty <bool>(lifetime, entry, null).Value; if (!isEnabled) { myFrontendBackendHost.Do(model => model.OnEditorModelOutOfSync()); } } else { var notification = new NotificationModel("Advanced Unity integration is unavailable", $"Make sure Rider {myHostProductInfo.VersionMarketingString} is set as the External Editor in Unity preferences.", true, RdNotificationEntryType.WARN, new List <NotificationHyperlink>()); mySolution.Locks.ExecuteOrQueue(lifetime, "OutOfSyncModels.Notify", () => myNotificationsModel.Notification(notification)); } }
private void ShowNotification() => NotificationsModel.Notification(new NotificationModel( "Could not execute T4 file", "Execution is already running", true, RdNotificationEntryType.ERROR, new List <NotificationHyperlink>() ));
private void CreateProtocols(FileSystemPath protocolInstancePath) { if (!protocolInstancePath.ExistsFile) { return; } List <ProtocolInstance> protocolInstanceList; try { protocolInstanceList = ProtocolInstance.FromJson(protocolInstancePath.ReadAllText2().Text); } catch (Exception e) { myLogger.Warn($"Unable to parse {protocolInstancePath}" + Environment.NewLine + e); return; } var protocolInstance = protocolInstanceList?.SingleOrDefault(a => a.SolutionName == mySolution.SolutionFilePath.NameWithoutExtension); if (protocolInstance == null) { return; } myLogger.Info($"EditorPlugin protocol port {protocolInstance.Port} for Solution: {protocolInstance.SolutionName}."); try { var lifetime = mySessionLifetimes.Next(); myLogger.Info("Create protocol..."); myLogger.Info("Creating SocketWire with port = {0}", protocolInstance.Port); var wire = new SocketWire.Client(lifetime, myDispatcher, protocolInstance.Port, "UnityClient"); var protocol = new Protocol("UnityEditorPlugin", new Serializers(), new Identities(IdKind.Client), myDispatcher, wire, lifetime); protocol.ThrowErrorOnOutOfSyncModels = false; protocol.OutOfSyncModels.AdviseOnce(lifetime, e => { if (myPluginInstallations.Contains(mySolution.SolutionFilePath)) { return; } myPluginInstallations.Add(mySolution.SolutionFilePath); // avoid displaying Notification multiple times on each AppDomain.Reload in Unity var appVersion = myUnityVersion.GetActualVersionForSolution(); if (appVersion < new Version(2019, 2)) { var entry = myBoundSettingsStore.Schema.GetScalarEntry((UnitySettings s) => s.InstallUnity3DRiderPlugin); var isEnabled = myBoundSettingsStore.GetValueProperty <bool>(lifetime, entry, null).Value; if (!isEnabled) { myHost.PerformModelAction(model => model.OnEditorModelOutOfSync()); } } else { var notification = new NotificationModel("Advanced Unity integration is unavailable", $"Please update External Editor to {myHostProductInfo.VersionMarketingString} in Unity Preferences.", true, RdNotificationEntryType.WARN); mySolution.Locks.ExecuteOrQueue(lifetime, "OutOfSyncModels.Notify", () => myNotificationsModel.Notification(notification)); } }); wire.Connected.WhenTrue(lifetime, lf => { myLogger.Info("WireConnected."); var editor = new EditorPluginModel(lf, protocol); editor.IsBackendConnected.Set(rdVoid => true); if (PlatformUtil.RuntimePlatform == PlatformUtil.Platform.Windows) { var frontendProcess = Process.GetCurrentProcess().GetParent(); // RiderProcessId is not used on non-Windows, but this line gives bad warning in the log if (frontendProcess != null) { editor.RiderProcessId.SetValue(frontendProcess.Id); } } myHost.PerformModelAction(m => m.SessionInitialized.Value = true); SubscribeToLogs(lf, editor); SubscribeToOpenFile(editor); editor.Play.Advise(lf, b => myHost.PerformModelAction(rd => rd.Play.SetValue(b))); editor.Pause.Advise(lf, b => myHost.PerformModelAction(rd => rd.Pause.SetValue(b))); editor.ClearOnPlay.Advise(lf, time => myHost.PerformModelAction(rd => rd.ClearOnPlay(time))); editor.UnityProcessId.View(lf, (_, pid) => myHost.PerformModelAction(t => t.UnityProcessId.Set(pid))); // I have split this into groups, because want to use async api for finding reference and pass them via groups to Unity myHost.PerformModelAction(t => t.ShowFileInUnity.Advise(lf, v => editor.ShowFileInUnity.Fire(v))); myHost.PerformModelAction(t => t.ShowPreferences.Advise(lf, v => { editor.ShowPreferences.Fire(); })); editor.EditorLogPath.Advise(lifetime, s => myHost.PerformModelAction(a => a.EditorLogPath.SetValue(s))); editor.PlayerLogPath.Advise(lifetime, s => myHost.PerformModelAction(a => a.PlayerLogPath.SetValue(s))); // Note that these are late-init properties. Once set, they are always set and do not allow nulls. // This means that if/when the Unity <-> Backend protocol closes, they still retain the last value // they had - so the front end will retain the log and application paths of the just-closed editor. // Opening a new editor instance will reconnect and push a new value through to the front end editor.UnityApplicationData.Advise(lifetime, s => myHost.PerformModelAction(a => { var version = UnityVersion.Parse(s.ApplicationVersion); a.UnityApplicationData.SetValue(new UnityApplicationData(s.ApplicationPath, s.ApplicationContentsPath, s.ApplicationVersion, UnityVersion.RequiresRiderPackage(version))); })); editor.ScriptCompilationDuringPlay.Advise(lifetime, s => myHost.PerformModelAction(a => a.ScriptCompilationDuringPlay.Set(ConvertToScriptCompilationEnum(s)))); myHost.PerformModelAction(rd => { rd.GenerateUIElementsSchema.Set((l, u) => editor.GenerateUIElementsSchema.Start(l, u).ToRdTask(l)); }); TrackActivity(editor, lf); if (!myComponentLifetime.IsTerminated) { myLocks.ExecuteOrQueueEx(myComponentLifetime, "setModel", () => { UnityModel.SetValue(editor); }); } lf.AddAction(() => { if (!myComponentLifetime.IsTerminated) { myLocks.ExecuteOrQueueEx(myComponentLifetime, "clearModel", () => { myLogger.Info("Wire disconnected."); myHost.PerformModelAction(m => m.SessionInitialized.Value = false); UnityModel.SetValue(null); }); } }); }); } catch (Exception ex) { myLogger.Error(ex); } }
private void RefreshAndRunTask(IUnitTestRun run, TaskCompletionSource <bool> tcs, Lifetime taskLifetime) { var cancellationTs = run.GetData(ourCancellationTokenSourceKey); myLogger.Verbose("Before calling Refresh."); Refresh(run.Lifetime, cancellationTs.NotNull().Token).GetAwaiter().OnCompleted(() => { // KS: Can't use run.Lifetime for ExecuteOrQueueEx here and in all similar places: run.Lifetime is terminated when // Unit Test Session is closed from UI without cancelling the run. This will leave task completion source in running state forever. mySolution.Locks.ExecuteOrQueueEx(myLifetime, "Check compilation", () => { if (!run.Lifetime.IsAlive || cancellationTs.IsCancellationRequested) { tcs.SetCanceled(); return; } if (myEditorProtocol.UnityModel.Value == null) { myLogger.Verbose("Unity Editor connection unavailable."); tcs.SetException(new Exception("Unity Editor connection unavailable.")); return; } var task = myEditorProtocol.UnityModel.Value.GetCompilationResult.Start(Unit.Instance); task.Result.AdviseNotNull(myLifetime, result => { if (!run.Lifetime.IsAlive || cancellationTs.IsCancellationRequested) { tcs.SetCanceled(); } else if (!result.Result) { tcs.SetException(new Exception("There are errors during compilation in Unity.")); mySolution.Locks.ExecuteOrQueueEx(run.Lifetime, "RunViaUnityEditorStrategy compilation failed", () => { var notification = new NotificationModel("Compilation failed", "Script compilation in Unity failed, so tests were not started.", true, RdNotificationEntryType.INFO); myNotificationsModel.Notification(notification); }); myUnityHost.PerformModelAction(model => model.ActivateUnityLogView()); } else { var launch = SetupLaunch(run); mySolution.Locks.ExecuteOrQueueEx(myLifetime, "ExecuteRunUT", () => { if (!run.Lifetime.IsAlive || cancellationTs.IsCancellationRequested) { tcs.SetCanceled(); return; } if (myEditorProtocol.UnityModel.Value == null) { tcs.SetException(new Exception("Unity Editor connection unavailable.")); return; } myEditorProtocol.UnityModel.ViewNotNull(taskLifetime, (lt, model) => { // recreate UnitTestLaunch in case of AppDomain.Reload, which is the case with PlayMode tests model.UnitTestLaunch.SetValue(launch); SubscribeResults(run, lt, tcs, launch); }); myUnityProcessId.When(taskLifetime, (int?)null, _ => tcs.TrySetException(new Exception("Unity Editor has been closed."))); var rdTask = myEditorProtocol.UnityModel.Value.RunUnitTestLaunch.Start(Unit.Instance); rdTask?.Result.Advise(taskLifetime, res => { myLogger.Trace($"RunUnitTestLaunch result = {res.Result}"); if (!res.Result) { var defaultMessage = "Failed to start tests in Unity."; var isCoverage = run.HostController.HostId != WellKnownHostProvidersIds.DebugProviderId && run.HostController.HostId != WellKnownHostProvidersIds.RunProviderId; if (myPackageValidator.HasNonCompatiblePackagesCombination(isCoverage, out var message)) { defaultMessage = $"{defaultMessage} {message}"; } if (myEditorProtocol.UnityModel.Value.UnitTestLaunch.Value.TestMode == TestMode.Play) { if (!myPackageValidator.CanRunPlayModeTests(out var playMessage)) { defaultMessage = $"{defaultMessage} {playMessage}"; } } tcs.TrySetException(new Exception(defaultMessage)); } }); }); } });
private async Task <bool> InstallPlugin(Lifetime lifetime, UnrealPluginInstallInfo.InstallDescription installDescription, FileSystemPath uprojectFile, IProperty <double> progressProperty, double range) { using var def = new LifetimeDefinition(); var ZIP_STEP = 0.1 * range; var PATCH_STEP = 0.1 * range; var BUILD_STEP = 0.6 * range; var REFRESH_STEP = 0.1 * range; var pluginRootFolder = installDescription.UnrealPluginRootFolder; var editorPluginPathFile = myPathsProvider.PathToPackedPlugin; var pluginTmpDir = FileSystemDefinition.CreateTemporaryDirectory(null, TMP_PREFIX); def.Lifetime.OnTermination(() => { pluginTmpDir.Delete(); }); try { ZipFile.ExtractToDirectory(editorPluginPathFile.FullPath, pluginTmpDir.FullPath); progressProperty.Value += ZIP_STEP; } catch (Exception exception) { myLogger.Warn(exception, $"[UnrealLink]: Couldn't extract {editorPluginPathFile} to {pluginTmpDir}"); const string unzipFailTitle = "Failed to unzip new RiderLink plugin"; var unzipFailText = $"Failed to unzip new version of RiderLink ({editorPluginPathFile.FullPath}) to user folder ({pluginTmpDir.FullPath})\n" + "Try restarting Rider in administrative mode"; myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(unzipFailTitle, ContentType.Error)); myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(unzipFailText, ContentType.Error)); return(false); } lifetime.ToCancellationToken().ThrowIfCancellationRequested(); var upluginFile = UnrealPluginDetector.GetPathToUpluginFile(pluginTmpDir); var pluginBuildOutput = FileSystemDefinition.CreateTemporaryDirectory(null, TMP_PREFIX); def.Lifetime.OnTermination(() => { pluginBuildOutput.Delete(); }); var buildProgress = progressProperty.Value; var isPluginBuilt = await BuildPlugin(lifetime, upluginFile, pluginBuildOutput, uprojectFile, value => progressProperty.SetValue(buildProgress + value *BUILD_STEP)); if (!isPluginBuilt) { myLogger.Warn($"Failed to build RiderLink for any available project"); const string failedBuildText = "Failed to build RiderLink plugin"; myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(failedBuildText, ContentType.Error)); return(false); } progressProperty.Value = buildProgress + BUILD_STEP; lifetime.ToCancellationToken().ThrowIfCancellationRequested(); if (!PatchUpluginFileAfterInstallation(pluginBuildOutput)) { const string failedToPatch = "Failed to patch RiderLink.uplugin"; var failedPatchText = "Failed to set `EnableByDefault` to true in RiderLink.uplugin\n" + "You need to manually enable RiderLink in UnrealEditor"; myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(failedToPatch, ContentType.Normal)); myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(failedPatchText, ContentType.Normal)); } progressProperty.Value += PATCH_STEP; lifetime.ToCancellationToken().ThrowIfCancellationRequested(); pluginRootFolder.CreateDirectory().DeleteChildren(); pluginBuildOutput.Copy(pluginRootFolder); progressProperty.Value += REFRESH_STEP; installDescription.IsPluginAvailable = true; installDescription.PluginVersion = myPathsProvider.CurrentPluginVersion; const string title = "RiderLink plugin installed"; var text = $"RiderLink plugin was installed to: {pluginRootFolder}"; myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(title, ContentType.Normal)); myUnrealHost.myModel.RiderLinkInstallMessage(new InstallMessage(text, ContentType.Normal)); var notification = new NotificationModel(title, text, true, RdNotificationEntryType.INFO, new List <NotificationHyperlink>()); mySolution.Locks.ExecuteOrQueue(Lifetime, "UnrealLink.InstallPlugin", () => { myNotificationsModel.Notification(notification); }); mySolution.Locks.ExecuteOrQueueReadLock(Lifetime, "UnrealLink.RegenerateProjectFiles", () => { var cppUe4SolutionDetector = mySolution.GetComponent <CppUE4SolutionDetector>(); if (cppUe4SolutionDetector.SupportRiderProjectModel != CppUE4ProjectModelSupportMode.UprojectOpened) { RegenerateProjectFiles(uprojectFile); } }); return(true); }
private void Install(UnityPluginDetector.InstallationInfo installationInfo, bool force) { if (!force) { if (!installationInfo.ShouldInstallPlugin) { Assertion.Assert(false, "Should not be here if installation is not required."); return; } if (myPluginInstallations.Contains(mySolution.SolutionFilePath)) { myLogger.Verbose("Installation already done."); return; } } myLogger.Info("Installing Rider Unity editor plugin: {0}", installationInfo.InstallReason); if (!TryCopyFiles(installationInfo, out var installedPath)) { myLogger.Warn("Plugin was not installed"); } else { string userTitle; string userMessage; switch (installationInfo.InstallReason) { case UnityPluginDetector.InstallReason.FreshInstall: userTitle = "Unity Editor plugin installed"; userMessage = $@"Please switch to Unity Editor to load the plugin. Rider plugin v{myCurrentVersion} can be found at: {installedPath.MakeRelativeTo(mySolution.SolutionDirectory)}."; break; case UnityPluginDetector.InstallReason.Update: userTitle = "Unity Editor plugin updated"; userMessage = $@"Please switch to the Unity Editor to reload the plugin. Rider plugin v{myCurrentVersion} can be found at: {installedPath.MakeRelativeTo(mySolution.SolutionDirectory)}."; break; case UnityPluginDetector.InstallReason.ForceUpdateForDebug: userTitle = "Unity Editor plugin updated (debug build)"; userMessage = $@"Please switch to the Unity Editor to reload the plugin. Rider plugin v{myCurrentVersion} can be found at: {installedPath.MakeRelativeTo(mySolution.SolutionDirectory)}."; break; case UnityPluginDetector.InstallReason.UpToDate: userTitle = "Unity Editor plugin updated (up to date)"; userMessage = $@"Please switch to the Unity Editor to reload the plugin. Rider plugin v{myCurrentVersion} can be found at: {installedPath.MakeRelativeTo(mySolution.SolutionDirectory)}."; break; default: myLogger.Error("Unexpected install reason: {0}", installationInfo.InstallReason); return; } myLogger.Info(userTitle); var notification = new NotificationModel(userTitle, userMessage, true, RdNotificationEntryType.INFO); myShellLocks.ExecuteOrQueueEx(myLifetime, "UnityPluginInstaller.Notify", () => myNotifications.Notification(notification)); } }
public MonoInstallTrigger(Lifetime lifetime, ILogger logger, ISolutionLoadTasksScheduler scheduler, ISolution solution, UnitySolutionTracker unitySolutionTracker, UnityHost host, NotificationsModel notificationsModel) { if (PlatformUtil.RuntimePlatform == PlatformUtil.Platform.Windows) { return; } scheduler.EnqueueTask(new SolutionLoadTask("Check mono runtime", SolutionLoadTaskKinds.AfterDone, () => { if (!unitySolutionTracker.IsUnityGeneratedProject.Value) { return; } if (!HasModernUnityProjects(solution)) { return; } solution.Locks.Tasks.Queue(lifetime, () => { var wellKnownMonoRuntimes = MonoRuntimeDetector.DetectWellKnownMonoRuntimes(); var installedValidMono = wellKnownMonoRuntimes .Any(runtime => { var parsedVersion = string.Empty; try { parsedVersion = ProcessOutputUtil.ExtractMonoVersion(runtime.ExePath); } catch (Exception e) { logger.Warn(e); } // if we fail to parse version - consider it is old if (Version.TryParse(parsedVersion, out var version)) { return(version >= new Version(5, 16)); // mono 5.16+ supports C# 7.3 } logger.Warn("Failed to parse ProcessOutputUtil.ExtractMonoVersion output."); return(false); }); if (installedValidMono) { return; } if (PlatformUtil.RuntimePlatform == PlatformUtil.Platform.MacOsX) { solution.Locks.ExecuteOrQueue(lifetime, "Show install mono dialog", () => { host.PerformModelAction(model => model.ShowInstallMonoDialog()); }); } else if (PlatformUtil.RuntimePlatform == PlatformUtil.Platform.Linux) { var notification = new NotificationModel("Mono 5.16+ is required", "<html>Project requires new Mono with MSBuild for new C# language features support.<br>" + RiderContextNotificationHelper.MakeOpenSettingsLink( WellKnownSettingPages.Environment, "Install the latest Mono") + ".<br>" + "If a Mono runtime is available in a non-standard location, please " + RiderContextNotificationHelper.MakeOpenSettingsLink( WellKnownSettingPages.ToolsetAndBuild, "specify the custom runtime location in settings") + ".</html>" , true, RdNotificationEntryType.WARN); solution.Locks.ExecuteOrQueue(lifetime, "MonoInstallTrigger.Notify", () => notificationsModel.Notification(notification)); } }); })); }