diff --git a/BHoM_UI/Global/AssemblyResolver.cs b/BHoM_UI/Global/AssemblyResolver.cs index 2d66d66..12653ea 100644 --- a/BHoM_UI/Global/AssemblyResolver.cs +++ b/BHoM_UI/Global/AssemblyResolver.cs @@ -84,7 +84,7 @@ public bool MakeSureAssemblyIsLoadedForType(string type) { if (!BH.Engine.Base.Query.IsAssemblyLoaded(assemblyName)) { - string assemblyPath = Path.Combine(BH.Engine.Base.Query.BHoMFolder(), assemblyName + ".dll"); + string assemblyPath = BH.Engine.UI.Query.AssemblyPath(assemblyName); if (!File.Exists(assemblyPath)) { BH.Engine.Base.Compute.RecordError($"Assembly file not found when trying to load assemblies for type {type}: {assemblyPath}"); @@ -162,7 +162,7 @@ public bool MakeSureAssemblyIsLoadedForExtensionMethod(string methodName, Type t { if (!BH.Engine.Base.Query.IsAssemblyLoaded(assemblyName)) { - string assemblyPath = Path.Combine(BH.Engine.Base.Query.BHoMFolder(), assemblyName + ".dll"); + string assemblyPath = BH.Engine.UI.Query.AssemblyPath(assemblyName); if (!File.Exists(assemblyPath)) { BH.Engine.Base.Compute.RecordNote($"Assembly not found for extension method {methodName}: {assemblyPath}"); diff --git a/BHoM_UI/Global/Initialisation.cs b/BHoM_UI/Global/Initialisation.cs index b3091be..f612a8c 100644 --- a/BHoM_UI/Global/Initialisation.cs +++ b/BHoM_UI/Global/Initialisation.cs @@ -68,9 +68,9 @@ public static bool Activate() success &= LoadCodeElements(); success &= CreateAssemblyResolver(); + success &= LoadToolkitSettings(); success &= LoadNewAssemblies(); success &= CreateSearchItems(CodeElements); - success &= LoadToolkitSettings(); CompletionTime = DateTime.UtcNow; @@ -127,7 +127,10 @@ private static bool InitialiseToolkit(IInitialisationSettings settings) // Make sure the assembly is loaded for that method if (!string.IsNullOrEmpty(settings.InitialisationAssembly) && !BH.Engine.Base.Query.IsAssemblyLoaded(settings.InitialisationAssembly)) - BH.Engine.Base.Compute.LoadAssembly(Path.Combine(BH.Engine.Base.Query.BHoMFolder(), settings.InitialisationAssembly + ".dll")); + { + string initAssemblyPath = BH.Engine.UI.Query.AssemblyPath(settings.InitialisationAssembly); + BH.Engine.Base.Compute.LoadAssembly(initAssemblyPath); + } // Get method declaring type List typeCandidates = Engine.Base.Create.AllTypes(typeName).Where(x => x.FullName == typeName).ToList(); diff --git a/UI_Engine/Compute/LoadNewAssemblies.cs b/UI_Engine/Compute/LoadNewAssemblies.cs index a924649..4355cf9 100644 --- a/UI_Engine/Compute/LoadNewAssemblies.cs +++ b/UI_Engine/Compute/LoadNewAssemblies.cs @@ -55,16 +55,42 @@ public static List LoadNewAssemblies(Dictionary lastAs // Make sure the keys for the assemblies are in lower case to avoid casing mismatching Dictionary lastUpdateTimes = lastAssemblyUpdateTimes.ToDictionary(x => x.Key.ToLower(), x => x.Value); + HashSet loadedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet visitedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); - List loadedAssemblies = new List(); + // Pass 1: runtime-specific subdirectory (preferred over flat folder when present) + string bhomFolder = BH.Engine.Base.Query.BHoMFolder(); + foreach (string subFolder in BH.Engine.UI.Query.SubFoldersForRuntime()) + { + string runtimeFolder = Path.Combine(bhomFolder, subFolder); + LoadNewAssembliesForFolder(runtimeFolder, lastUpdateTimes, loadedAssemblies, visitedAssemblies); + } + + // Pass 2: flat folder, skipping any assembly already handled by the runtime subdir pass + LoadNewAssembliesForFolder(bhomFolder, lastUpdateTimes, loadedAssemblies, visitedAssemblies); + + return loadedAssemblies.ToList(); + } - Regex regex = new Regex(@"oM$|_Engine$|_Adapter$"); - foreach (string file in Directory.GetFiles(BH.Engine.Base.Query.BHoMFolder(), "*.dll", SearchOption.TopDirectoryOnly)) + + /*************************************/ + /**** Private Methods ****/ + /*************************************/ + + private static void LoadNewAssembliesForFolder(string folderPath, Dictionary lastUpdateTimes, HashSet loadedAssemblies, HashSet visitedAssemblies) + { + if (!Directory.Exists(folderPath)) + return; + + foreach (string file in Directory.GetFiles(folderPath, "*.dll", SearchOption.TopDirectoryOnly)) { string name = Path.GetFileNameWithoutExtension(file); - if (regex.IsMatch(name)) + + if (m_AssemblyNameFilter.IsMatch(name) && !visitedAssemblies.Contains(name)) { + visitedAssemblies.Add(name); string key = name.ToLower(); + if (!lastUpdateTimes.ContainsKey(key) || lastUpdateTimes[key] < File.GetLastWriteTimeUtc(file)) { Assembly assembly = BH.Engine.Base.Compute.LoadAssembly(file); @@ -72,14 +98,19 @@ public static List LoadNewAssemblies(Dictionary lastAs { BH.Engine.Base.Compute.RecordNote($"Assembly {name} loaded as it was newer than its last recorded update time."); loadedAssemblies.Add(name); - } - } + } + } } } - - return loadedAssemblies; } + + /*************************************/ + /**** Private Fields ****/ + /*************************************/ + + private static readonly Regex m_AssemblyNameFilter = new Regex(@"oM$|_Engine$|_Adapter$"); + /*************************************/ } diff --git a/UI_Engine/Query/AssemblyPath.cs b/UI_Engine/Query/AssemblyPath.cs new file mode 100644 index 0000000..af7ded7 --- /dev/null +++ b/UI_Engine/Query/AssemblyPath.cs @@ -0,0 +1,70 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.Base; +using BH.oM.Base.Attributes; +using BH.oM.UI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BH.Engine.UI +{ + public static partial class Query + { + /*************************************/ + /**** Public Methods ****/ + /*************************************/ + + [Description("Returns the best on-disk path for a BHoM assembly, preferring the runtime-specific subdirectory (netX.0\\ or netfx\\) over the flat folder.")] + [Input("assemblyName", "Assembly name without extension, e.g. 'SQL_Adapter'")] + [Output("path", "Full path to the .dll file; the file may or may not exist.")] + public static string AssemblyPath(string assemblyName) + { + string bhomFolder = BH.Engine.Base.Query.BHoMFolder(); + + // First try to return an assembly from a runtime-specific folder + foreach (string subFolder in BH.Engine.UI.Query.SubFoldersForRuntime()) + { + string runtimePath = Path.Combine(bhomFolder, subFolder, assemblyName + ".dll"); + if (File.Exists(runtimePath)) + return runtimePath; + } + + //Then fallback to returning the assembly from the default rool folder + return Path.Combine(bhomFolder, assemblyName + ".dll"); + } + + /*************************************/ + } +} + + + + + + + diff --git a/UI_Engine/Query/SubFoldersForRuntime.cs b/UI_Engine/Query/SubFoldersForRuntime.cs new file mode 100644 index 0000000..63b886d --- /dev/null +++ b/UI_Engine/Query/SubFoldersForRuntime.cs @@ -0,0 +1,85 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.Base; +using BH.oM.Base.Attributes; +using BH.oM.UI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BH.Engine.UI +{ + public static partial class Query + { + /*************************************/ + /**** Public Methods ****/ + /*************************************/ + + [Description("Returns the runtime-specific subdirectories of the BHoM Assemblies folder where assemblies compatible with the current .NET runtime can be found. " + + "Returns '.../Assemblies/netfx/' on .NET Framework and '.../Assemblies/netX.0/' on CoreCLR (.NET X).")] + [Output("subFolders", "runtime-specific subdirectories for the BHoM assemblies sorted in the order they should be traversed.")] + public static List SubFoldersForRuntime() + { + if (m_SubFoldersForRuntime != null) + return m_SubFoldersForRuntime; + + + var desc = RuntimeInformation.FrameworkDescription; + if (desc.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase)) + { + // Return 'netfx' if the framework is a .NET Framework + m_SubFoldersForRuntime = new List { "netfx" }; + } + else + { + // For .NET Core, return exact TFM first, then descend to lower versions as fallback + m_SubFoldersForRuntime = new List(); + int major = Environment.Version.Major; + for (int v = major; v >= 5; v--) + m_SubFoldersForRuntime.Add($"net{v}.0"); + } + + return m_SubFoldersForRuntime; + } + + + /*************************************/ + /**** Private Fields ****/ + /*************************************/ + + private static List m_SubFoldersForRuntime = null; + + /*************************************/ + } +} + + + + + + +