Skip to content
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

Copy dll from global‑packages to output #10518

Closed
MarcoRossignoli opened this issue Sep 25, 2019 · 24 comments
Closed

Copy dll from global‑packages to output #10518

MarcoRossignoli opened this issue Sep 25, 2019 · 24 comments
Milestone

Comments

@MarcoRossignoli
Copy link
Member

Hi I'm Marco from coverlet https://github.com/tonerdo/coverlet

We have an issue with instrumentation through cecil, I'll explain what's happen.
Coverlet is usable through 3 drivers, msbuild, dotnet tool and vstest collectors, and it works for full and core .NET
After a user fire the test command with coverage enabled coverlet instrument needed assemblies.
To do that we use Mono.Cecil.
Mono.Cecil sometimes need to load referenced assemblies, but it fails to load transitive reference(for instance testing apps that usesMicrosoft.Extensions.Logging.Abstractions)
Default cecil resolver does not resolve from global-packages location.
One solution users try is to reference missed dll in csproj, but it doesn't work because there is no way to "copy PackageReference dll to output folder", i.e. on test project:

   ...
   <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
   ...

Now my question: is there a way to force the copy of PackageReference dll on output folder?
Another approach I'm testing is to resolve reference by myself on custom resolver(with one of our user case it works)...but I think's it not possible, because for instance with dotnet tool I'll instrument always with "dotnet core runtime" but I could test full framework app, so I'll load incorrect dll...again with multitarget libs it's a hell, because we need to implement all loading rule with runtime/version matrix.

Current workaround is manual copy dll on output folder.
Coverlet reference issue coverlet-coverage/coverlet#560

cc: @tonerdo

@livarcocc
Copy link
Contributor

This is already the default behavior for 3.0. We will copy all assets during build to the build output. If you use .NET Core 3.0.100, you should see that behavior.

Also, you can force that to happen by setting <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>.

Let us know if any of these work for you.

@MarcoRossignoli
Copy link
Member Author

Did some test

  1. with 3.0 it works as expected
  2. with 2.2 with CopyLocalLockFileAssemblies works only if Microsoft.AspNetCore.All is not referenced, sample repro with empty xunit project template.
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <IsPackable>false</IsPackable>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />

    <PackageReference Include="Microsoft.AspNetCore.All" /> <-- without this it works 
    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
  </ItemGroup>

</Project>

@livarcocc
Copy link
Contributor

How exactly does it fail? It just does not copy the files?

Could you share binlogs for these with us? Or a repro repo on github we could use? If you decide to share binlogs, please take a look at https://aka.ms/binlog.

@MarcoRossignoli
Copy link
Member Author

It just does not copy the files?

Yep, with csproj above I cannot copy Microsoft.Extensions.Logging.Abstractions.dll to output folder if Microsoft.AspNetCore.All is referenced.
Without Microsoft.Extensions.Logging.Abstractions.dll Mono.Cecil fails to "load" dependency.

CoverletRepro.zip

Simple repro above.

  1. Build as is and you won't find Microsoft.Extensions.Logging.Abstractions.dll on output folder
  2. Cleanup all and remove <PackageReference Include="Microsoft.AspNetCore.All" /> from csproj and rebuild and you'll find Microsoft.Extensions.Logging.Abstractions.dll in the output folder

@livarcocc
Copy link
Contributor

When you add the asp.net package, that dll exists in the shared framework, so we are picking it from the shared framework and therefore we are not binplacing it.

@MarcoRossignoli
Copy link
Member Author

That was my suspect...so is there a solution(for runtime under 3.0) or workaround...or the only way is "manual" copy of dll?
As I said above I don't know if it's possible write a custom resolver...because the resoluton matrix is complex and could change in future.

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Sep 30, 2019

that dll exists in the shared framework

Is there a way to know shared framework path(the correct version) through code?I could try to find the lib there.

@MarcoRossignoli
Copy link
Member Author

Or some trick on csproj PackageReference to move dll to output.

@livarcocc
Copy link
Contributor

@dsplaisted @nguerrera any ideas?

@dsplaisted
Copy link
Member

@jeffschwMSFT / @jbevain Is there a good way to programmatically resolve assemblies from the shared framework in order for Mono.Cecil to be able to analyze / instrument user assemblies?

If reference assemblies will work, you may be able to use PreserveCompilationContext, and then the reference assemblies for the shared framework should be included in the output.

@livarcocc
Copy link
Contributor

@MarcoRossignoli would reference assemblies work, as @dsplaisted asked above?

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Oct 8, 2019

@livarcocc I've added <PreserveCompilationContext>true</PreserveCompilationContext> to PropertyGroup but no luck, let me know if this setting is enough.
I'm trying also this way by @jbevain jbevain/cecil#306 (comment) but if run with core 3.0 I don't find any valid path for lib Microsoft.Extensions.Logging.Abstractions and if downgrade to 2.2(the project version of user issue) I get exception for missing Microsoft.Extensions.DependencyModel 3.0

@dasMulli
Copy link
Contributor

dasMulli commented Oct 8, 2019

There was a related discussion at https://github.com/dotnet/core-setup/issues/4975
TL;DR the host doesn't even provide APIs to resolve shared frameworks locations so users would have to re-implement even more of the host's resolution logic. The dependency model API also doesn't process runtimeconfig.json and thus has no knowledge of the shared frameworks being used.

@dasMulli
Copy link
Contributor

dasMulli commented Oct 8, 2019

I've added true to PropertyGroup but no luck,

This should create a ref subdirectory in the build/publish output, but it will only contain reference assemblies.

@MarcoRossignoli
Copy link
Member Author

so users would have to re-implement even more of the host's resolution logic.

@dasMulli thanks's, do you know where is this logic?Some repo link?I know that is fragile and could change in future...but maybe we could reach best-effort.

@dasMulli
Copy link
Contributor

dasMulli commented Oct 8, 2019

Most of it is in https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/multilevel-sharedfx-lookup.md, https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/host-probing.md and other files in that directory.

Though i'm sure you could try some hacks to simplify it whil covering 90%+ of user configurations.
E.g. trying to find out where your current CoreCLR location is (typeof(object).Assembly location) and extracting the current patch version (e.g. "3.0.1" - assuming your tool is always forwarding to the latest version or started with dotnet --runtimeconfig target/app.runtimeconfig.json mytool.dll) and the location of the shared/ folder.
Then you could resolve using the DependencyModel API for everything that is listed in the deps.json and for everything that's not, iterate through the framework+frameworks in the runtimeconfig.json and look in the shared/{FrameworkName}/{CurrentPatchVersion} folders for a matching dll.
This isn't perfect but could probably unblock a large majority of use cases.

@nguerrera
Copy link
Contributor

, but it will only contain reference assemblies.

That seems fine for the purpose at hand to me. Why would you need implementation of framework to instrument user assemblies?

I'd like to understand more about why https://github.com/dotnet/cli/issues/12705#issuecomment-539440201 didn't work out. Do you have a repro of that approach failing? Is it possibly same as disccussed in https://github.com/dotnet/core-setup/issues/8490 where you need to also set PreserveCompilationReferences=true in ASP.NET Core 3.0 projects?

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Oct 8, 2019

Why would you need implementation of framework to instrument user assemblies?

@nguerrera when we instrument Cecil need to load types that are present on shared framework or it fails. The only workaround now is manual copy missing dll to output.

Sample is here https://github.com/dotnet/cli/issues/12705#issuecomment-536685114

66397807-e4126b00-e9dc-11e9-8db3-e7146ee2a055

If I add <PreserveCompilationContext>true</PreserveCompilationContext> seem do nothing

@dasMulli
Copy link
Contributor

dasMulli commented Oct 8, 2019

The question is more if it would be enough for Cecil to load types from reference assemblies for reflection purposes, similar to how the reference assemblies for compilation are passed to roslyn/csc for compilation.

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Oct 8, 2019

The question is more if it would be enough for Cecil to load types from reference assemblies for reflection purposes

In these case I think so...we instrument and re-write only users dll not dependencies.
I mean I'm not sure but I think that cecil need to "inspect" metadata.

@dasMulli
Copy link
Contributor

dasMulli commented Oct 8, 2019

PreserveCompilationContext should however cause your deps.json file to contain references to the DLL files being used (in "compile" lists) which can be found by applying the package name/version and relative path to one of the paths listed in xyz.runtimeconfig.dev.json as additional probing paths.

So in my case:
xyz.runtimeconfig.dev.json:

{
  "runtimeOptions": {
    "additionalProbingPaths": [
      "C:\\Users\\ullrimar\\.dotnet\\store\\|arch|\\|tfm|",
      "C:\\Users\\ullrimar\\.nuget\\packages",
      "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
    ]
  }
}

xyz.deps.json:

      "Microsoft.Extensions.Logging.Abstractions/2.2.0": {
        "compile": {
          "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll": {}
        },
        "compileOnly": true
      },

Can be found in C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.logging.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll

When you call dotnet publish on a project with compilation context preservation it will copy all the compile-references to a refs/ folder, sorry i thought that happened during build as well (not used in a long time)

@MarcoRossignoli
Copy link
Member Author

When you call dotnet publish on a project with compilation context preservation it will copy all the compile-references to a refs/ folder, sorry i thought that happened during build as well (not used in a long time)

Confirm publish work build not
image

So I could try a pair of trick

  1. Add probing directory to Cecil to .\refs if user publish+PreserveCompilationContext should work
  2. Try found dll using Microsoft.Extensions.DependencyModel or manually...does Microsoft.Extensions.DependencyModel handle all these files?

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Oct 30, 2019

@dasMulli @livarcocc @dsplaisted is there a way to load a .deps.json directly other than DependencyContext.Load(?
I'm trying to manually find dll path inside packages folder, with PreserveCompilationContext(on users test project) I get targets in .deps.json file but if I try to load using

 var r = DependencyContext.Load(System.Reflection.Assembly.Load(ms.ToArray())); <- manually binary loaded i.e. case-service.Tests.dll to load case-service.Tests.deps.json in target build folder

I get null ref...I'm inside msbuild task so I think that I cannot use DependencyContext.Default because I need to load "another" .deps.json so my idea is to "load" new context using test assembly.
Instrumentation is and "external process" operation so I need to load "external" .deps.json in a "instrumentation" target folder(usually target output of build where user build test libs)

@MarcoRossignoli
Copy link
Member Author

Thank's to https://github.com/dotnet/core-setup/issues/8805 and <PreserveCompilationContext>true</PreserveCompilationContext> we can find dll, thank's to all for the help!

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

No branches or pull requests

6 participants