-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #206 from SceneGate/feature/new-plugins
❇️ Re-implementation of plugins API
- Loading branch information
Showing
36 changed files
with
1,772 additions
and
1,267 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# Loading .NET assemblies | ||
|
||
.NET provide already APIs to load additional assemblies via | ||
[`AssemblyLoadContext`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext). | ||
Yarhl provides extensions methods for `AssemblyLoadContext` to facilitate | ||
loading files from disk. | ||
|
||
You can use the main `AssemblyLoadContext` from `AssemblyLoadContext.Default` to | ||
load them. For advanced use cases, it's possible to create a new | ||
`AssemblyLoadContext` that would provide isolation. | ||
|
||
> [!TIP] | ||
> If you plan to use [`ConverterLocator`](./locate-types.md#converterlocator), | ||
> remember to call `ScanAssemblies` after loading new assemblies. | ||
<!-- ignore markdown warning --> | ||
|
||
> [!WARNING] | ||
> Loading a .NET assembly may load also its required dependencies. You may run | ||
> into dependency issues if they use different versions of a base library such | ||
> as Yarhl or Newtonsoft.Json. | ||
<!-- ignore markdown warning --> | ||
|
||
> [!IMPORTANT] | ||
> There may a security risk by loading **untrusted** assemblies from a file or a | ||
> directory. .NET does provide any security feature to validate it's not | ||
> malicious code. | ||
## Load from file paths | ||
|
||
The method | ||
[`TryLoadFromAssemblyPath`](<xref:Yarhl.Plugins.AssemblyLoadContextExtensions.TryLoadFromAssemblyPath(System.Runtime.Loader.AssemblyLoadContext,System.String)>) | ||
will try to load the .NET assembly in the given path. If this assembly fails to | ||
load (e.g. it's not a .NET binary) it will return `null`. | ||
|
||
Similar, the method | ||
[`TryLoadFromAssembliesPath`](<xref:Yarhl.Plugins.AssemblyLoadContextExtensions.TryLoadFromAssembliesPath(System.Runtime.Loader.AssemblyLoadContext,System.Collections.Generic.IEnumerable{System.String})>) | ||
will try to load every assembly in the list of paths given. If any of them fails | ||
to load, no exception will be raised and it would be skipped. | ||
|
||
Additionally, this API will skip any file where its name starts with any of the | ||
following prefixes. The goal is to prevent loading unwanted dependencies. If you | ||
want to force loading them, use `TryLoadFromAssemblyPath`. | ||
|
||
- `System.` | ||
- `Microsoft.` | ||
- `netstandard` | ||
- `nuget` | ||
- `nunit` | ||
- `testhost` | ||
|
||
## Load from a directory | ||
|
||
The method | ||
[`TryLoadFromDirectory`](<xref:Yarhl.Plugins.AssemblyLoadContextExtensions.TryLoadFromDirectory(System.Runtime.Loader.AssemblyLoadContext,System.String,System.Boolean)>) | ||
will try to load every file in the given directory with an extension `.dll` or | ||
`.exe`. If any of them fails, no error will be reported and it would be skipped. | ||
|
||
Via an argument it's possible to configure if it should load files from the | ||
given directory or from its subdirectories recursively as well. | ||
|
||
## Load from executing directory | ||
|
||
A common use case it's to load every assembly from the executable directory. | ||
Because .NET will load an assembly lazily, only when type actually need it, upon | ||
startup not every assembly from the executable directory could be loaded. | ||
|
||
The method | ||
[`TryLoadFromBaseLoadDirectory`](<xref:Yarhl.Plugins.AssemblyLoadContextExtensions.TryLoadFromBaseLoadDirectory(System.Runtime.Loader.AssemblyLoadContext)>) | ||
addresses this use case by loading every `.dll` and `.exe` from the current | ||
`AppDomain.CurrentDomain.BaseDirectory`. | ||
|
||
> [!TIP] | ||
> To use _plugins_ in a _controlled way_, the application may add a set of | ||
> `PackageReference`s. After running `dotnet publish` these dependencies will be | ||
> copied to the output directory. At startup call | ||
> `AssemblyLoadContext.Default.TryLoadFromBaseLoadDirectory` to load all of | ||
> them. Otherwise, unless the application also references their types, the | ||
> assemblies will not be loaded. | ||
<!-- ignore warning --> | ||
|
||
> [!NOTE] | ||
> It does not use `Environment.ProcessPath` because sometimes the application | ||
> (or tests) may run by passing the main library file to the `dotnet` host | ||
> application (e.g. `dotnet MyApp.dll`). In that case it would scan the | ||
> installation path of the .NET SDK instead of the application installation | ||
> directory. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Locate types | ||
|
||
After [loading external .NET assemblies](./load-assembly.md) containing | ||
implementation of _formats_ and _converters_, the application can get a list of | ||
them via `ConverterLocator`. | ||
|
||
> [!NOTE] | ||
> This is only needed if the application does not know in advance the converter | ||
> to use. It can present the list to the user so it can choose. Or it can get | ||
> the converter names from a configuration file and later find the actual type | ||
> via reflection. For instance for generic Tinke-like applications. | ||
## TypeLocator | ||
|
||
The `TypeLocator` provides features to find types that implement or inherit a | ||
given base type. It searches in the **loaded assemblies** of an | ||
`AssemblyLoadContext` instance. The default _singleton_ instance is accesible | ||
via `TypeLocator.Default` and it uses `AssemblyLoadContext.Default`. Normally | ||
you don't need to create your own instance. | ||
|
||
> [!NOTE] | ||
> .NET loads assemblies lazily, when a code to run needs them. If you need a | ||
> deterministic search consider loading every assembly from the application | ||
> path. See | ||
> [Load from executing directory](./load-assembly.md#load-from-executing-directory) | ||
> for more information. | ||
To find a list of types that inherit a given base class or implements an | ||
interface use the method | ||
[`FindImplementationsOf(Type)`](<xref:Yarhl.Plugins.TypeLocator.FindImplementationsOf(System.Type)>). | ||
It searches for final types, that is: **classes that are public and not | ||
abstract**. It returns information for each of these types in the _record_ | ||
[`TypeImplementationInfo`](xref:Yarhl.Plugins.TypeImplementationInfo) | ||
|
||
For instance to find every _format_ in the loaded asssemblies use: | ||
|
||
[!code-csharp[FindFormats](../../../src/Yarhl.Examples/Plugins/LocateTypesExamples.cs?name=FindFormats)] | ||
|
||
The case of a _generic base type_ is special as types may implemented it | ||
multiple. For instance a _class_ may implement `IConverter<Po, BinaryFormat>` | ||
**and** `IConverter<BinaryFormat, Po>`. Using the _generic type definition_ | ||
(`typeof(IConverter<,>)`) to find types will throw an exception. Use this method | ||
if you are searching for a specific implementation, like | ||
`typeof(IConverter<Po, BinaryFormat>)` | ||
|
||
Use the method | ||
[`FindImplementationsOfGeneric(Type)`](<xref:Yarhl.Plugins.TypeLocator.FindImplementationsOfGeneric(System.Type)>) | ||
to get a list of types implementing the **generic base type definition** with | ||
any type arguments. For instance in the previous example calling | ||
`FindImplementationsOfGeneric(typeof(IConverter<,>))` will return two results | ||
for that class. One for `IConverter<Po, BinaryFormat>` and a second for | ||
`IConverter<BinaryFormat, Po>`. The return type is the _record_ | ||
[`GenericTypeImplementationInfo`](xref:Yarhl.Plugins.GenericTypeImplementationInfo) | ||
|
||
[!code-csharp[FindConverters](../../../src/Yarhl.Examples/Plugins/LocateTypesExamples.cs?name=FindConverters)] | ||
|
||
## ConverterLocator | ||
|
||
The [`ConverterLocator`](xref:Yarhl.Plugins.FileFormat.ConverterLocator) class | ||
provides a cache of formats and converters found in the loaded assemblies. | ||
During initialization (first use) it will use `TypeLocator` to find every format | ||
and converter types. The `Default` singleton instance use `TypeLocator.Default`. | ||
You can pass a custom `TypeLocator` via its public constructor. | ||
|
||
The properties | ||
[`Converters`](xref:Yarhl.Plugins.FileFormat.ConverterLocator.Converters) and | ||
[`Formats`](xref:Yarhl.Plugins.FileFormat.ConverterLocator.Formats) provides a | ||
list of the types found, so there is no need to re-scan the assemblies each | ||
time. | ||
|
||
> [!NOTE] | ||
> If a new assembly is loaded in the `AssemblyLoadContext`, the | ||
> `ConverterLocator` will need to performn a re-scan to find the new types. Make | ||
> sure to call | ||
> [`ConverterLocator.ScanAssemblies()`](xref:Yarhl.Plugins.FileFormat.ConverterLocator.ScanAssemblies) | ||
> after loading new assemblies. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,33 @@ | ||
# Plugins overview | ||
|
||
TODO | ||
`Yarhl.Plugins` provides a set of APIs that helps to load .NET assemblies and | ||
find types. | ||
|
||
Its main goal is to find [converter](../core/formats/converters.md) and | ||
[format](../core/formats/formats.md) types in external .NET assemblies. Generic | ||
applications, like [SceneGate](https://github.com/SceneGate/SceneGate), that | ||
have no knowledge in the converters to use, could use the APIs to find and | ||
propose them to the user. | ||
|
||
The _plugins_ are regular .NET libraries or executable that contains | ||
implementations of _converters_ and _formats_. They don't need to implement any | ||
additional interface or fullfil other requirements. | ||
|
||
The main APIs are: | ||
|
||
- [`AssemblyLoadContextExtensions`](./load-assembly.md): extension methods for | ||
`AssemblyLoadContext` to load .NET assemblies from disk. | ||
- [`TypeLocator`](./locate-types.md#typelocator): find types that implement a | ||
specific interface. | ||
- [`ConverterLocator`](./locate-types.md#converterlocator): find _converter_ and | ||
_format_ types. | ||
|
||
```mermaid | ||
flowchart TB | ||
Application ---> |Load external .NET assemblies| AssemblyLoadContext | ||
Application --> |Find converters| ConverterLocator | ||
ConverterLocator --> |Find implementations\nof IConverter<,>| TypeLocator | ||
TypeLocator --> |Iterate through types in\nloaded assemblies| AssemblyLoadContext | ||
``` | ||
|
||
You can get more information in their subpage. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright (c) 2023 SceneGate | ||
|
||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
|
||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
namespace Yarhl.Examples.Plugins; | ||
|
||
using Yarhl.FileFormat; | ||
using Yarhl.Plugins; | ||
|
||
public static class LocateTypesExamples | ||
{ | ||
public static void FindFormats() | ||
{ | ||
#region FindFormats | ||
TypeImplementationInfo[] formatsInfo = TypeLocator.Default | ||
.FindImplementationsOf(typeof(IFormat)) | ||
.ToArray(); | ||
|
||
Console.WriteLine(formatsInfo[0].Name); // e.g. Yarhl.IO.BinaryFormat | ||
Console.WriteLine(formatsInfo[0].Type); // e.g. Type object for BinaryFormat | ||
#endregion | ||
} | ||
|
||
public static void FindConverters() | ||
{ | ||
#region FindConverters | ||
GenericTypeImplementationInfo[] convertersInfo = TypeLocator.Default | ||
.FindImplementationsOfGeneric(typeof(IConverter<,>)) | ||
.ToArray(); | ||
|
||
Console.WriteLine(convertersInfo[0].Name); // e.g. Yarhl.Media.Text.Binary2Po | ||
Console.WriteLine(convertersInfo[0].Type); // e.g. Type object for Yarhl.Media.Text.Binary2Po | ||
Console.WriteLine(convertersInfo[0].GenericBaseType); // e.g. Type IConverter<BinaryFormat, Po> | ||
Console.WriteLine(convertersInfo[0].GenericTypeParameters); // e.g. [BinaryFormat, Po] | ||
#endregion | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.