Skip to content

Publish to single file gives an empty Assembly.Location which crashes Roslyn Evaluator #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
angrynik opened this issue Sep 28, 2023 · 25 comments
Labels

Comments

@angrynik
Copy link

I'm running a .NET 7 WPF app with a reference to CSScriptLib nuget.

My project works great and scripts run right in debug mode. When I publish I get the error:

"Current version of Roslyn-based evaluator does not support referencing assemblies which are not loaded from the file location."

I publish to a single file which merges most of the referenced dlls into a single dll. Looks like this code in Evaluator.Roslyn.cs is having a problem with it:

        public override IEvaluator ReferenceAssembly(Assembly assembly)
        {
            //Microsoft.Net.Compilers.1.2.0 - beta
            if (assembly.Location.IsEmpty())

I checked this in my code when publishing both to many files and to a single file and in the latter case Location is empty.

            if (string.IsNullOrEmpty(typeof(IRunScriptAsync).Assembly.Location))
                throw new System.Exception("Assembly.Location is empty");

@oleg-shilo
Copy link
Owner

oleg-shilo commented Sep 28, 2023

This error means that you are trying to reference (from the script) an assembly that has not been loaded to your app domain from the file but from memory. I guess this is what runtime does in the "single file" scenario.

It's not a problem for the host assembly execution, but it is a problem for Roslyn, which has the limitation that it cannot reference assemblies without a file.

If it is you who is calling ReferenceAssembly, then I suggest you try to reference the assembly from a file. Replace ReferenceAssembly(Assembly assembly) call with ReferenceAssembly(string assembly).

If it is CS-Script itself when loading domain assemblies, then you should disable referencing domain assemblies and reference them individually by file name. In this case, it will be that single file you built. Though I am not sure how you can find its path if Assembly.Location is empty.

@angrynik
Copy link
Author

I think it's a Roslyn problem, this issue has gone nowhere for a few years:

dotnet/roslyn#50719

That page links to a workaround here: https://github.com/andersstorhaug/SingleFileScripting

I'm new to this so I don't know if that's something I can do myself, or if it could be used in cs-script ? For now I'm going to switch off single file output, and add a hundred dlls to my Wix installer.

@oleg-shilo
Copy link
Owner

I am not sure it is a real workaround.
We have the problem here that Roslyn scripts cannot reference loaded assemblies but only assembly files. The code there does not show the script that references any of such assemblies. It only references core assembly. You can do it with CS-Script too.
Your problem is more fundamental. After merging all your dependency assemblies in a single file it's no longer an assembly that can be referenced.

But... I will play a little with it today to confirm what I just described.

@oleg-shilo
Copy link
Owner

Looked at the sample more...
it might be the way out, actually.
give me some time, I think it is something that can be the way out for cs-script in this scenario.

@angrynik
Copy link
Author

Thanks for looking, would be very nice....

@oleg-shilo
Copy link
Owner

oleg-shilo commented Sep 29, 2023

I can confirm now that with that work around it is now possible to execute scripts from the host app built as a single self-contaied file. Thank you for sharing the info.

It will take a little time to properly integrate it and release the update. The future syntax will look like this:

var calc = CSScript.Evaluator.Execute("1 + 2");

or

var calc = CSScript.Evaluator
                   .Execute(@"using System;
                              public class Script
                              {
                                  public int Sum(int a, int b)
                                  {
                                      return a+b;
                                  }
                              }
                              return new Script();");

int sum = calc.Sum(1, 2);

oleg-shilo added a commit that referenced this issue Oct 1, 2023
  - Issue #343: Publish to single file gives an empty Assembly.Location which crashes Roslyn Evaluator
oleg-shilo added a commit that referenced this issue Oct 1, 2023
- Issue #343: Publish to single file gives an empty Assembly.Location which crashes Roslyn Evaluator
- Added support for single-file published host applications
@oleg-shilo
Copy link
Owner

oleg-shilo commented Oct 1, 2023

Done.
Please update your nuget ref to v4.8.3.

var calc = CSScript.Evaluator
                   .Eval(@"using System;
                           public class Script
                           {
                               public int Sum(int a, int b)
                               {
                                   return a+b;
                               }
                           }
                           return new Script();");

int sum = calc.Sum(1, 2);
Console.WriteLine(sum);

The complete sample can be found here.

@angrynik
Copy link
Author

angrynik commented Oct 2, 2023

Ok, awesome, I have refactored from LoadMethod to now use Eval, and it works great in debug mode. I publish to a single file and now I get:

CodeBase is not supported on assemblies loaded from a single-file bundle

I'm pretty sure my code is similar to your example, but with lots of "using " statements for other referenced assemblies.

@oleg-shilo
Copy link
Owner

I did test the code sample (https://github.com/oleg-shilo/cs-script/blob/master/src/CSScriptLib/src/Client.SingleFileBuild/Program.cs) after publishing so there is something new in your case.

Can you share the solution you are testing so I can have a look? A sanitized version of it.
Or a hello-world example that demonstrates the problem.

@angrynik
Copy link
Author

angrynik commented Oct 2, 2023

Right, I added a class library with one class that runs a script.

namespace BreakLib;
public class BreakClass
{
    public static string RunScript()
    {
        var calc = CSScript.Evaluator
                           .Eval(@"using System;
                           public class Script
                           {
                               public int Sum(int a, int b)
                               {
                                   return a+b;
                               }
                           }
                           return new Script();");

        int sum = calc.Sum(1, 2);
        return $"sum is {sum}";
    }
}

Then I reference the project and call that method from the Client.SingleFileBuild sample.

...
...
var result = BreakLib.BreakClass.RunScript();
Console.WriteLine(result);

This is the setup I use, where the script runner is in a dedicated project and I call that from my apps.

@oleg-shilo
Copy link
Owner

I repeated the test. Seems to work as expected.
I have attached the test project
cs-script.#343.zip

@angrynik
Copy link
Author

angrynik commented Oct 3, 2023

Hmmm. Can I ask how you publish? I do it through Visual Studio with these settings, and managed to crash this sample.

image

@oleg-shilo
Copy link
Owner

it's in the code program.cs.

image

image

image

@angrynik
Copy link
Author

angrynik commented Oct 3, 2023

It looks like the Deployment Mode must be set to Self-contained.

@oleg-shilo
Copy link
Owner

oleg-shilo commented Oct 3, 2023

Please have a look at "publish" folder content on the screenshot I provided.
It does contain only a single executable. It is published as self-contained simply because self-contained mode is configured in the project file (very first screenshot).

Just to ensure we are on the same page I have rebuilt the project with explicit CLI parameters for self-containment:

dotnet publish -c Release --self-contained true

The outcome is the same.

Can you please share the project sample, and the CLI command to build it? So we are working on the same things.

@angrynik
Copy link
Author

angrynik commented Oct 3, 2023

Sorry for the confusion, I am using your project and yes it does work in self contained mode. The problem arises when deployed as Framework dependent, or self-contained = false.

I have always used Framework dependent deployments, due to a smaller file size, but I'm thinking self-contained does have benefits, and the bandwidth is not such a big deal these days. So deploying self-contained as the solution is not a problem.

@oleg-shilo
Copy link
Owner

OK, but...
How did you publish your project anyway?
I would like to see the scenario that is still not covered, so I can possibly address it.

@angrynik
Copy link
Author

angrynik commented Oct 3, 2023

dotnet publish -c Release --no-self-contained

@oleg-shilo
Copy link
Owner

Great, it works.

In the code I analyze if it is a single-file deployment. The analysis is done like this:

public static bool IsSingleFileApplication { get; } = "".GetType().Assembly.Location.IsEmpty();

. . .

catch (Exception ex)
{
#if class_lib
    if (Runtime.IsSingleFileApplication)
        return null; // a single file compilation (published with PublishSingleFile option)
#endif
    throw;
}                

But if the app compiled as in your case the IsSingleFileApplication does not detect "danger of calling CodeBase".

The updated version with the fallback exception handler looks like this:

catch (Exception ex)
{
#if class_lib
    if (Runtime.IsSingleFileApplication)
        return null; // a single file compilation (published with PublishSingleFile option)
    else if (ex.Message.Contains("CodeBase is not supported on assemblies loaded from a single-file bundle")
            || ex.StackTrace.Contains("at System.Reflection.RuntimeAssembly.get_CodeBase()"))
        return null;
#endif
    throw;
}

And it works just fine.
image

@oleg-shilo
Copy link
Owner

I do not want to do premature release so will release this change as a pre-release.

You can probably appreciate now why I always ask for a VS test project 😄
A verbal description of the test actions is never as accurate as code.

oleg-shilo added a commit that referenced this issue Oct 4, 2023
- Issue #343: Publish to single file gives an empty Assembly.Location which crashes Roslyn Evaluator
oleg-shilo added a commit that referenced this issue Oct 4, 2023
---

## Changes

### CLI

- no changes

### CSScriptLib

- Issue #343: Publish to single file gives an empty Assembly.Location which crashes Roslyn Evaluator
  Added support for single-file publishing with runtime dependency
@oleg-shilo
Copy link
Owner

Done, you can get the latest v4.8.4-pre pre-release from nuget.org.

dotnet add package CS-Script --version 4.8.4-pre

@angrynik
Copy link
Author

angrynik commented Oct 4, 2023

It works! Amazing thank you.

@jackhab
Copy link

jackhab commented Apr 29, 2025

@oleg-shilo

Hi Oleg

I'm also trying to build single-file executable so I used your attached example which works fine but when I moved the code from the class library into Main() and removed the library from the solution the generated exe failed with

Unhandled exception. CSScriptLib.CompilerException: <script>(1,7): error CS0246: The type or namespace name 'System' could not be found (are you missing a using directive or an assembly reference?)
<script>(2,35): error CS0246: The type or namespace name 'System' could not be found (are you missing a using directive or an assembly reference?)
...

Is it supposed to be like this and I should always use the library to publish single-file executable?

@oleg-shilo
Copy link
Owner

That is a very interesting scenario which, ironically, is already covered in the samples but not well reflected in the documentation. The reason is... I did not know about this behaviour. :)

Your post pushed me to re-test both samples:

  1. The sample shared in this thread (uses host app that loads the library doing script execution with CSScriptLib)
  2. The sample included in the repo codebase (uses the host app that does script execution with CSScriptLib). This is the case you are enquiring about.

This is what I have found:

  • Sample # 1
    works with: dotnet publish -c Release --self-contained true
    does not work with: dotnet publish -c Release --self-contained false

  • Sample # 2
    does not work with: dotnet publish -c Release --self-contained true
    works with: dotnet publish -c Release --self-contained false

This means that the different deployment strategies lead to different points of failure. And while it sounds very hacky, the way you structure your single-file build is the only way to control this behaviour today. Unfortunately. Thus, I suggest you either keep that library. Or change the value of --self-contained you use for the build (if it is acceptable for you).

What is kinda disappointing is that Roslyn does not deliver any proper solution, and the workaround found and discussed in this thread (using CSharpParseOptions.kind = SourceCodeKind.Script) is kinda flaky and may fail because of the single-file build structuring. But at least it gives us some way of achieving what is needed.

Have a look at the sample. It might be exactly wat you need.

@jackhab
Copy link

jackhab commented May 4, 2025

Oleg,
keeping the script engine in a library is not a problem, I just thought I'm dong something wrong in my project settings.

Thank you very much for this wonderful project and your support effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants