oleg-shilo / cs-script

C# scripting platform
http://www.cs-script.net
MIT License
1.57k stars 234 forks source link

Can .Net 6 Single File work with ReferenceAssemblyOf()? #298

Closed yejinmo closed 2 years ago

yejinmo commented 2 years ago

Description

Can .Net 6 Single File work with ReferenceAssemblyOf()?

Code:

CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn;
CSScript.Evaluator.IsCachingEnabled = true;
CSScript.Evaluator.ReferenceAssemblyByNamespace("NPOI");
CSScript.Evaluator.ReferenceAssemblyOf<ISheet>(); // NPOI ISheet

Crash:

Unhandled exception. System.Exception: Current version of Roslyn-based evaluator does not support referencing assemblies which are not loaded from the file location.
   at CSScriptLib.RoslynEvaluator.ReferenceAssembly(Assembly assembly)
   at CSScriptLib.EvaluatorBase`1.ReferenceAssemblyOf[T]()

Configuration

pubxml:

<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
  <PropertyGroup>
    <Configuration>Release</Configuration>
    <Platform>Any CPU</Platform>
    <PublishDir>bin\Release\net6.0\publish\linux-x64\</PublishDir>
    <PublishProtocol>FileSystem</PublishProtocol>
    <TargetFramework>net6.0</TargetFramework>
    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
    <SelfContained>true</SelfContained>
    <PublishSingleFile>true</PublishSingleFile>
    <PublishTrimmed>false</PublishTrimmed>
  </PropertyGroup>
</Project>

Other information

The point is src/CSScriptLib/src/CSScriptLib/Evaluator.Roslyn.cs :

public override IEvaluator ReferenceAssembly(Assembly assembly)
{
    //Microsoft.Net.Compilers.1.2.0 - beta
    if (assembly.Location.IsEmpty())
        throw new Exception(
            $"Current version of Roslyn-based evaluator does not support referencing assemblies " +
                "which are not loaded from the file location.");

    if (!refAssemblies.Contains(assembly))
        refAssemblies.Add(assembly);
    return this;
}

But:

https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.location?view=net-6.0 In .NET 5 and later versions, for bundled assemblies, the value returned is an empty string. .NET Framework only: If the loaded file was shadow-copied, the location is that of the file after being shadow-copied. To get the location before the file has been shadow-copied, use the CodeBase property.

Does it mean that Single File cannot be used with ReferenceAssemblyOf()? If so is there any other way to achieve something like ReferenceAssemblyOf()?

Thanks.

oleg-shilo commented 2 years ago

The problem is with the Roslyn engine that expects the Roslyn not to support referencing loaded assemblies that have no assembly file available.

Meaning that you need to find a way to discover the location (e.g. Location() vs .Location) of that assembly.

CSScript.Evaluator.ReferenceAssemblyByNamespace("NPOI")
                  .ReferenceAssembly(typeof<ISheet>.Location()) // potentially better option
                  .LoadCode(...); 

And... if you cannot then... it's not much you can do. Note, it is not a limitation of CS-Script but Roslyn.

The other option I can see is to try to use dynamic types instead of interfaces. You will lose type safety but will gain flexibility.

Another approach is to try to pass the host application instead. If the 'Single File' compiling option does what ILMerge does then the interface assembly is the same as the entry assembly.

On another topic, your code will not work as expected. You create and abandon a new instance evaluator every time you call ReferenceAssembly....

Please use Fluent Interface syntax (my code snippet) or simply create the instance of evaluator and then use it directly.

yejinmo commented 2 years ago

The problem is with the Roslyn engine that expects the Roslyn not to support referencing loaded assemblies that have no assembly file available.

Meaning that you need to find a way to discover the location (e.g. Location() vs .Location) of that assembly.

CSScript.Evaluator.ReferenceAssemblyByNamespace("NPOI")
                  .ReferenceAssembly(typeof<ISheet>.Location()) // potentially better option
                  .LoadCode(...); 

And... if you cannot then... it's not much you can do. Note, it is not a limitation of CS-Script but Roslyn.

The other option I can see is to try to use dynamic types instead of interfaces. You will lose type safety but will gain flexibility.

Another approach is to try to pass the host application instead. If the 'Single File' compiling option does what ILMerge does then the interface assembly is the same as the entry assembly.

On another topic, your code will not work as expected. You create and abandon a new instance evaluator every time you call ReferenceAssembly....

Please use Fluent Interface syntax (my code snippet) or simply create the instance of evaluator and then use it directly.

Thanks for the suggestion, it looks like the problem is not that simple, I will disable Single File to keep the functionality available.