ILSupport icon indicating copy to clipboard operation
ILSupport copied to clipboard

Can't build template project

Open Entomy opened this issue 5 years ago • 20 comments

Steps to reproduce:

  • Create C# Class Library (.NET Standard) with IL Support
  • Run dotnet build against it

This presents the following error

Microsoft (R) Build Engine version 16.3.0+0f4c62fea for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 23.11 ms for C:\Users\p-kel\source\repos\IL Experiments\IL Experiments\IL Experiments.csproj.
C:\Users\p-kel\source\repos\IL Experiments\IL Experiments\IL Experiments.csproj(21,5): error MSB4062: The "Microsoft.Build.Tasks.GetFrameworkSdkPath" task could not be loaded from the assembly Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.  Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.

Build FAILED.

C:\Users\p-kel\source\repos\IL Experiments\IL Experiments\IL Experiments.csproj(21,5): error MSB4062: The "Microsoft.Build.Tasks.GetFrameworkSdkPath" task could not be loaded from the assembly Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.  Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:00:00.61

And the output of dotnet --info:

.NET Core SDK (reflecting any global.json):
 Version:   3.0.100
 Commit:    04339c3a26

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.18362
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.0.100\

Host (useful for support):
  Version: 3.0.0
  Commit:  7d57652f33

.NET Core SDKs installed:
  3.0.100 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Considering this is a .NET Standard project I would think this should build regardless, since a non IL Support project would just fine.

Entomy avatar Oct 28 '19 18:10 Entomy

Upon further testing it seems like this happens regardless of the project type.

Moreso, building projects inside Visual Studio causes Visual Studio to report the following error.

The command ""bin\ildasm.exe" /nobar /linenum /output:"C:\Users\p-kel\source\repos\IL Experiments\IL Support\obj\Debug\IL Support.il" "C:\Users\p-kel\source\repos\IL Experiments\IL Support\obj\Debug\IL Support.dll"" exited with code 9009.

My understanding is that 9009 means it was unable to find the tool, but I most definately have it installed.

image

Entomy avatar Oct 28 '19 18:10 Entomy

From this error, it seems $(FrameworkSdkPath) is empty under .NET Core 3.0.

The location fallbacks for ILDasm are those lines in your csproj: https://github.com/ins0mniaque/ILSupport/blob/7e440fbf71930a12449560d6fcaca0e9c13ba6bb/IL%20Support.ProjectTemplates/IL%20Support.targets#L26-L43

Meanwhile, you can add another fallback pointing directly to ILDasm. The correct fix would be to find the variable that gives the correct $(FrameworkSdkPath) under .NET Core 3.0.

I'll investigate as soon as possible.

ins0mniaque avatar Oct 28 '19 20:10 ins0mniaque

Okay thanks, this sets me on the right path, so I can look into it as well.

Entomy avatar Oct 28 '19 20:10 Entomy

A small tip for debugging... You can put: <Message Importance="High" Text="Test: $(FrameworkSdkPath)" /> inside the <Target> tag to print out variables when building.

ins0mniaque avatar Oct 28 '19 21:10 ins0mniaque

I found these $(Win10SDKBinPath) $(Win10SDKVersion) which I'm pretty sure are the correct variables. I'll play around with getting this to work tomorrow.

Entomy avatar Oct 30 '19 02:10 Entomy

Apparently I misunderstood what I was looking at. Those were locally created variables, that were filled in by looking for a specific registry key (which of course doesn't exist anymore either).

Entomy avatar Oct 30 '19 14:10 Entomy

I looked around a bit for a solution, and it appears it might be a bit more complicated than expected.

To have access to ILDasm with .NET Core, and to make it work cross-platform, it seems I'll have to make new .NET Core / Standard project templates that use the Microsoft.NETCore.ILDAsm NuGet package, and then somehow figure out where the binary gets installed.

On macOS, it seems to be ~/.nuget/packages/runtime.osx-x64.microsoft.netcore.ildasm/2.0.8/runtimes/osx-x64/native/ildasm. I expect it to be %UserProfile%\.nuget\packages\runtime.win-x64.microsoft.netcore.ildasm\2.0.8\runtimes\win-x64\native\ildasm.exe or something similar.

The package reference looks like this: <PackageReference Include="Microsoft.NETCore.ILDAsm" Version="2.0.8" />

You could try adding this package to your project, and then a fallback for: %UserProfile%\.nuget\packages\runtime.win-x64.microsoft.netcore.ildasm\2.0.8\runtimes\win-x64\native\ildasm.exe or whatever is the correct path to ILDasm is.

It might be necessary to instead reference the "runtime.win-x64.Microsoft.NETCore.ILDAsm" package to have the binary installed.

This issue on the dotnet repository is tracking the problem: https://github.com/dotnet/cli/issues/6223

ins0mniaque avatar Oct 30 '19 14:10 ins0mniaque

In that thread there was this: dotnet-ildasm which might abstract away a lot of the complexities of getting the right tool.

Entomy avatar Oct 30 '19 15:10 Entomy

This might be a viable solution, and could be included in the project with: <DotNetCliToolReference Include="dotnet-ildasm" Version="0.11.0" /> but I'm not sure where the ildasm.exe binary will end up.

However, I'd rather have a solution that does not include NuGet packages not maintained by Microsoft, for security reasons.

ins0mniaque avatar Oct 30 '19 16:10 ins0mniaque

I completely understand that. I'll look into the nuget packages and where they dump their contents.

Entomy avatar Oct 30 '19 16:10 Entomy

I randomly stumbled upon this: https://github.com/ntscosta/TaxAuditCommunity-Codes/blob/master/src/corefx-master/src/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.ilproj

While it uses a different SDK ("Microsoft.NET.Sdk.IL"), it seems to be able to call ildasm on .NET Core by using the $(NuGetPackageRoot) variable. So maybe $(NuGetPackageRoot)\packages\runtime.win-x64.microsoft.netcore.ildasm\2.0.8\runtimes\win-x64\native\ildasm.exe or $(NuGetPackageRoot)\runtime.win-x64.microsoft.netcore.ildasm\2.0.8\runtimes\win-x64\native\ildasm.exe would work.

ins0mniaque avatar Oct 30 '19 17:10 ins0mniaque

And that approach should work for any platform. Perfect. I'll play around with it.

Entomy avatar Oct 30 '19 17:10 Entomy

Here's where I'm at.

Microsoft.NETCore.ILDasm doesn't seem to actually contain anything remotely useful.

image

And in fact nothing useful is pulled down when a restore occurs

image

Which then requires instead to reference the specific runtime.$(platformName)-$(architecture).Microsoft.NETCore.ILDAsm package which actually does.

And also sometimes there's a $(platformVersion) after the name, but only sometimes.

image

This same problem exists with Microsoft.NETCore.ILAsm

So the tools are available. However...

It's not runnable in the state nuget fetches it in.

image

This can be dealt with however. The runtime library ildasm needs is irrelevant to the runtime library of the project, so it's a non-issue, just an inconvenience.

Right?

Well this time around it's runtime.$(platformName)$(platformVer)-$(architecture).Microsoft.NETCore.Runtime.CoreCLR

or runtime.$(platformName)-$(architecture).Microsoft.NETCore.App

Good luck figuring out which one is needed, it's remarkably inconsistent from what I can tell.

But once done, does yeild a working ildasm.

image

From there, I built a multitarget library for about 9 different targets, covering .NET Framework, .NET Core, and .NET Standard, of both new and old versions. All we able to be disassembled.

So it seems like this is possible, just a massive PIA.

Entomy avatar Oct 30 '19 22:10 Entomy

Thank you for taking the time to figure all this out, and it got me searching in the right place.

After a while I found this: https://github.com/dotnet/coreclr/blob/master/src/.nuget/Microsoft.NET.Sdk.IL/targets/Microsoft.NET.Sdk.IL.targets

Which led me to this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <PropertyGroup>
    <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('windows'))">win</_OSPlatform>
    <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('linux'))">linux</_OSPlatform>
    <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('osx'))">osx</_OSPlatform>
    <_OSPlatform Condition="$([MSBuild]::IsOSPlatform('freebsd'))">freebsd</_OSPlatform>
    <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)</_OSArchitecture>

    <MicrosoftNetCoreIlasmPackageRuntimeId>$(_OSPlatform)-$(_OSArchitecture.ToLower())</MicrosoftNetCoreIlasmPackageRuntimeId>

    <MicrosoftNetCoreIlasmPackageName>runtime.$(MicrosoftNetCoreIlasmPackageRuntimeId).microsoft.netcore.ilasm</MicrosoftNetCoreIlasmPackageName>
    <MicrosoftNetCoreIlasmPackageVersion>2.0.8</MicrosoftNetCoreIlasmPackageVersion>
    <MicrosoftNetCoreIlasm>$(NuGetPackageRoot)\$(MicrosoftNetCoreIlasmPackageName)\$(MicrosoftNetCoreIlasmPackageVersion)\runtimes\$(MicrosoftNetCoreIlasmPackageRuntimeId)\native\ilasm</MicrosoftNetCoreIlasm>

    <MicrosoftNetCoreIldasmPackageName>runtime.$(MicrosoftNetCoreIlasmPackageRuntimeId).microsoft.netcore.ildasm</MicrosoftNetCoreIldasmPackageName>
    <MicrosoftNetCoreIldasmPackageVersion>2.0.8</MicrosoftNetCoreIldasmPackageVersion>
    <MicrosoftNetCoreIldasm>$(NuGetPackageRoot)\$(MicrosoftNetCoreIldasmPackageName)\$(MicrosoftNetCoreIldasmPackageVersion)\runtimes\$(MicrosoftNetCoreIlasmPackageRuntimeId)\native\ildasm</MicrosoftNetCoreIldasm>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="$(MicrosoftNetCoreIlasmPackageName)" Version="$(MicrosoftNetCoreIlasmPackageVersion)" PrivateAssets="all" />
    <PackageReference Include="$(MicrosoftNetCoreIldasmPackageName)" Version="$(MicrosoftNetCoreIldasmPackageVersion)" PrivateAssets="all" />
  </ItemGroup>

  <Target Name="DisplayTestMessage" BeforeTargets="BeforeBuild">
    <Message Importance="High" Text="Package: $(MicrosoftNetCoreIlasmPackageName)" />
    <Message Importance="High" Text="Binary: $(MicrosoftNetCoreIlasm)" />
    <Message Importance="High" Text="Package: $(MicrosoftNetCoreIldasmPackageName)" />
    <Message Importance="High" Text="Binary: $(MicrosoftNetCoreIldasm)" />
  </Target>

</Project>

which on macOS outputs the correct path for ilasm/ildasm (and it runs):

  Package: runtime.osx-x64.microsoft.netcore.ilasm
  Binary: ~/.nuget/packages/runtime.osx-x64.microsoft.netcore.ilasm/2.0.8/runtimes/osx-x64/native/ilasm
  Package: runtime.osx-x64.microsoft.netcore.ildasm
  Binary: ~/.nuget/packages/runtime.osx-x64.microsoft.netcore.ildasm/2.0.8/runtimes/osx-x64/native/ildasm

Still not sure how to deal with CoreCLR. I don't seem to have a package for it, but ildasm ran correctly. I'll do some tests on Windows when I have time.

And I haven't found a way to not target a specific version. I tried briefly using wildcards and an ItemGroup like in Microsoft.NET.Sdk.IL.targets, but couldn't make work.

ins0mniaque avatar Nov 02 '19 05:11 ins0mniaque

image

Path resolution looks okay on Windows.

I'll look into ways to deal with the library.

Entomy avatar Nov 02 '19 12:11 Entomy

Thank you for taking the time to figure all this out

Hey no problem. I do FOSS development too, and know what it's like when users just go "hey this is broke, fix it". I'm not familiar with MSBuild, but I can do what I can to help out.

Entomy avatar Nov 02 '19 12:11 Entomy

I can't seem to find a way around this that isn't referencing the specific library package and manually moving it over.

Entomy avatar Nov 07 '19 14:11 Entomy

I didn't have time to test this, but I found out that running dotnet list package outputs the following:

Project 'ildasm_crap' has the following package references
   [netcoreapp3.0]:
   Top-level Package                               Requested   Resolved
   > runtime.osx-x64.microsoft.netcore.ilasm       2.0.8       2.0.8
   > runtime.osx-x64.microsoft.netcore.ildasm      2.0.8       2.0.8

It might be possible to reference the package using: <PackageReference Include="$(MicrosoftNetCoreIldasmPackageName)" Version="*" PrivateAssets="all" /> and then extract the version number from dotnet list package in a target that runs after Nuget Packages are restored.

Such a target would be defined like so:

<Target BeforeTargets="BeforeBuild" AfterTargets="Restore">
    ... somehow set MicrosoftNetCoreIldasmPackageVersion here using "dotnet list package" ...
</Target>

ins0mniaque avatar Nov 09 '19 16:11 ins0mniaque

This is really beyond my knowledge of MSBuild and the project syntax. I don't think I'm going to be of much help at this point. I've tried a bit but I'm completely lost.

Entomy avatar Nov 13 '19 16:11 Entomy

Frankly, all of this is getting hackish, and for the new .NET projects, it should probably be published as a project SDK, so the usage would simply be to change the SDK from: <Project Sdk="Microsoft.NET.Sdk"> to <Project Sdk="ILSupport.NET.Sdk"> and targets would be automatically imported. And by the time I get to do this, maybe Microsoft will have added a better way to call ildasm.

And all this aside, a better way to use custom IL is probably to move all the IL code to an .ilproj with the Microsoft.NET.Sdk.IL project SDK as in the System.Runtime.CompilerServices.Unsafe project: https://github.com/dotnet/corefx/blob/master/src/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.ilproj

It's not published to NuGet yet, but it seems people are able to use it: https://github.com/dotnet/coreclr/issues/20816

ins0mniaque avatar Nov 13 '19 16:11 ins0mniaque