diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs index 4e7783c878a..ce1fb334231 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Loader; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Forms; @@ -2218,7 +2219,7 @@ private void AddDllFile(string filename) { // we're not sure if this is a managed assembly or not // we try to load it, if it fails - we add it as an additional file - var result = PackageLoader.TryLoadFrom(filename, out Assembly assem); + var result = PackageLoader.TryLoadFrom(AssemblyLoadContext.Default, filename, out Assembly assem); if (result) { var assemName = assem.GetName().Name; diff --git a/src/DynamoPackages/Package.cs b/src/DynamoPackages/Package.cs index 196dac48be9..ee72507afc9 100644 --- a/src/DynamoPackages/Package.cs +++ b/src/DynamoPackages/Package.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Loader; using Dynamo.Core; using Dynamo.Exceptions; using Dynamo.Graph.Nodes.CustomNodes; @@ -228,6 +229,7 @@ internal IEnumerable NodeLibraries /// internal bool RequiresSignedEntryPoints { get; set; } + internal AssemblyLoadContext AssemblyLoadContext { get; set; } = AssemblyLoadContext.Default; #endregion public Package(string directory, string name, string versionName, string license) @@ -374,7 +376,7 @@ internal IEnumerable EnumerateAndLoadAssembliesInBinDirectory() if (shouldLoadFile) { // dll files may be un-managed, skip those - var result = PackageLoader.TryLoadFrom(assemFile.FullName, out assem); + var result = PackageLoader.TryLoadFrom(AssemblyLoadContext, assemFile.FullName, out assem); if (result) { // IsNodeLibrary may fail, we store the warnings here and then show @@ -586,7 +588,7 @@ internal void UninstallCore(CustomNodeManager customNodeManager, PackageLoader p if (BuiltInPackage) { LoadState.SetAsUnloaded(); - Analytics.TrackEvent(Actions.BuiltInPackageConflict, Categories.PackageManagerOperations, $"{Name } {versionName} set unloaded"); + Analytics.TrackEvent(Actions.BuiltInPackageConflict, Categories.PackageManagerOperations, $"{Name} {versionName} set unloaded"); RaisePropertyChanged(nameof(LoadState)); diff --git a/src/DynamoPackages/PackageAssemblyLoadContext.cs b/src/DynamoPackages/PackageAssemblyLoadContext.cs new file mode 100644 index 00000000000..bfd31cfec0e --- /dev/null +++ b/src/DynamoPackages/PackageAssemblyLoadContext.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace Dynamo.PackageManager +{ + internal class PkgAssemblyLoadContext : AssemblyLoadContext + { + private readonly string RootDir; + private IEnumerable pkgAssemblies = null; + public PkgAssemblyLoadContext(string name, string pkgRoot, bool unloadable = true) : base(name, unloadable) + { + this.RootDir = pkgRoot; + } + + protected override Assembly Load(AssemblyName assemblyName) + { + pkgAssemblies ??= new DirectoryInfo(RootDir).EnumerateFiles("*.dll", new EnumerationOptions() { RecurseSubdirectories = true }); + + var targetAssemName = assemblyName.Name + ".dll"; + var targetAssembly = pkgAssemblies.FirstOrDefault(x => x.Name == targetAssemName); + if (targetAssembly != null) + { + return LoadFromAssemblyPath(targetAssembly.FullName); + } + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + pkgAssemblies ??= new DirectoryInfo(RootDir).EnumerateFiles("*.dll", new EnumerationOptions() { RecurseSubdirectories = true }); + + var targetAssemName = unmanagedDllName + ".dll"; + var targetAssembly = pkgAssemblies.FirstOrDefault(x => x.Name == targetAssemName); + if (targetAssembly != null) + { + return NativeLibrary.Load(targetAssembly.FullName); + } + return IntPtr.Zero; + } + } +} diff --git a/src/DynamoPackages/PackageLoader.cs b/src/DynamoPackages/PackageLoader.cs index e880fe2549c..c4aafa2a22a 100644 --- a/src/DynamoPackages/PackageLoader.cs +++ b/src/DynamoPackages/PackageLoader.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Loader; using Dynamo.Core; using Dynamo.Exceptions; using Dynamo.Extensions; @@ -37,6 +37,52 @@ public class PackageLoader : LogSourceBase internal event Func RequestLoadExtension; internal event Action RequestAddExtension; + #region Package Isolation Feature Flags + // Remove when we fully enable package isolation + private HashSet packagesToIsolate = null; + private HashSet packagesToNotIsolate = null; + + internal bool CanIsolatePackage(string package) + { + static void populateFlags(string fflagKey, ref HashSet pkgs) + { + string flags = DynamoModel.FeatureFlags?.CheckFeatureFlag(fflagKey, string.Empty); + if (!string.IsNullOrEmpty(flags)) + { + foreach (var x in flags.Split(",")) + { + pkgs.Add(x); + } + } + } + + if (packagesToIsolate == null) + { + packagesToIsolate = []; + populateFlags("IsolatePackages", ref packagesToIsolate); + } + + if (packagesToNotIsolate == null) + { + packagesToNotIsolate = []; + populateFlags("DoNotIsolatePackages", ref packagesToNotIsolate); + } + + // NotIsolate has the highest priority. + if (packagesToNotIsolate.Contains(package) || packagesToNotIsolate.Contains("All")) + { + return false; + } + + if (packagesToIsolate.Contains(package) || packagesToIsolate.Contains("All")) + { + return true; + } + + return false; + } + #endregion + /// /// This event is raised when a package is first added to the list of packages this package loader is loading. /// This event occurs before the package is fully loaded. @@ -201,7 +247,12 @@ private void TryLoadPackageIntoLibrary(Package package) { var dynamoVersion = VersionUtilities.PartialParse(DynamoModel.Version); - List blockedAssemblies = new List(); + if (CanIsolatePackage(package.Name)) + { + package.AssemblyLoadContext = new PkgAssemblyLoadContext(package.Name + "@" + package.VersionName, package.RootDirectory); + } + + List blockedAssemblies = []; // Try to load node libraries from all assemblies foreach (var assem in package.EnumerateAndLoadAssembliesInBinDirectory()) { @@ -742,14 +793,15 @@ internal static void CleanSharedPublishLoadContext(MetadataLoadContext mlc) /// /// Attempt to load a managed assembly in to LoadFrom context. /// + /// /// The filename of a DLL /// out Assembly - the passed value does not matter and will only be set if loading succeeds /// Returns true if success, false if BadImageFormatException (i.e. not a managed assembly) - internal static bool TryLoadFrom(string filename, out Assembly assem) + internal static bool TryLoadFrom(AssemblyLoadContext alc, string filename, out Assembly assem) { try { - assem = Assembly.LoadFrom(filename); + assem = alc.LoadFromAssemblyPath(filename); return true; } catch (FileLoadException e) diff --git a/src/DynamoUtilities/DynamoFeatureFlagsManager.cs b/src/DynamoUtilities/DynamoFeatureFlagsManager.cs index 32be9566ca6..bc041138f35 100644 --- a/src/DynamoUtilities/DynamoFeatureFlagsManager.cs +++ b/src/DynamoUtilities/DynamoFeatureFlagsManager.cs @@ -2,16 +2,17 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; -using System.Reflection; using System.Threading; -using System.Threading.Tasks; namespace DynamoUtilities { + internal interface IFFlags + { + internal T CheckFeatureFlag(DynamoFeatureFlagsManager mgr, string featureFlagKey, T defaultval); + } /// /// A wrapper around the DynamoFeatureFlags CLI tool. @@ -20,9 +21,21 @@ namespace DynamoUtilities /// internal class DynamoFeatureFlagsManager : CLIWrapper { + // Utility class that supports mocking during tests + class FFlags : IFFlags + { + T IFFlags.CheckFeatureFlag(DynamoFeatureFlagsManager mgr, string featureFlagKey, T defaultval) + { + return mgr.CheckFeatureFlagInternal(featureFlagKey, defaultval); + } + } + + // Useful for mocking in tests + internal IFFlags flags { get; set; } = new FFlags(); private string relativePath = Path.Combine("DynamoFeatureFlags", "DynamoFeatureFlags.exe"); private Dictionary AllFlagsCache { get; set; }//TODO lock is likely overkill. private SynchronizationContext syncContext; + private readonly bool testmode = false; internal static event Action FlagsRetrieved; //TODO(DYN-6464)- remove this field!. @@ -43,8 +56,10 @@ internal class DynamoFeatureFlagsManager : CLIWrapper /// context used for raising FlagRetrieved event. /// will not contact feature flag service in testmode, will respond with defaults. internal DynamoFeatureFlagsManager(string userkey, SynchronizationContext syncContext, bool testmode=false) - { + { this.syncContext = syncContext; + this.testmode = testmode; + //dont pass userkey arg if null/empty var userkeyarg = $"-u {userkey}"; var testmodearg = string.Empty; @@ -62,7 +77,6 @@ internal DynamoFeatureFlagsManager(string userkey, SynchronizationContext syncCo internal void CacheAllFlags() { - //wait for response var dataFromCLI = GetData(featureFlagTimeoutMs); //convert from json string to dictionary. @@ -91,6 +105,13 @@ internal void CacheAllFlags() /// Currently the flag and default val MUST be a bool or string. /// internal T CheckFeatureFlag(string featureFlagKey, T defaultval) + { + // with testmode = true, the call goes through an interface so that we can intercept it with Mock + // with testmode = false, the call simply goes to the CheckFeatureFlagInternal + return testmode ? flags.CheckFeatureFlag(this, featureFlagKey, defaultval) : CheckFeatureFlagInternal(featureFlagKey, defaultval); + } + + private T CheckFeatureFlagInternal(string featureFlagKey, T defaultval) { if(!(defaultval is bool || defaultval is string)){ throw new ArgumentException("unsupported flag type", defaultval.GetType().ToString()); diff --git a/src/DynamoUtilities/Properties/AssemblyInfo.cs b/src/DynamoUtilities/Properties/AssemblyInfo.cs index 181c011d33e..6ff91d38d74 100644 --- a/src/DynamoUtilities/Properties/AssemblyInfo.cs +++ b/src/DynamoUtilities/Properties/AssemblyInfo.cs @@ -29,3 +29,6 @@ [assembly: InternalsVisibleTo("LibraryViewExtensionWebView2")] [assembly: InternalsVisibleTo("Notifications")] [assembly: InternalsVisibleTo("SystemTestServices")] +[assembly: InternalsVisibleTo("PackageManagerTests")] +//DynamicProxyGenAssembly2 is used by Mock to allow stubbing internal interfaces +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/test/Libraries/PackageManagerTests/PackageLoaderTests.cs b/test/Libraries/PackageManagerTests/PackageLoaderTests.cs index 427453a62d0..93400322194 100644 --- a/test/Libraries/PackageManagerTests/PackageLoaderTests.cs +++ b/test/Libraries/PackageManagerTests/PackageLoaderTests.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Packaging; using System.Linq; using System.Reflection; +using System.Runtime.Loader; using System.Threading; using Dynamo.Configuration; using Dynamo.Core; @@ -12,7 +14,10 @@ using Dynamo.Graph.Nodes.CustomNodes; using Dynamo.Graph.Workspaces; using Dynamo.Interfaces; +using Dynamo.Models; +using Dynamo.Scheduler; using Dynamo.Search.SearchElements; +using DynamoUtilities; using Moq; using NUnit.Framework; @@ -644,7 +649,74 @@ public void PlacingCustomNodeInstanceFromPackageRetainsCorrectPackageInfoState() loader.RequestLoadNodeLibrary -= libraryLoader.LoadLibraryAndSuppressZTSearchImport; } - + [Test] + public void LoadPackagesInAssemblyIsolation() + { + var ff = new Mock(); + DynamoModel.FeatureFlags.flags = ff.Object; + ff.Setup(x => x.CheckFeatureFlag(DynamoModel.FeatureFlags, "IsolatePackages", "")).Returns(() => "Package1,Package2,Package"); + ff.Setup(x => x.CheckFeatureFlag(DynamoModel.FeatureFlags, "DoNotIsolatePackages", "")).Returns(() => "Package"); + + // Needed for FeatureFlags + Assert.IsTrue(DynamoModel.IsTestMode); + Assert.AreEqual("Package1,Package2,Package", DynamoModel.FeatureFlags.CheckFeatureFlag("IsolatePackages", "")); + Assert.AreEqual("Package", DynamoModel.FeatureFlags.CheckFeatureFlag("DoNotIsolatePackages", "")); + + var pathManager = new Mock(); + pathManager.SetupGet(x => x.PackagesDirectories).Returns( + () => new List { PackagesDirectory }); + + var loader = new PackageLoader(pathManager.Object); + var libraryLoader = new ExtensionLibraryLoader(CurrentDynamoModel); + + loader.PackagesLoaded += libraryLoader.LoadPackages; + + var packageDirectory = Path.Combine(TestDirectory, "testAssemblyIsolation", "Package1"); + var packageDirectory2 = Path.Combine(TestDirectory, "testAssemblyIsolation", "Package2"); + var packageDirectory3 = Path.Combine(TestDirectory, "pkgs", "Package"); + var package1 = Package.FromDirectory(packageDirectory, CurrentDynamoModel.Logger); + var package2 = Package.FromDirectory(packageDirectory2, CurrentDynamoModel.Logger); + var package3 = Package.FromDirectory(packageDirectory3, CurrentDynamoModel.Logger); + loader.LoadPackages([package1, package2, package3]); + + loader.PackagesLoaded -= libraryLoader.LoadPackages; + + // 2 packages loaded as expected + var expectedLoadedPackageNum = 0; + foreach (var pkg in loader.LocalPackages) + { + if (pkg.Name == "Package1" || pkg.Name == "Package2" || pkg.Name == "Package") + { + expectedLoadedPackageNum++; + } + } + Assert.AreEqual(3, expectedLoadedPackageNum); + + string openPath = Path.Combine(TestDirectory, @"testAssemblyIsolation\graph.dyn"); + RunModel(openPath); + + var expectedVersions = 0; + var pkgLoadContexts = AssemblyLoadContext.All.Where(x => x.Name.Equals("Package1@1.0.0") || x.Name.Equals("Package2@1.0.0")).ToList(); + foreach (var pkg in pkgLoadContexts) + { + var dep = pkg.Assemblies.FirstOrDefault(x => x.GetName().Name == "Newtonsoft.Json"); + Assert.IsNotNull(dep); + + var ver = dep.GetName().Version.ToString(); + // Expected both versions of Newtonsoft to be loaded + if (ver == "13.0.0.0" || ver == "8.0.0.0") + { + expectedVersions++; + } + } + Assert.AreEqual(2, expectedVersions); + + // Make sure the "DoNotIsolatePackages" fflag puts the pacakge in the default load context(i.e does not isolate it) + var contexts = AssemblyLoadContext.All.Where(l => l.Assemblies.FirstOrDefault(x => x.GetName().Name == "Package") != null).ToList(); + Assert.AreEqual(1, contexts.Count); + Assert.True(contexts[0] == AssemblyLoadContext.Default); + } + [Test] public void LoadingConflictingCustomNodePackageDoesNotGetLoaded() { diff --git a/test/testAssemblyIsolation/Package1/bin/Newtonsoft.Json.dll b/test/testAssemblyIsolation/Package1/bin/Newtonsoft.Json.dll new file mode 100644 index 00000000000..3af21d596d2 Binary files /dev/null and b/test/testAssemblyIsolation/Package1/bin/Newtonsoft.Json.dll differ diff --git a/test/testAssemblyIsolation/Package1/bin/TestAssemblyIsolation1.dll b/test/testAssemblyIsolation/Package1/bin/TestAssemblyIsolation1.dll new file mode 100644 index 00000000000..df78c2de366 Binary files /dev/null and b/test/testAssemblyIsolation/Package1/bin/TestAssemblyIsolation1.dll differ diff --git a/test/testAssemblyIsolation/Package1/pkg.json b/test/testAssemblyIsolation/Package1/pkg.json new file mode 100644 index 00000000000..2af5e65950e --- /dev/null +++ b/test/testAssemblyIsolation/Package1/pkg.json @@ -0,0 +1 @@ +{"license":"","file_hash":null,"name":"Package1","version":"1.0.0","description":"original package","group":"","keywords":null,"dependencies":[],"contents":"","engine_version":"2.1.0.7840","engine":"dynamo","engine_metadata":"","site_url":"","repository_url":"","contains_binaries":true,"node_libraries":["TestAssemblyIsolation1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"]} \ No newline at end of file diff --git a/test/testAssemblyIsolation/Package2/bin/Newtonsoft.Json.dll b/test/testAssemblyIsolation/Package2/bin/Newtonsoft.Json.dll new file mode 100644 index 00000000000..4d42dd9c5fe Binary files /dev/null and b/test/testAssemblyIsolation/Package2/bin/Newtonsoft.Json.dll differ diff --git a/test/testAssemblyIsolation/Package2/bin/TestAssemblyIsolation2.dll b/test/testAssemblyIsolation/Package2/bin/TestAssemblyIsolation2.dll new file mode 100644 index 00000000000..0e18af7538e Binary files /dev/null and b/test/testAssemblyIsolation/Package2/bin/TestAssemblyIsolation2.dll differ diff --git a/test/testAssemblyIsolation/Package2/pkg.json b/test/testAssemblyIsolation/Package2/pkg.json new file mode 100644 index 00000000000..f23ff8d8ba9 --- /dev/null +++ b/test/testAssemblyIsolation/Package2/pkg.json @@ -0,0 +1 @@ +{"license":"","file_hash":null,"name":"Package2","version":"1.0.0","description":"original package","group":"","keywords":null,"dependencies":[],"contents":"","engine_version":"2.1.0.7840","engine":"dynamo","engine_metadata":"","site_url":"","repository_url":"","contains_binaries":true,"node_libraries":["TestAssemblyIsolation2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"]} \ No newline at end of file diff --git a/test/testAssemblyIsolation/graph.dyn b/test/testAssemblyIsolation/graph.dyn new file mode 100644 index 00000000000..15119dac8ff --- /dev/null +++ b/test/testAssemblyIsolation/graph.dyn @@ -0,0 +1,469 @@ +{ + "Uuid": "867a94cc-c780-446a-917e-2c7011eb05b5", + "IsCustomNode": false, + "Description": "", + "Name": "graph", + "ElementResolver": { + "ResolutionMap": {} + }, + "Inputs": [], + "Outputs": [], + "Nodes": [ + { + "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", + "Id": "c0f6d39010f04f8eba7223e80df4cc5d", + "NodeType": "FunctionNode", + "Inputs": [ + { + "Id": "3006c4e2f4224ecd978b191dd903c784", + "Name": "obj", + "Description": "var", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "b2fc6133a83f4655915345fe9304e9b9", + "Name": "string", + "Description": "string", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "FunctionSignature": "TestAssemblyIsolationNamespace1.TestAssemblyIsolationClass1.SerializeSomething@var", + "Replication": "Auto", + "Description": "TestAssemblyIsolationClass1.SerializeSomething (obj: var): string" + }, + { + "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", + "Id": "14db13f89687489db1d328836400ec9f", + "NodeType": "FunctionNode", + "Inputs": [ + { + "Id": "82d6b7375c2943fbaa709c1fbe6679d7", + "Name": "input", + "Description": "string", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "4b672fdb8082498d8be8c1978c1ea3a6", + "Name": "var[]..[]", + "Description": "var[]..[]", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "FunctionSignature": "TestAssemblyIsolationNamespace1.TestAssemblyIsolationClass1.DeserializeSomething@string", + "Replication": "Auto", + "Description": "TestAssemblyIsolationClass1.DeserializeSomething (input: string): var[]..[]" + }, + { + "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", + "Id": "8c387b58add74a70b0e9dc701fa48182", + "NodeType": "FunctionNode", + "Inputs": [ + { + "Id": "09b1fb8e6ce247849d8ea7ef440f0007", + "Name": "obj", + "Description": "var", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "feeec2a041cc4652b1b744b2d5381efc", + "Name": "string", + "Description": "string", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "FunctionSignature": "TestAssemblyIsolationNamespace2.TestAssemblyIsolationClass2.SerializeSomething@var", + "Replication": "Auto", + "Description": "TestAssemblyIsolationClass2.SerializeSomething (obj: var): string" + }, + { + "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", + "Id": "0ce6ab144a2a438bba57e974ca04ce2e", + "NodeType": "FunctionNode", + "Inputs": [ + { + "Id": "8c87519187024d52860864ce5175ca45", + "Name": "input", + "Description": "string", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "20ebd6181eea416ba666af37779fa4d1", + "Name": "var[]..[]", + "Description": "var[]..[]", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "FunctionSignature": "TestAssemblyIsolationNamespace2.TestAssemblyIsolationClass2.DeserializeSomething@string", + "Replication": "Auto", + "Description": "TestAssemblyIsolationClass2.DeserializeSomething (input: string): var[]..[]" + }, + { + "ConcreteType": "CoreNodeModels.CreateList, CoreNodeModels", + "VariableInputPorts": true, + "Id": "348a041f700d441a9b12f7ca95c29a45", + "NodeType": "ExtensionNode", + "Inputs": [ + { + "Id": "33155df314384403bbb3e8143ccb22ef", + "Name": "item0", + "Description": "Item Index #0", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + }, + { + "Id": "51bb733788404d9b84338fb0407f9eab", + "Name": "item1", + "Description": "Item Index #1", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + }, + { + "Id": "c760cce5952c4a12a2fbc501684488c2", + "Name": "item2", + "Description": "Item Index #2", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "7b76cd5a13cb4286806c5e4610a35fdc", + "Name": "list", + "Description": "A list (type: var[]..[])", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "Makes a new list from the given inputs" + }, + { + "ConcreteType": "CoreNodeModels.Input.DoubleInput, CoreNodeModels", + "NumberType": "Double", + "Id": "821680cb39e14ad7ae8f9f91ccdd091f", + "NodeType": "NumberInputNode", + "Inputs": [], + "Outputs": [ + { + "Id": "8eea449f7a464469a1185db3fc02c06a", + "Name": "", + "Description": "Double", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "Creates a number", + "InputValue": 29.0 + }, + { + "ConcreteType": "CoreNodeModels.Watch, CoreNodeModels", + "WatchWidth": 200.0, + "WatchHeight": 200.0, + "Id": "80d468af5d8f40aeb0d7bedec96752f7", + "NodeType": "ExtensionNode", + "Inputs": [ + { + "Id": "4c71bbd9186848b1b35ec260261ed2bd", + "Name": "", + "Description": "Node to show output from", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "681adab7ea3d489396895ab85a571efc", + "Name": "", + "Description": "Node output", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "Visualizes a node's output" + }, + { + "ConcreteType": "CoreNodeModels.Watch, CoreNodeModels", + "WatchWidth": 200.0, + "WatchHeight": 200.0, + "Id": "d1c56ca9b01a4451b50d2af194668f93", + "NodeType": "ExtensionNode", + "Inputs": [ + { + "Id": "4852b8f1a9354579a1f8abd8d45d5f04", + "Name": "", + "Description": "Node to show output from", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "0814d78b06ef43519f9f893a5faf29b1", + "Name": "", + "Description": "Node output", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "Visualizes a node's output" + } + ], + "Connectors": [ + { + "Start": "b2fc6133a83f4655915345fe9304e9b9", + "End": "82d6b7375c2943fbaa709c1fbe6679d7", + "Id": "774a20d100dd47f3a2d111567290d896", + "IsHidden": "False" + }, + { + "Start": "4b672fdb8082498d8be8c1978c1ea3a6", + "End": "4852b8f1a9354579a1f8abd8d45d5f04", + "Id": "fd440802ea8840728031698dcc3a45a1", + "IsHidden": "False" + }, + { + "Start": "feeec2a041cc4652b1b744b2d5381efc", + "End": "8c87519187024d52860864ce5175ca45", + "Id": "eae10e1c5bd94a99a3df97c7e2138723", + "IsHidden": "False" + }, + { + "Start": "20ebd6181eea416ba666af37779fa4d1", + "End": "4c71bbd9186848b1b35ec260261ed2bd", + "Id": "e9dae4cf7dd54f028a576b0a4a71cf27", + "IsHidden": "False" + }, + { + "Start": "7b76cd5a13cb4286806c5e4610a35fdc", + "End": "3006c4e2f4224ecd978b191dd903c784", + "Id": "15079b24952648a682444760e83b6ff8", + "IsHidden": "False" + }, + { + "Start": "7b76cd5a13cb4286806c5e4610a35fdc", + "End": "09b1fb8e6ce247849d8ea7ef440f0007", + "Id": "f8aec11498c04f74a7336b478a9bd415", + "IsHidden": "False" + }, + { + "Start": "8eea449f7a464469a1185db3fc02c06a", + "End": "33155df314384403bbb3e8143ccb22ef", + "Id": "69bce44f2a594b1a90743bbc87b78de2", + "IsHidden": "False" + }, + { + "Start": "8eea449f7a464469a1185db3fc02c06a", + "End": "51bb733788404d9b84338fb0407f9eab", + "Id": "3dc8a37395274626af8368f87228db80", + "IsHidden": "False" + }, + { + "Start": "8eea449f7a464469a1185db3fc02c06a", + "End": "c760cce5952c4a12a2fbc501684488c2", + "Id": "248a1d534ca945b0b1d4e0af4d9c29fb", + "IsHidden": "False" + } + ], + "Dependencies": [], + "NodeLibraryDependencies": [ + { + "Name": "Package1", + "Version": "1.0.0", + "ReferenceType": "Package", + "Nodes": [ + "c0f6d39010f04f8eba7223e80df4cc5d", + "14db13f89687489db1d328836400ec9f" + ] + }, + { + "Name": "Package2", + "Version": "1.0.0", + "ReferenceType": "Package", + "Nodes": [ + "8c387b58add74a70b0e9dc701fa48182", + "0ce6ab144a2a438bba57e974ca04ce2e" + ] + } + ], + "EnableLegacyPolyCurveBehavior": null, + "Thumbnail": "", + "GraphDocumentationURL": null, + "ExtensionWorkspaceData": [ + { + "ExtensionGuid": "28992e1d-abb9-417f-8b1b-05e053bee670", + "Name": "Properties", + "Version": "3.4", + "Data": {} + } + ], + "Author": "", + "Linting": { + "activeLinter": "None", + "activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a", + "warningCount": 0, + "errorCount": 0 + }, + "Bindings": [], + "View": { + "Dynamo": { + "ScaleFactor": 1.0, + "HasRunWithoutCrash": true, + "IsVisibleInDynamoLibrary": true, + "Version": "3.4.0.6124", + "RunType": "Manual", + "RunPeriod": "1000" + }, + "Camera": { + "Name": "_Background Preview", + "EyeX": -17.0, + "EyeY": 24.0, + "EyeZ": 50.0, + "LookX": 12.0, + "LookY": -13.0, + "LookZ": -58.0, + "UpX": 0.0, + "UpY": 1.0, + "UpZ": 0.0 + }, + "ConnectorPins": [], + "NodeViews": [ + { + "Id": "c0f6d39010f04f8eba7223e80df4cc5d", + "Name": "TestAssemblyIsolationClass1.SerializeSomething", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 193.40717948717952, + "Y": 56.36307692307696 + }, + { + "Id": "14db13f89687489db1d328836400ec9f", + "Name": "TestAssemblyIsolationClass1.DeserializeSomething", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 674.6461538461538, + "Y": 42.49641025641026 + }, + { + "Id": "8c387b58add74a70b0e9dc701fa48182", + "Name": "TestAssemblyIsolationClass2.SerializeSomething", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 166.52717948717947, + "Y": 298.471794871795 + }, + { + "Id": "0ce6ab144a2a438bba57e974ca04ce2e", + "Name": "TestAssemblyIsolationClass2.DeserializeSomething", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 652.4430769230771, + "Y": 304.6666666666667 + }, + { + "Id": "348a041f700d441a9b12f7ca95c29a45", + "Name": "List Create", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": -20.908717948718106, + "Y": 98.81743589743596 + }, + { + "Id": "821680cb39e14ad7ae8f9f91ccdd091f", + "Name": "Number", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": -291.40102564102597, + "Y": 80.14051282051284 + }, + { + "Id": "80d468af5d8f40aeb0d7bedec96752f7", + "Name": "Watch", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 1251.031794871795, + "Y": 319.2379487179488 + }, + { + "Id": "d1c56ca9b01a4451b50d2af194668f93", + "Name": "Watch", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 1254.75282051282, + "Y": 18.74564102564102 + } + ], + "Annotations": [], + "X": -0.07419354838680192, + "Y": 41.38629032258066, + "Zoom": 0.786290322580645 + } +} \ No newline at end of file diff --git a/test/test_support_projects/ClassFunctionality.cs b/test/test_support_projects/EmbeddedInterop/ClassFunctionality.cs similarity index 100% rename from test/test_support_projects/ClassFunctionality.cs rename to test/test_support_projects/EmbeddedInterop/ClassFunctionality.cs diff --git a/test/test_support_projects/EmbeddedInterop.csproj b/test/test_support_projects/EmbeddedInterop/EmbeddedInterop.csproj similarity index 100% rename from test/test_support_projects/EmbeddedInterop.csproj rename to test/test_support_projects/EmbeddedInterop/EmbeddedInterop.csproj diff --git a/test/test_support_projects/TestAssemblyIsolation/ClassFunctionality1.cs b/test/test_support_projects/TestAssemblyIsolation/ClassFunctionality1.cs new file mode 100644 index 00000000000..2397cc73e7e --- /dev/null +++ b/test/test_support_projects/TestAssemblyIsolation/ClassFunctionality1.cs @@ -0,0 +1,19 @@ + +namespace TestAssemblyIsolationNamespace1 +{ + /// + /// Tests + /// + public class TestAssemblyIsolationClass1 + { + public static string SerializeSomething(object obj) + { + return Newtonsoft.Json.JsonConvert.SerializeObject(obj, new Newtonsoft.Json.JsonSerializerSettings()); + } + + public static object DeserializeSomething(string input) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(input, new Newtonsoft.Json.JsonSerializerSettings()); + } + } +} diff --git a/test/test_support_projects/TestAssemblyIsolation/ClassFunctionality2.cs b/test/test_support_projects/TestAssemblyIsolation/ClassFunctionality2.cs new file mode 100644 index 00000000000..f5e6ebe6908 --- /dev/null +++ b/test/test_support_projects/TestAssemblyIsolation/ClassFunctionality2.cs @@ -0,0 +1,19 @@ + +namespace TestAssemblyIsolationNamespace2 +{ + /// + /// Tests + /// + public class TestAssemblyIsolationClass2 + { + public static string SerializeSomething(object obj) + { + return Newtonsoft.Json.JsonConvert.SerializeObject(obj, new Newtonsoft.Json.JsonSerializerSettings()); + } + + public static object DeserializeSomething(string input) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(input, new Newtonsoft.Json.JsonSerializerSettings()); + } + } +} diff --git a/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation.sln b/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation.sln new file mode 100644 index 00000000000..d0074de48b5 --- /dev/null +++ b/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestAssemblyIsolation1", "TestAssemblyIsolation1.csproj", "{C933A91C-C66E-4772-91E9-9E6F8785A35A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestAssemblyIsolation2", "TestAssemblyIsolation2.csproj", "{8BB9F923-83CD-4699-B7BF-0409D92BA96C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C933A91C-C66E-4772-91E9-9E6F8785A35A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C933A91C-C66E-4772-91E9-9E6F8785A35A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C933A91C-C66E-4772-91E9-9E6F8785A35A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C933A91C-C66E-4772-91E9-9E6F8785A35A}.Release|Any CPU.Build.0 = Release|Any CPU + {8BB9F923-83CD-4699-B7BF-0409D92BA96C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BB9F923-83CD-4699-B7BF-0409D92BA96C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BB9F923-83CD-4699-B7BF-0409D92BA96C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BB9F923-83CD-4699-B7BF-0409D92BA96C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9F22860F-E4E1-46A5-856C-14C20592B5F5} + EndGlobalSection +EndGlobal diff --git a/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation1.csproj b/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation1.csproj new file mode 100644 index 00000000000..c61eba0d125 --- /dev/null +++ b/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation1.csproj @@ -0,0 +1,14 @@ + + + netstandard2.0 + TestAssemblyIsolation1 + Library + true + False + bin/$(AssemblyName)/$(Configuration) + + + + + + diff --git a/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation2.csproj b/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation2.csproj new file mode 100644 index 00000000000..ac6371f5925 --- /dev/null +++ b/test/test_support_projects/TestAssemblyIsolation/TestAssemblyIsolation2.csproj @@ -0,0 +1,14 @@ + + + netstandard2.0 + TestAssemblyIsolation2 + Library + true + False + bin/$(AssemblyName)/$(Configuration) + + + + + +