From 65751e4d4df0caaabac9f1ef13dccd400e932347 Mon Sep 17 00:00:00 2001 From: aparajit-pratap Date: Thu, 16 Jul 2020 16:43:08 -0400 Subject: [PATCH] Easily install missing IronPython extension package (#10869) * easily install missing ironpython extension package * code cleanup * code cleanup * code cleanup * fix regressions * minor fix * code cleanup * use event, remove dependency on PythonMigrationViewExtension from WorkspaceDependencyViewExtension * code cleanup * more cleanup * unsubscribe event Co-authored-by: Aaron (Qilong) <173288704@qq.com> --- .../Graph/Workspaces/PackageDependencyInfo.cs | 21 -------- .../Graph/Workspaces/WorkspaceModel.cs | 17 +++++++ src/DynamoCore/Models/DynamoModel.cs | 2 +- .../GraphPythonDependencies.cs | 51 ++++++++++++++++--- .../PythonMigrationViewExtension.cs | 7 +++ .../WorkspaceDependencyView.xaml.cs | 11 ++-- test/DynamoCoreWpfTests/DynamoTestUIBase.cs | 11 ++++ .../PythonMigrationViewExtensionTests.cs | 37 +++++++++++--- .../WorkspaceDependencyViewExtensionTests.cs | 20 +++----- 9 files changed, 125 insertions(+), 52 deletions(-) diff --git a/src/DynamoCore/Graph/Workspaces/PackageDependencyInfo.cs b/src/DynamoCore/Graph/Workspaces/PackageDependencyInfo.cs index e470b244e53..1e1f35730a5 100644 --- a/src/DynamoCore/Graph/Workspaces/PackageDependencyInfo.cs +++ b/src/DynamoCore/Graph/Workspaces/PackageDependencyInfo.cs @@ -252,27 +252,6 @@ public void AddDependent(Guid guid) Nodes.Add(guid); } - /// - /// Add the Guids of a dependent nodes - /// - /// - internal void AddDependents(IEnumerable guids) - { - foreach (var guid in guids) - { - Nodes.Add(guid); - } - } - - /// - /// Remove a dependent node - /// - /// - internal void RemoveDependent(Guid guid) - { - Nodes.Remove(guid); - } - /// /// Checks whether two PackageDependencyInfo instances are equal /// They are equal if their Name and Versions are equal diff --git a/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs b/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs index cd3fed1ca26..8b1d10134f3 100644 --- a/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs +++ b/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs @@ -541,6 +541,23 @@ public HashSet Dependencies } } + /// + /// Event requesting subscribers to return additional package dependencies for + /// current workspace. + /// + internal event Func> RequestPackageDependencies; + + /// + /// Raised when the workspace needs to request for additional package dependencies + /// that can be returned from other subscribers such as view extensions. + /// E.g. The PythonMigrationViewExtension returns additional package dependencies required for Python engines. + /// + /// + internal IEnumerable OnRequestPackageDependencies() + { + return RequestPackageDependencies?.Invoke(); + } + /// /// NodeLibraries that the nodes in this graph depend on /// diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs index 06a1676e8eb..4c9630a79bc 100644 --- a/src/DynamoCore/Models/DynamoModel.cs +++ b/src/DynamoCore/Models/DynamoModel.cs @@ -366,7 +366,7 @@ public WorkspaceModel CurrentWorkspace var old = currentWorkspace; currentWorkspace = value; OnWorkspaceHidden(old); - OnPropertyChanged("CurrentWorkspace"); + OnPropertyChanged(nameof(CurrentWorkspace)); } } diff --git a/src/PythonMigrationViewExtension/GraphPythonDependencies.cs b/src/PythonMigrationViewExtension/GraphPythonDependencies.cs index ab3cfd83cc9..1c830ce72f0 100644 --- a/src/PythonMigrationViewExtension/GraphPythonDependencies.cs +++ b/src/PythonMigrationViewExtension/GraphPythonDependencies.cs @@ -12,6 +12,9 @@ namespace Dynamo.PythonMigration public class GraphPythonDependencies { private ViewLoadedParams ViewLoaded { get; set; } + internal static readonly string PythonPackage = "DSIronPython_Test"; + internal static readonly Version PythonPackageVersion = new Version(1, 0, 7); + // A dictionary to mark Custom Nodes if they have a IronPython dependency or not. internal static Dictionary CustomNodePythonDependencyMap = new Dictionary(); @@ -28,21 +31,39 @@ internal GraphPythonDependencies(ViewLoadedParams viewLoadedParams) this.ViewLoaded = viewLoadedParams; } + private static bool IsIronPythonPackageLoaded() + { + try + { + PythonEngineSelector.Instance.GetEvaluatorInfo(PythonEngineVersion.IronPython2, + out string evaluatorClass, out string evaluationMethod); + if (evaluatorClass == PythonEngineSelector.Instance.IronPythonEvaluatorClass && + evaluationMethod == PythonEngineSelector.Instance.IronPythonEvaluationMethod) + { + return true; + } + } + catch (Exception) + { + return false; + } + return false; + } + internal bool ContainsIronPythonDependencyInCurrentWS() { var containsIronPythonDependency = false; - var workspace = ViewLoaded.CurrentWorkspaceModel; - if (workspace == null) - throw new ArgumentNullException(nameof(workspace)); + var workspace = ViewLoaded.CurrentWorkspaceModel as WorkspaceModel; + + var customNodeManager = ViewLoaded.StartupParams.CustomNodeManager; - if (workspace.Nodes.Any(n => IsIronPythonNode(n))) + if (workspace.Nodes.Any(IsIronPythonNode)) { containsIronPythonDependency = true; } // Check if any of the custom nodes has IronPython dependencies in it. - var customNodeManager = ViewLoaded.StartupParams.CustomNodeManager; var customNodes = workspace.Nodes.OfType(); if (CustomNodesContainIronPythonDependency(customNodes, customNodeManager)) @@ -53,16 +74,32 @@ internal bool ContainsIronPythonDependencyInCurrentWS() return containsIronPythonDependency; } + internal IEnumerable AddPythonPackageDependency() + { + if (!ContainsIronPythonDependencyInCurrentWS()) + return null; + + var packageInfo = new PackageInfo(PythonPackage, PythonPackageVersion); + var packageDependencyInfo = new PackageDependencyInfo(packageInfo); + packageDependencyInfo.State = IsIronPythonPackageLoaded() + ? PackageDependencyState.Loaded + : PackageDependencyState.Missing; + + return new[] {packageDependencyInfo}; + } + // This function returns true, if any of the custom nodes in the input list has an IronPython dependency. // It traverses all CN's in the given list of customNodes and marks them as true in CustomNodePythonDependency, // if the prarent custom node or any of its child custom nodes contain an IronPython dependency. - internal static bool CustomNodesContainIronPythonDependency(IEnumerable customNodes, ICustomNodeManager customNodeManager) + private static bool CustomNodesContainIronPythonDependency(IEnumerable customNodes, ICustomNodeManager customNodeManager) { var containIronPythonDependency = false; foreach (var customNode in customNodes) { - customNodeManager.TryGetFunctionWorkspace(customNode.FunctionSignature, false, out ICustomNodeWorkspaceModel customNodeWS); + if (!customNodeManager.TryGetFunctionWorkspace(customNode.FunctionSignature, false, + out ICustomNodeWorkspaceModel customNodeWS)) + continue; // If a custom node workspace is already checked for IronPython dependencies, // check the CustomNodePythonDependency dictionary instead of processing it again. diff --git a/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs b/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs index 926b121e564..b6393b32416 100644 --- a/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs +++ b/src/PythonMigrationViewExtension/PythonMigrationViewExtension.cs @@ -63,6 +63,7 @@ public void Loaded(ViewLoadedParams p) DynamoViewModel = LoadedParams.DynamoWindow.DataContext as DynamoViewModel; CurrentWorkspace = LoadedParams.CurrentWorkspaceModel as WorkspaceModel; CustomNodeManager = (CustomNodeManager) LoadedParams.StartupParams.CustomNodeManager; + CurrentWorkspace.RequestPackageDependencies += PythonDependencies.AddPythonPackageDependency; Dispatcher = Dispatcher.CurrentDispatcher; DynamoView = LoadedParams.DynamoWindow as DynamoView; @@ -119,6 +120,7 @@ private void UnsubscribeFromDynamoEvents() LoadedParams.CurrentWorkspaceChanged -= OnCurrentWorkspaceChanged; DynamoViewModel.CurrentSpaceViewModel.Model.NodeAdded -= OnNodeAdded; DynamoViewModel.Model.Logger.NotificationLogged -= OnNotificationLogged; + CurrentWorkspace.RequestPackageDependencies -= PythonDependencies.AddPythonPackageDependency; } private void OnNotificationLogged(NotificationMessage obj) @@ -160,8 +162,13 @@ private void OnCurrentWorkspaceChanged(IWorkspaceModel workspace) { NotificationTracker.Remove(CurrentWorkspace.Guid); GraphPythonDependencies.CustomNodePythonDependencyMap.Clear(); + + CurrentWorkspace.RequestPackageDependencies -= PythonDependencies.AddPythonPackageDependency; + CurrentWorkspace = workspace as WorkspaceModel; + CurrentWorkspace.RequestPackageDependencies += PythonDependencies.AddPythonPackageDependency; + if (Configuration.DebugModes.IsEnabled("Python2ObsoleteMode") && !Models.DynamoModel.IsTestMode && PythonDependencies.ContainsIronPythonDependencyInCurrentWS()) diff --git a/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml.cs b/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml.cs index 79b86244292..98dfd533948 100644 --- a/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml.cs +++ b/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml.cs @@ -123,8 +123,12 @@ private void OnWorkspacePropertyChanged(object sender, PropertyChangedEventArgs /// workspace model internal void DependencyRegen(WorkspaceModel ws) { - this.RestartBanner.Visibility = Visibility.Hidden; - var packageDependencies = ws.NodeLibraryDependencies.Where(d => d is PackageDependencyInfo); + RestartBanner.Visibility = Visibility.Hidden; + var packageDependencies = ws.NodeLibraryDependencies.Where(d => d is PackageDependencyInfo).ToList(); + + var pythonPackageDependencies = ws.OnRequestPackageDependencies(); + if (pythonPackageDependencies != null) + packageDependencies.AddRange(pythonPackageDependencies); if (packageDependencies.Any(d => d.State != PackageDependencyState.Loaded)) { @@ -137,7 +141,8 @@ internal void DependencyRegen(WorkspaceModel ws) // If package is set to uninstall state, update the package info foreach (var package in dependencyViewExtension.pmExtension.PackageLoader.LocalPackages.Where(x => x.MarkedForUninstall)) { - (packageDependencies.Where(x => x.Name == package.Name).FirstOrDefault() as PackageDependencyInfo).State = PackageDependencyState.RequiresRestart; + (packageDependencies.FirstOrDefault(x => x.Name == package.Name) as PackageDependencyInfo).State = + PackageDependencyState.RequiresRestart; hasPackageMarkedForUninstall = true; } this.RestartBanner.Visibility = hasPackageMarkedForUninstall ? Visibility.Visible: Visibility.Hidden; diff --git a/test/DynamoCoreWpfTests/DynamoTestUIBase.cs b/test/DynamoCoreWpfTests/DynamoTestUIBase.cs index e9c56d979cb..7e84b82bdfa 100644 --- a/test/DynamoCoreWpfTests/DynamoTestUIBase.cs +++ b/test/DynamoCoreWpfTests/DynamoTestUIBase.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Threading; +using System.Windows; using Dynamo.Configuration; using Dynamo.Controls; using Dynamo.Graph.Nodes; @@ -78,6 +79,16 @@ public virtual void Start() SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); } + protected static void RaiseLoadedEvent(FrameworkElement element) + { + MethodInfo eventMethod = typeof(FrameworkElement).GetMethod("OnLoaded", + BindingFlags.Instance | BindingFlags.NonPublic); + + RoutedEventArgs args = new RoutedEventArgs(FrameworkElement.LoadedEvent); + + eventMethod.Invoke(element, new object[] { args }); + } + /// /// Derived test classes can override this method to provide different configurations. /// diff --git a/test/DynamoCoreWpfTests/ViewExtensions/PythonMigrationViewExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/PythonMigrationViewExtensionTests.cs index 057d2236f4b..923bb7a2cc0 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/PythonMigrationViewExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/PythonMigrationViewExtensionTests.cs @@ -7,10 +7,13 @@ using Dynamo; using Dynamo.Configuration; using Dynamo.Graph.Nodes.CustomNodes; +using Dynamo.Graph.Workspaces; using Dynamo.Models; using Dynamo.PythonMigration; using Dynamo.Utilities; +using Dynamo.Wpf.Extensions; using DynamoCoreWpfTests.Utility; +using GraphLayout; using NUnit.Framework; namespace DynamoCoreWpfTests @@ -18,11 +21,10 @@ namespace DynamoCoreWpfTests class PythonMigrationViewExtensionTests : DynamoTestUIBase { private PythonMigrationViewExtension viewExtension = new PythonMigrationViewExtension(); - + private string CoreTestDirectory { get { return Path.Combine(GetTestDirectory(ExecutingDirectory), "core"); } } private List raisedEvents = new List(); - /// /// This test is created to check if the extension displays a dialog to the user /// when opening a saved dyn file that contains python nodes with their engine set to `IronPython2` @@ -231,7 +233,7 @@ public void CanDetectIronPythonNodesInGraph() /// /// Checks if pressing the `More Information` button on the IronPythonInfoDialog window - /// will open the DocumentaionBrowser ViewExtension. + /// will open the DocumentationBrowser ViewExtension. /// [Test] public void CanOpenDocumentationBrowserWhenMoreInformationIsClicked() @@ -293,13 +295,36 @@ public void CustomNodeContainsIronPythonDependencyTest() var examplePath = Path.Combine(UnitTestBase.TestDirectory, @"core\python", "PythonCustomNodeHomeWorkspace.dyn"); Open(examplePath); - var customNodes = ViewModel.Model.CurrentWorkspace.Nodes.OfType(); - var customNodeManager = ViewModel.Model.CustomNodeManager; + var pythonMigration = View.viewExtensionManager.ViewExtensions + .Where(x => x.Name == viewExtension.Name) + .Select(x => x) + .First() as PythonMigrationViewExtension; - var result = GraphPythonDependencies.CustomNodesContainIronPythonDependency(customNodes, customNodeManager); + var result = pythonMigration.PythonDependencies.ContainsIronPythonDependencyInCurrentWS(); Assert.IsTrue(result); DispatcherUtil.DoEvents(); } + + [Test] + public void IronPythonPackageLoadedTest() + { + RaiseLoadedEvent(View); + + Open(Path.Combine(CoreTestDirectory, @"python\python.dyn")); + + var loadedParams = new ViewLoadedParams(View, ViewModel); + viewExtension.Loaded(loadedParams); + + var currentWorkspace = ViewModel.Model.CurrentWorkspace; + + var pkgDependencyInfo = currentWorkspace.OnRequestPackageDependencies().FirstOrDefault(); + + Assert.IsTrue(pkgDependencyInfo != null); + Assert.AreEqual(PackageDependencyState.Loaded, pkgDependencyInfo.State); + Assert.AreEqual(GraphPythonDependencies.PythonPackage, pkgDependencyInfo.Name); + Assert.AreEqual(GraphPythonDependencies.PythonPackageVersion, pkgDependencyInfo.Version); + } + } } diff --git a/test/DynamoCoreWpfTests/ViewExtensions/WorkspaceDependencyViewExtensionTests.cs b/test/DynamoCoreWpfTests/ViewExtensions/WorkspaceDependencyViewExtensionTests.cs index ec4e933aeb9..e1381d0e3a4 100644 --- a/test/DynamoCoreWpfTests/ViewExtensions/WorkspaceDependencyViewExtensionTests.cs +++ b/test/DynamoCoreWpfTests/ViewExtensions/WorkspaceDependencyViewExtensionTests.cs @@ -48,7 +48,8 @@ public void RestartBannerDefaultStateTest() var loadedParams = new ViewLoadedParams(View, ViewModel); viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType().FirstOrDefault(); - viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams); + viewExtension.Loaded(loadedParams); + var CurrentWorkspace = ViewModel.Model.CurrentWorkspace; viewExtension.DependencyView.DependencyRegen(CurrentWorkspace); // Restart banner should not display by default @@ -67,10 +68,9 @@ public void DownloadSpecifiedVersionOfPackageTest() var loadedParams = new ViewLoadedParams(View, ViewModel); viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType().FirstOrDefault(); - viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams); + viewExtension.Loaded(loadedParams); var CurrentWorkspace = ViewModel.Model.CurrentWorkspace; - var info = CurrentWorkspace.NodeLibraryDependencies.Find(x => x.Name == "Dynamo Samples"); // This is equivalent to uninstall the package var package = viewExtension.pmExtension.PackageLoader.LocalPackages.Where(x => x.Name == "Dynamo Samples").FirstOrDefault(); @@ -160,7 +160,7 @@ public void DependencyRegenCrashingDynamoTest() var loadedParams = new ViewLoadedParams(View, ViewModel); viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType().FirstOrDefault(); - viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams); + viewExtension.Loaded(loadedParams); var homeWorkspaceModel = ViewModel.Model.Workspaces.OfType().FirstOrDefault(); @@ -185,7 +185,7 @@ public void KeepInstalledVersionOfPackageTest() var loadedParams = new ViewLoadedParams(View, ViewModel); viewExtension.pmExtension = this.Model.ExtensionManager.Extensions.OfType().FirstOrDefault(); - viewExtension.DependencyView = new WorkspaceDependencyView(viewExtension, loadedParams); + viewExtension.Loaded(loadedParams); var CurrentWorkspace = ViewModel.Model.CurrentWorkspace; var info = CurrentWorkspace.NodeLibraryDependencies.Find(x => x.Name == "Dynamo Samples"); @@ -239,14 +239,6 @@ public void TestPropertiesWithCodeInIt() } - public static void RaiseLoadedEvent(FrameworkElement element) - { - MethodInfo eventMethod = typeof(FrameworkElement).GetMethod("OnLoaded", - BindingFlags.Instance | BindingFlags.NonPublic); - - RoutedEventArgs args = new RoutedEventArgs(FrameworkElement.LoadedEvent); - - eventMethod.Invoke(element, new object[] { args }); - } + } }