Example #1
0
        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));
                                    }
                                });
                            });
                        }
                    });
Example #5
0
        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));
                    }
                });
            }));
        }