Brightspace / rules_csharp

Bazel rules for C#
Apache License 2.0
8 stars 5 forks source link

Creation of .NET executable file broken with .NET 5 SDK #148

Open jimevans opened 3 years ago

jimevans commented 3 years ago

When building an executable assembly (csharp_binary), the C# compiler has changed its behavior with the release of .NET 5.0. Previously, the C# compiler would simply create the executable assembly, with a .exe extension, and it was possible to execute dotnet <assembly name>.exe and have the executable run. This is the pattern used by these C# Bazel rules. This is no longer the case with .NET 5.0.

Now, the compiler emits two assemblies, <assembly name>.dll and <assembly name>.exe with an <assembly name>.deps.json file that references the dll (Note: I do not yet know if the json file is generated by csc.exe, or by MSBuild). With the binaries created by MSBuild/Visual Studio, it is possible to execute the created .dll using dotnet <assembly name>.dll, or to execute <assembly name>.exe directly. Accordingly, the Bazel rules should ideally emit the same file list that the MSBuild task emits, but an acceptable solution would be to just emit the .dll that can be executed via dotnet <assembly name>.dll.

j3parker commented 3 years ago

Oh neat! Thanks for the report.

jimevans commented 3 years ago

Okay, after some further digging, it looks like building the actual .exe will be challenging, because it's not done by the compiler directly. However, I think an executable dll assembly can be emitted, but it needs one, and possibly two, things. First, it the rules need to not set the extension to .exe when running the csharp_binary rule.

Second, it may need an emitted "AssemblyAttributes.cs" source file to be generated that has the framework version attribute. This appears to be generated by Visual Studio under the obj directory, and will have content similar to this:

// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v5.0", FrameworkDisplayName = "")]

What actions need to happen to actually create the executable file (.exe) will need some further investigation.

jimevans commented 3 years ago

I'll attempt to generate a PR for at least the temporary (dll-only) solution ASAP.

j3parker commented 3 years ago

Cool, thanks! That sounds good. I'd like to investigate what their exe is doing now. For fixing stuff like #9 the WIP fix I have (urg) involves a little stub exe, which is a little similar... I wonder if they're doing something like a little C wrapper + embedding the DLL as a resource at the end of the exe or something like that.

jimevans commented 3 years ago

@j3parker The immediate "cannot execute an executable assembly at all" issue is now fixed with PR #145 (merged in bf24e58). I strongly suspect that you're on the right track as to what the VS-generated executable is actually doing. The .exe generated is not a raw .NET assembly. In the VS build process, there's a CreateAppHost task that gets called. I suspect the magic, such as it is, happens there.

j3parker commented 3 years ago

Ahh good pointer. That got me to https://github.com/dotnet/runtime/blob/master/src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs and yeah it looks something like that.

It sounds like we probably want to mimic CreateAppHost. I wonder if only for .NET 5+ though?

I think probably the app host output would be a separate build output, and we'd probably still need to do special assembly loading stuff to solve something like #9 (where we want to use the DLLs spread out across the disk in the way Bazel likes).