Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions src/Common/AssemblyLoadingUtils.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.ML.Runtime.Internal.Utilities;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.ML.Runtime
{
internal static class AssemblyLoadingUtils
{
/// <summary>
/// Make sure the given assemblies are loaded and that their loadable classes have been catalogued.
/// </summary>
public static void LoadAndRegister(IHostEnvironment env, string[] assemblies)
{
Contracts.AssertValue(env);

if (Utils.Size(assemblies) > 0)
{
foreach (string path in assemblies)
{
Exception ex = null;
try
{
// REVIEW: Will LoadFrom ever return null?
Contracts.CheckNonEmpty(path, nameof(path));
var assem = LoadAssembly(env, path);
if (assem != null)
continue;
}
catch (Exception e)
{
ex = e;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we try to load zip files? Why not either load or unzip depending on the extension?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - existing behavior from the command line code that was removed from ComponentCatalog.

}

// If it is a zip file, load it that way.
ZipArchive zip;
try
{
zip = ZipFile.OpenRead(path);
}
catch (Exception e)
{
// Couldn't load as an assembly and not a zip, so warn the user.
ex = ex ?? e;
Console.Error.WriteLine("Warning: Could not load '{0}': {1}", path, ex.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be writing to the Console from libraries. And more generally, why do we swallow this error?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied out of the ComponentCatalog and moved to an internal class that is only called from the command line. (see src/Microsoft.ML.Maml/HelpCommand.cs and src/Microsoft.ML.ResultProcessor/ResultProcessor.cs) I wasn't going to change the behavior, because I'm sure someone is depending on it.

continue;
}

string dir;
try
{
dir = CreateTempDirectory();
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Creating temp directory for extra assembly zip extraction failed: '{0}'", path);
}

try
{
zip.ExtractToDirectory(dir);
}
catch (Exception e)
{
throw Contracts.ExceptIO(e, "Extracting extra assembly zip failed: '{0}'", path);
}

LoadAssembliesInDir(env, dir, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use zip files and just blast them into a folder instead using Nuget? It only works if the extensions don't use other dependencies. The moment they use other dependencies, we need to worry about versions. Nuget (theoretically) handles all of this.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing behavior that I am refactoring out of ComponentCatalog so it isn't part of the public API anymore. It is only called from 2 places in the command-line (HelpCommand and ResultProcessor).

ComponentCatalog.CacheClassesExtra(_extraAssemblies);

// extra DLLs for dynamic loading
[Argument(ArgumentType.Multiple,HelpText="Extra DLLs",ShortName="dll")]
publicstring[]ExtraAssemblies=null;

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who deletes the temp path and when?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess nobody. This is an internal API that is only called from 2 places: HelpCommand and ResultProcessor, so it isn't part of our public API after this change.

}
}

public static IDisposable CreateAssemblyRegistrar(IHostEnvironment env, string loadAssembliesPath = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only thing that can be done with the registrar is to dispose it?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is an internal method that is only invoked on that command-line. It creates an object that listens for new assemblies to be loaded, and registers them automatically.

{
Contracts.CheckValue(env, nameof(env));
env.CheckValueOrNull(loadAssembliesPath);

return new AssemblyRegistrar(env, loadAssembliesPath);
}

public static void RegisterCurrentLoadedAssemblies(IHostEnvironment env)
{
Contracts.CheckValue(env, nameof(env));

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will attempt to register lots of assemblies (e.g. all framework assemblies). Isn't it wasteful?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I brought back the code that checks if the assembly references the assembly containing the LoadableClassAttributeBase class.

boolfound=false;
vartargetName=target.GetName();
foreach(varnameinassembly.GetReferencedAssemblies())
{
if(name.Name==targetName.Name)
{
found=true;
break;
}
}
if(!found)
continue;

{
TryRegisterAssembly(env.ComponentCatalog, a);
}
}

private static string CreateTempDirectory()
{
string dir = GetTempPath();
Directory.CreateDirectory(dir);
return dir;
}

private static string GetTempPath()
{
Guid guid = Guid.NewGuid();
return Path.GetFullPath(Path.Combine(Path.GetTempPath(), "MLNET_" + guid.ToString()));
}

private static readonly string[] _filePrefixesToAvoid = new string[] {
"api-ms-win",
"clr",
"coreclr",
"dbgshim",
"ext-ms-win",
"microsoft.bond.",
"microsoft.cosmos.",
"microsoft.csharp",
"microsoft.data.",
"microsoft.hpc.",
"microsoft.live.",
"microsoft.platformbuilder.",
"microsoft.visualbasic",
"microsoft.visualstudio.",
"microsoft.win32",
"microsoft.windowsapicodepack.",
"microsoft.windowsazure.",
"mscor",
"msvc",
"petzold.",
"roslyn.",
"sho",
"sni",
"sqm",
"system.",
"zlib",
};

private static bool ShouldSkipPath(string path)
{
string name = Path.GetFileName(path).ToLowerInvariant();
switch (name)
{
case "cqo.dll":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the list? Are we should the list does not have items that are not needed anymore? Might be at least worth to add a comment.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the list in the current code. I just moved the list out of the ComponentCatalog class.

privatestaticboolShouldSkipPath(stringpath)
{
stringname=Path.GetFileName(path).ToLowerInvariant();
switch(name)
{
case"cqo.dll":
case"fasttreenative.dll":
case"libiomp5md.dll":
case"libvw.dll":
case"matrixinterf.dll":
case"microsoft.ml.neuralnetworks.gpucuda.dll":
case"mklimports.dll":
case"microsoft.research.controls.decisiontrees.dll":
case"microsoft.ml.neuralnetworks.sse.dll":
case"neuraltreeevaluator.dll":
case"optimizationbuilderdotnet.dll":
case"parallelcommunicator.dll":
case"microsoft.ml.runtime.runtests.dll":
case"scopecompiler.dll":
case"tbb.dll":
case"internallearnscope.dll":
case"unmanagedlib.dll":
case"vcclient.dll":
case"libxgboost.dll":
case"zedgraph.dll":
case"__scopecodegen__.dll":
case"cosmosClientApi.dll":

case "fasttreenative.dll":
case "libiomp5md.dll":
case "libvw.dll":
case "matrixinterf.dll":
case "microsoft.ml.neuralnetworks.gpucuda.dll":
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
case "microsoft.ml.runtime.runtests.dll":
case "scopecompiler.dll":
case "tbb.dll":
case "internallearnscope.dll":
case "unmanagedlib.dll":
case "vcclient.dll":
case "libxgboost.dll":
case "zedgraph.dll":
case "__scopecodegen__.dll":
case "cosmosClientApi.dll":
return true;
}

foreach (var s in _filePrefixesToAvoid)
{
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static void LoadAssembliesInDir(IHostEnvironment env, string dir, bool filter)
{
if (!Directory.Exists(dir))
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you silently return? Why not throw or at least debug assert.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are cases where this is called on potentially non-existing directories, for example here where we try calling it on the AutoLoad folder, which may not exist. (again that was all existing behavior that I'm refactoring out of ComponentCatalog.)


using (var ch = env.Start("LoadAssembliesInDir"))
{
// Load all dlls in the given directory.
var paths = Directory.EnumerateFiles(dir, "*.dll");
foreach (string path in paths)
{
if (filter && ShouldSkipPath(path))
{
ch.Info($"Skipping assembly '{path}' because its name was filtered.");
continue;
}

LoadAssembly(env, path);
}
}
}

/// <summary>
/// Given an assembly path, load the assembly and register it with the ComponentCatalog.
/// </summary>
private static Assembly LoadAssembly(IHostEnvironment env, string path)
{
Assembly assembly = null;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception e)
{
using (var ch = env.Start("LoadAssembly"))
{
ch.Error("Could not load assembly {0}:\n{1}", path, e.ToString());
}
return null;
}

if (assembly != null)
{
TryRegisterAssembly(env.ComponentCatalog, assembly);
}

return assembly;
}

/// <summary>
/// Checks whether <paramref name="assembly"/> references the assembly containing LoadableClassAttributeBase,
/// and therefore can contain components.
/// </summary>
private static bool CanContainComponents(Assembly assembly)
{
var targetFullName = typeof(LoadableClassAttributeBase).Assembly.GetName().FullName;

bool found = false;
foreach (var name in assembly.GetReferencedAssemblies())
{
if (name.FullName == targetFullName)
{
found = true;
break;
}
}

return found;
}

private static void TryRegisterAssembly(ComponentCatalog catalog, Assembly assembly)
{
// Don't try to index dynamic generated assembly
if (assembly.IsDynamic)
return;

if (!CanContainComponents(assembly))
return;

catalog.RegisterAssembly(assembly);
}

private sealed class AssemblyRegistrar : IDisposable
{
private readonly IHostEnvironment _env;

public AssemblyRegistrar(IHostEnvironment env, string path)
{
_env = env;

RegisterCurrentLoadedAssemblies(_env);

if (!string.IsNullOrEmpty(path))
{
LoadAssembliesInDir(_env, path, true);
path = Path.Combine(path, "AutoLoad");
LoadAssembliesInDir(_env, path, true);
}

AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainAssemblyLoad;
}

private void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
TryRegisterAssembly(_env.ComponentCatalog, args.LoadedAssembly);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.ML.Api/ComponentCreation.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -439,7 +439,7 @@ private static TRes CreateCore<TRes, TArgs, TSig>(IHostEnvironment env, TArgs ar
{
env.CheckValue(args, nameof(args));

var classes = ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
var classes = env.ComponentCatalog.FindLoadableClasses<TArgs, TSig>();
if (classes.Length == 0)
throw env.Except("Couldn't find a {0} class that accepts {1} as arguments.", typeof(TRes).Name, typeof(TArgs).FullName);
if (classes.Length > 1)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.ML.Api/SerializableLambdaTransform.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,8 @@ public static VersionInfo GetVersionInfo()
verWrittenCur: 0x00010001,
verReadableCur: 0x00010001,
verWeCanReadBack: 0x00010001,
loaderSignature: LoaderSignature);
loaderSignature: LoaderSignature,
loaderAssemblyName: typeof(SerializableLambdaTransform).Assembly.FullName);
}

public const string LoaderSignature = "UserLambdaMapTransform";
Expand Down
Loading