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

Behavior of dependency resolution #63617

Closed
Falco20019 opened this issue Jan 11, 2022 · 12 comments
Closed

Behavior of dependency resolution #63617

Falco20019 opened this issue Jan 11, 2022 · 12 comments
Labels
area-Host untriaged New issue has not been triaged by the area owner

Comments

@Falco20019
Copy link

Falco20019 commented Jan 11, 2022

I ran into an issue, that when an assembly that was added as runtime asset through the framework (i.e. Microsoft.NETCore.App.deps.json), when it's changed to a later version (i.e. through another PackageReference dependency), the application is crashing if the applications .deps.json is missing.

I have a somewhat complex setup:

  • Multiple applications (.NET Core Apps)
  • Shared DLLs between them, that need to use dependency resolution to find the common one
  • The application dynamically loads some of the shared DLLs at runtime, so it's not possible to create a .deps.json file when the application is created
  • At deployment, a shared .deps.json is created that contains all information automatically

We use one MSBuild that has PackageReferences on all tools and publish that csproj to get a folder that includes all DLLs that would work together. It's not possible to create a application specific dependency file for each tool, since only one for the whole folder is created. If I run the application with dotnet exec --depsfile <GenerationProject>.deps.json <ToolName>.dll, everything works fine. Running it without depsfile argument, I would still expect the apphost's runtime to prefer the DLL that is in the probe directory over any version coming from the framework. Sadly, that's not the case. It will replace the one found in the framework.

I create a MCVE that shows this behavior.

Test.csproj

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

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

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.9.0" />
  </ItemGroup>

</Project>

Program.cs

using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var type = typeof(System.Collections.Immutable.ImmutableStack);
            Console.WriteLine(type.Name);
        }
    }
}

When publishing it, everything works. Once you remove the Test.deps.json, you will get:

Unhandled exception. System.IO.FileLoadException: Could not load file or assembly 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The located assembly's manifest definition does not match the assembly reference. (0x80131040)
File name: 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at test.Program.Main(String[] args)

Running this with COREHOST_TRACE on, I was able to trace the issue down to this:

Tracing enabled @ Tue Jan 11 10:36:43 2022 GMT
--- Invoked dotnet [version: 6.0.1, commit hash: 3a25a7f1cc446b60678ed25c9d829420d6321eba] main = {
dotnet
exec
Test.dll
}
Reading fx resolver directory=[C:\PROGRA~1\dotnet\host\fxr]
Considering fxr version=[2.1.30]...
Considering fxr version=[3.1.13]...
Considering fxr version=[3.1.19]...
Considering fxr version=[5.0.13]...
Considering fxr version=[5.0.9]...
Considering fxr version=[6.0.0]...
Considering fxr version=[6.0.1]...
Detected latest fxr version=[C:\PROGRA~1\dotnet\host\fxr\6.0.1]...
Resolved fxr [C:\PROGRA~1\dotnet\host\fxr\6.0.1\hostfxr.dll]...
Loaded library from C:\PROGRA~1\dotnet\host\fxr\6.0.1\hostfxr.dll
Invoking fx resolver [C:\PROGRA~1\dotnet\host\fxr\6.0.1\hostfxr.dll] hostfxr_main_startupinfo
Host path: [C:\PROGRA~1\dotnet\dotnet.exe]
Dotnet path: [C:\PROGRA~1\dotnet\]
App path: [C:\PROGRA~1\dotnet\dotnet.dll]
Tracing enabled @ Tue Jan 11 10:36:43 2022 GMT
--- Invoked hostfxr_main_startupinfo [commit hash: 3a25a7f1cc446b60678ed25c9d829420d6321eba]
Checking if CoreCLR path exists=[C:\PROGRA~1\dotnet\coreclr.dll]
--- Executing in muxer mode...
Using the provided arguments to determine the application to execute.
Using dotnet root path [C:\PROGRA~1\dotnet\]
App runtimeconfig.json from [...\Test\bin\Release\netcoreapp3.1\publish\Test.dll]
Runtime config is cfg=...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json dev=...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.dev.json
Attempting to read runtime config: ...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json
Attempting to read dev runtime config: ...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.dev.json
Runtime config [...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json] is valid=[1]
--- The specified framework 'Microsoft.NETCore.App', version '3.1.0', apply_patches=1, version_compatibility_range=minor is compatible with the previously referenced version '3.1.0'.
--- Resolving FX directory, name 'Microsoft.NETCore.App' version '3.1.0'
...
Chose FX version [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22]
Runtime config is cfg=C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.json dev=C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.dev.json
Attempting to read runtime config: C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.json
Attempting to read dev runtime config: C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.dev.json
Runtime config [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.json] is valid=[1]
--- Summary of all frameworks:
     framework:'Microsoft.NETCore.App', lowest requested version='3.1.0', found version='3.1.22', effective reference version='3.1.0' apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, folder=C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22
Executing as a framework-dependent app as per config file [...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json]
--- Resolving hostpolicy.dll version from deps json [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json]
Resolved version 3.1.22 from dependency manifest file [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json]
Did not find hostpolicy.dll in directory C:\Program Files (x86)\coreservicing\pkgs\runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy\3.1.22\runtimes\win-x64\native
The expected hostpolicy.dll directory is [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22]
Loaded library from C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\hostpolicy.dll
Tracing enabled @ Tue Jan 11 10:36:43 2022 GMT
Reading from host interface version: [0x16041101:248] to initialize policy version: [0x16041101:240]
--- Invoked hostpolicy [commit hash: 35fa579a3029aef544cfb0b337eb77b882244d80] [runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy,3.1.22,runtimes/win-x64/native][x64] corehost_main = {
dotnet
Test.dll
}
Deps file: 
-- arguments_t: host_path='C:\PROGRA~1\dotnet\dotnet.exe' app_root='...\Test\bin\Release\netcoreapp3.1\publish\' deps='...\Test\bin\Release\netcoreapp3.1\publish\Test.deps.json' core_svc='C:\Program Files (x86)\coreservicing' mgd_app='...\Test\bin\Release\netcoreapp3.1\publish\Test.dll'
-- arguments_t: dotnet shared store: 'C:\PROGRA~1\dotnet\store\x64\netcoreapp3.1'
-- arguments_t: global shared store: 'C:\Program Files\dotnet\store\x64\netcoreapp3.1'
Using Fx C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json deps file
Loading deps file... C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json as framework dependent=[0]
...
Adding runtime asset runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll assemblyVersion=1.2.5.0 fileVersion=4.700.21.57101 from runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3
...
Reconciling library runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3
...
Parsed runtime deps entry 8 for asset name: System.Collections.Immutable from package: runtime.win-x64.Microsoft.NETCore.App, library version: 3.1.22-servicing.21571.3, relpath: runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll, assemblyVersion 1.2.5.0, fileVersion 4.700.21.57101
...
Using ...\Test\bin\Release\netcoreapp3.1\publish\Test.deps.json deps file
Could not locate the dependencies manifest file [...\Test\bin\Release\netcoreapp3.1\publish\Test.deps.json]. Some libraries may fail to resolve.
-- Listing probe configurations...
probe_config_t: probe=[] deps-dir-probe=[1]
probe_config_t: probe=[C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22] deps-dir-probe=[0]
...
Adding System.Collections.Immutable to local assembly set from ...\Test\bin\Release\netcoreapp3.1\publish\System.Collections.Immutable.dll
Adding tpa entry: ...\Test\bin\Release\netcoreapp3.1\publish\System.Collections.Immutable.dll, AssemblyVersion: , FileVersion: 
...
Processing TPA for deps entry [runtime.win-x64.Microsoft.NETCore.App, 3.1.22-servicing.21571.3, runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll]
  Considering entry [runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3/runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll], probe dir [], probe fx level:0, entry fx level:1
    Skipping... not found in deps dir 'C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22'
    Skipping... not found in probe dir ''
  Considering entry [runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3/runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll], probe dir [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22], probe fx level:1, entry fx level:1
    Local path query exists C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll
    Probed deps json and matched 'C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll'
Replacing deps entry [...\Test\bin\Release\netcoreapp3.1\publish\System.Collections.Immutable.dll, AssemblyVersion:, FileVersion:] with [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll, AssemblyVersion:1.2.5.0, FileVersion:4.700.21.57101]
Adding tpa entry: C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll, AssemblyVersion: 1.2.5.0, FileVersion: 4.700.21.57101
...
Unhandled exception. System.IO.FileLoadException: Could not load file or assembly 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The located assembly's manifest definition does not match the assembly reference. (0x80131040)
File name: 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at Test.Program.Main(String[] args)

Since it's adding the one from the folder with AssemblyVersion: , FileVersion: , it seems to find the one from the framework to be more specific or the correct one.

The version 5 of System.Collections.Immutable is coming from referencing Microsoft.CodeAnalysis.CSharp.Scripting which itself references Microsoft.CodeAnalysis.Common, so it's nothing that I have a lot of influence on. In my real application, I reference the scripting package in a library targeting netstandard21 which then is referenced itself by two bootstrappers, one for .NET Core 3.1 and one for .NET 5. So I also would not want to force the DLL to a specific version, as I expect the publishing step to take care of this as part of the resolution phase.

Is there a way to enforce the versions to be loaded correctly from the local folder without referencing the deps-file? When referencing the deps-file, the line includes System.Collections.Immutable.dll, AssemblyVersion: 5.0.0.0, FileVersion: 5.0.20.51904. Sadly, also using --additional-deps is not helping, since it's also resolved using AssemblyVersion: , FileVersion: and gets replaced by the older version. So I found no other working solution than calling the application with the --depsfile, copying the combined .deps.json for each tools filename or by implementing the handler AppDomain.CurrentDomain.AssemblyResolve to load the one from the folder manually at runtime in each tool.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-Host untriaged New issue has not been triaged by the area owner labels Jan 11, 2022
@ghost
Copy link

ghost commented Jan 11, 2022

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

Issue Details

I ran into an issue, that when an assembly that was added as runtime asset through the framework (i.e. Microsoft.NETCore.App.deps.json), when it's changed to a later version (i.e. through another PackageReference dependency), the application is crashing if the applications .deps.json is missing.

I have a somewhat complex setup:

  • Multiple applications (.NET Core Apps)
  • Shared DLLs between them, that need to use dependency resolution to find the common one

We use one MSBuild that has PackageReferences on all tools and publish that csproj to get a folder that includes all DLLs that would work together. It's not possible to create a application specific dependency file for each tool, since only one for the whole folder is created. If I run the application with dotnet exec --depsfile <GenerationProject>.deps.json <ToolName>.dll, everything works fine. Running it without depsfile argument, I would still expect the apphost's runtime to prefer the DLL that is in the probe directory over any version coming from the framework. Sadly, that's not the case. It will replace the one found in the framework.

I create a MCVE that shows this behavior.

Test.csproj

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

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

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.9.0" />
  </ItemGroup>

</Project>

Program.cs

using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var type = typeof(System.Collections.Immutable.ImmutableStack);
            Console.WriteLine(type.Name);
        }
    }
}

When publishing it, everything works. Once you remove the Test.deps.json, you will get:

Unhandled exception. System.IO.FileLoadException: Could not load file or assembly 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The located assembly's manifest definition does not match the assembly reference. (0x80131040)
File name: 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at test.Program.Main(String[] args)

Running this with COREHOST_TRACE on, I was able to trace the issue down to this:

Tracing enabled @ Tue Jan 11 10:36:43 2022 GMT
--- Invoked dotnet [version: 6.0.1, commit hash: 3a25a7f1cc446b60678ed25c9d829420d6321eba] main = {
dotnet
exec
Test.dll
}
Reading fx resolver directory=[C:\PROGRA~1\dotnet\host\fxr]
Considering fxr version=[2.1.30]...
Considering fxr version=[3.1.13]...
Considering fxr version=[3.1.19]...
Considering fxr version=[5.0.13]...
Considering fxr version=[5.0.9]...
Considering fxr version=[6.0.0]...
Considering fxr version=[6.0.1]...
Detected latest fxr version=[C:\PROGRA~1\dotnet\host\fxr\6.0.1]...
Resolved fxr [C:\PROGRA~1\dotnet\host\fxr\6.0.1\hostfxr.dll]...
Loaded library from C:\PROGRA~1\dotnet\host\fxr\6.0.1\hostfxr.dll
Invoking fx resolver [C:\PROGRA~1\dotnet\host\fxr\6.0.1\hostfxr.dll] hostfxr_main_startupinfo
Host path: [C:\PROGRA~1\dotnet\dotnet.exe]
Dotnet path: [C:\PROGRA~1\dotnet\]
App path: [C:\PROGRA~1\dotnet\dotnet.dll]
Tracing enabled @ Tue Jan 11 10:36:43 2022 GMT
--- Invoked hostfxr_main_startupinfo [commit hash: 3a25a7f1cc446b60678ed25c9d829420d6321eba]
Checking if CoreCLR path exists=[C:\PROGRA~1\dotnet\coreclr.dll]
--- Executing in muxer mode...
Using the provided arguments to determine the application to execute.
Using dotnet root path [C:\PROGRA~1\dotnet\]
App runtimeconfig.json from [...\Test\bin\Release\netcoreapp3.1\publish\Test.dll]
Runtime config is cfg=...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json dev=...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.dev.json
Attempting to read runtime config: ...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json
Attempting to read dev runtime config: ...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.dev.json
Runtime config [...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json] is valid=[1]
--- The specified framework 'Microsoft.NETCore.App', version '3.1.0', apply_patches=1, version_compatibility_range=minor is compatible with the previously referenced version '3.1.0'.
--- Resolving FX directory, name 'Microsoft.NETCore.App' version '3.1.0'
...
Chose FX version [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22]
Runtime config is cfg=C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.json dev=C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.dev.json
Attempting to read runtime config: C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.json
Attempting to read dev runtime config: C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.dev.json
Runtime config [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.runtimeconfig.json] is valid=[1]
--- Summary of all frameworks:
     framework:'Microsoft.NETCore.App', lowest requested version='3.1.0', found version='3.1.22', effective reference version='3.1.0' apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, folder=C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22
Executing as a framework-dependent app as per config file [...\Test\bin\Release\netcoreapp3.1\publish\Test.runtimeconfig.json]
--- Resolving hostpolicy.dll version from deps json [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json]
Resolved version 3.1.22 from dependency manifest file [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json]
Did not find hostpolicy.dll in directory C:\Program Files (x86)\coreservicing\pkgs\runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy\3.1.22\runtimes\win-x64\native
The expected hostpolicy.dll directory is [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22]
Loaded library from C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\hostpolicy.dll
Tracing enabled @ Tue Jan 11 10:36:43 2022 GMT
Reading from host interface version: [0x16041101:248] to initialize policy version: [0x16041101:240]
--- Invoked hostpolicy [commit hash: 35fa579a3029aef544cfb0b337eb77b882244d80] [runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy,3.1.22,runtimes/win-x64/native][x64] corehost_main = {
dotnet
Test.dll
}
Deps file: 
-- arguments_t: host_path='C:\PROGRA~1\dotnet\dotnet.exe' app_root='...\Test\bin\Release\netcoreapp3.1\publish\' deps='...\Test\bin\Release\netcoreapp3.1\publish\Test.deps.json' core_svc='C:\Program Files (x86)\coreservicing' mgd_app='...\Test\bin\Release\netcoreapp3.1\publish\Test.dll'
-- arguments_t: dotnet shared store: 'C:\PROGRA~1\dotnet\store\x64\netcoreapp3.1'
-- arguments_t: global shared store: 'C:\Program Files\dotnet\store\x64\netcoreapp3.1'
Using Fx C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json deps file
Loading deps file... C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\Microsoft.NETCore.App.deps.json as framework dependent=[0]
...
Adding runtime asset runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll assemblyVersion=1.2.5.0 fileVersion=4.700.21.57101 from runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3
...
Reconciling library runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3
...
Parsed runtime deps entry 8 for asset name: System.Collections.Immutable from package: runtime.win-x64.Microsoft.NETCore.App, library version: 3.1.22-servicing.21571.3, relpath: runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll, assemblyVersion 1.2.5.0, fileVersion 4.700.21.57101
...
Using ...\Test\bin\Release\netcoreapp3.1\publish\Test.deps.json deps file
Could not locate the dependencies manifest file [...\Test\bin\Release\netcoreapp3.1\publish\Test.deps.json]. Some libraries may fail to resolve.
-- Listing probe configurations...
probe_config_t: probe=[] deps-dir-probe=[1]
probe_config_t: probe=[C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22] deps-dir-probe=[0]
...
Adding System.Collections.Immutable to local assembly set from ...\Test\bin\Release\netcoreapp3.1\publish\System.Collections.Immutable.dll
Adding tpa entry: ...\Test\bin\Release\netcoreapp3.1\publish\System.Collections.Immutable.dll, AssemblyVersion: , FileVersion: 
...
Processing TPA for deps entry [runtime.win-x64.Microsoft.NETCore.App, 3.1.22-servicing.21571.3, runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll]
  Considering entry [runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3/runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll], probe dir [], probe fx level:0, entry fx level:1
    Skipping... not found in deps dir 'C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22'
    Skipping... not found in probe dir ''
  Considering entry [runtime.win-x64.Microsoft.NETCore.App/3.1.22-servicing.21571.3/runtimes/win-x64/lib/netcoreapp3.1/System.Collections.Immutable.dll], probe dir [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22], probe fx level:1, entry fx level:1
    Local path query exists C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll
    Probed deps json and matched 'C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll'
Replacing deps entry [...\Test\bin\Release\netcoreapp3.1\publish\System.Collections.Immutable.dll, AssemblyVersion:, FileVersion:] with [C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll, AssemblyVersion:1.2.5.0, FileVersion:4.700.21.57101]
Adding tpa entry: C:\PROGRA~1\dotnet\shared\Microsoft.NETCore.App\3.1.22\System.Collections.Immutable.dll, AssemblyVersion: 1.2.5.0, FileVersion: 4.700.21.57101
...
Unhandled exception. System.IO.FileLoadException: Could not load file or assembly 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The located assembly's manifest definition does not match the assembly reference. (0x80131040)
File name: 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at Test.Program.Main(String[] args)

Since it's adding the one from the folder with AssemblyVersion: , FileVersion: , it seems to find the one from the framework to be more specific or the correct one.

Is there a way to enforce the versions to be loaded correctly from the local folder without referencing the deps-file? When referencing the deps-file, the line includes System.Collections.Immutable.dll, AssemblyVersion: 5.0.0.0, FileVersion: 5.0.20.51904. Sadly, also using additional-deps is not helping, since it's also resolved using AssemblyVersion: , FileVersion: and gets replaced by the older version.

Author: Falco20019
Assignees: -
Labels:

area-Host, untriaged

Milestone: -

@vitek-karas
Copy link
Member

I can't think of a way to do this without the .deps.json. The host has a hardcoded rule that it prefers entries with higher assembly version (and if equal then higher file version). Entries without version info are considered to have version lower than any specified version (the implementation uses version -1.-1.-1.-1 which compares as lower than any non-negative version), so the entry with version always wins over the one without version. I don't know if this was intentional or side-effect, but I can't see us changing this without a really good reason.

I do know that the version comparison was added for exactly cases like this, where the app carries its own version of an assembly which also exists in the framework. And it intentionally picks the highest version because it can't blindly pick the one from the app (for example, if the app was built for .NET 3.1 using some custom 3.1 version of a framework assembly, then trying to run this app on .NET 6 needs to upgrade the dependency to 6 as well, otherwise the framework would not work).

In all these cases we rarely consider apps without .deps.json as the SDK will pretty much always produce the .deps.json and it's integral part of the app. Basically removing the .deps.json is border-line unsupported, the product has code to deal with it, but for the most part we rely on the .deps.json for any more complex cases.

I understand that if you want to share assemblies between several apps it's problematic. We've been discussing ways to enable this but no concrete plans exist yet. For self-contained apps we're considering this: #53834. But it doesn't solve sharing non-framework assemblies.

@Falco20019
Copy link
Author

Falco20019 commented Jan 11, 2022

Thanks for your reply. I can understand, that it's hard to figure out, if the information is missing. But when I just rename the dependency file (to simulate the case, where I have the shared deps file), I would still assume, that it would behave exactly as if it would be using a deps file with the application name (or given using depsfile). But in my test, it shows, that additional-deps behaves differently than depsfile.

I ran it through dotnet exec --additional-deps _Test.deps.json Test.dll and dotnet exec --additional-deps _Test.deps.json Test.dll. The first looks pretty similar to if I would have not given it the dependency file at all.

For easier comparison, have a look at this screenshot:
image

Look especially for this line: Adding tpa entry: ...\System.Collections.Immutable.dll, AssemblyVersion: 5.0.0.0, FileVersion: 5.0.20.51904. This is missing in the additional-deps case but given in the depsfile case. I would have expected both to behave similar.

I only found https://github.com/dotnet/runtime/blob/main/docs/design/features/additional-deps.md so maybe it is related to the change of order? The additional deps would be able to at least be set through DOTNET_ADDITIONAL_DEPS, but AFAIK, there is no way to set the depsfile directly through an environment variable or through the runtime configuration. Being able to set this (either fix in the runtime config though a MSBuild property or as environment variable) would already work in our case.

My only other workaround would be, to copy the file to be found as application specific (which it isn't). From the applications perspective, these are additional, since they are not part of the tools build time dependencies. So even if I keep the one generated with the app, it's complaining, that it doesn't find the correct version. But due to those matching against the framework ones instead of the ones from the adds.deps.json, this is basically the main issue.

@vitek-karas
Copy link
Member

I think the additional deps doesn't work because:

  1. The app directory itself - without .deps.json, so no versions - path is <app path>/System.Collections.Immutable.dll
  2. The additional deps - with version - path is the same so <app path>/System.Collections.Immutable.dll
  3. The framework - with lower version - path is something like <path to install>/System.Collections.Immutable.dll

Number 1 is processed first and added to the result.
Number 2 is processed, but it has the same "level" as the app, so there's no version checks, it's basically as if the additional deps was appended to the app's deps file. For this case there's only a simple protection which doesn't add a second entry to the list if it's the same path - which it is now
Number 3 is processed, since this is framework, it's a different "level" and so version checks are performed. The framework version wins and so the result is replaces with the one from the framework.

I didn't really test this manually though, so I could be wrong.

@Falco20019
Copy link
Author

Falco20019 commented Jan 12, 2022

It would at least explain the seen behavior. Is there any possible way (besides copy/renaming the deps file for each application) to hint the executable to use that one other than using the --depsfile argument? This especially creates a problem for us, because we have a tool that get's files dropped onto it. Therefore, it's hard to also add that argument there. With the additional files, it would have been solvable through the environment variables. But this is also not our best shot, since it would not be folder specific but user- or system-wide.

It would have been great, to have a fourth option of dependency file between the app-specific and the additional deps one, but being treated as strong as the app-specific one. So basically forcing the version defined there to be considered (instead of just adding them with -1). Basically, something along the line of global.json or Directory.Build.[targets|props]. Technically, the depsfile seems to be evaluated before the app directory. So the observed flow was:

  1. App-Specific deps - with .deps.json and versions
  2. App directory deps - without versions and ignored if already known
  3. The additional deps - with version and ignored if already known (independent of version)
  4. The framework - with version, only replacing on higher version

This third one feels broken to me, since all other are replacing version aware. Just this one isn't.

So generating this combined .deps.json that is shared by all tools is no issue and already possible with the approach I wrote up here: #53834 (comment) The only missing thing is to tell the applications to also use it, besides having to use the arguments that is not always working for all use-cases.

@vitek-karas
Copy link
Member

I can't think of any other way right now.
You could publish the apps as self-contained in which case you simply overwrite the framework file with the one you want, but that could come with other problems - and it's much bigger.

The global.json approach is problematic - it requires the host to probe the disk and potentially many times (going towards the root of the file system) - and it would require every host for every app to do this. It would effectively add a perf burden on every app while befitting only a very small number of apps in total. We've been thinking about something like this for a while, but so far no really good idea.

@vitek-karas
Copy link
Member

So generating this combined .deps.json that is shared by all tools is no issue and already possible with the approach I wrote up here: #53834 (comment) The only missing thing is to tell the applications to also use it, besides having to use the arguments that is not always working for all use-cases.

If you can generate the file, while not simply create a copy for each app with the right name... sorry I must have missed something.

@Falco20019
Copy link
Author

Falco20019 commented Jan 12, 2022

The global.json approach is problematic - it requires the host to probe the disk and potentially many times (going towards the root of the file system) - and it would require every host for every app to do this. It would effectively add a perf burden on every app while befitting only a very small number of apps in total. We've been thinking about something like this for a while, but so far no really good idea.

I understand that. I would be fine if it's just checking in the same folder (like it's checking for a file named [app_name].deps.json, like Directory.Runtime.deps.json or something). But since the global.json and Directory.Build.[targets|props] are evaluated all the way up to the root, this would feel off...

So generating this combined .deps.json that is shared by all tools is no issue and already possible with the approach I wrote up here: #53834 (comment) The only missing thing is to tell the applications to also use it, besides having to use the arguments that is not always working for all use-cases.

If you can generate the file, while not simply create a copy for each app with the right name... sorry I must have missed something.

It's not that I can't. I'm just trying to find out, if there is a better solution. Especially one that is good to automate. If there isn't, and if there are no plans to address this, the way I will tackle this is most probably to add a PostPublish target to my SDK to copy the $(ProjectDepsFileName) for each tool. Right now, my best bet would be to check for all exe + dll combinations that don't have a .deps.json since I can't really detect it once packed as nupkg. We have a specialized deployment for each customer (modular software, lots of DLLs, a sub-set + configuration and tools is deployed), and the ones creating the deployments are not always developers. So this needs to be fully automated by basically publishing the csproj using the Company.SDK.Bundle SDK (containing all tools and other modules that are part of the setup).

@vitek-karas
Copy link
Member

Unfortunately we don't have any specific plans to address this scenario right now - it may change, but it's unlikely we would make improvements in this area for .NET 6 - if anything happens it would be for .NET 7+.

@Falco20019
Copy link
Author

Falco20019 commented Jan 12, 2022

Unfortunately we don't have any specific plans to address this scenario right now - it may change, but it's unlikely we would make improvements in this area for .NET 6 - if anything happens it would be for .NET 7+.

I fully understand that, especially since it's a very niche requirement. I mostly started this discussion to see if I'm understanding/observing it correctly or if I just missed something in plain sight. I'm also fine to just keep this as a low-priority "there's a use-case" reminder for .NET 7+ and using workarounds.

Do you know, if there is a way to detect apphost instances through MSBuild (to find out which files need a .deps.json) or is guessing it by exe + dll my best bet?

@vitek-karas
Copy link
Member

Do you know, if there is a way to detect apphost instances through MSBuild (to find out which files need a .deps.json) or is guessing it by exe + dll my best bet?

If you're building the app itself, there is a variable which holds the name of the apphost (I would have to look for it), but in your setup where you're combining multiple apps through NuGets - I don't think there is anything (the fact that it's an apphost would have to be stored in the NuGet somewhere... )

@Falco20019
Copy link
Author

Thanks for all the feedback. I will close this for now and work on my workaround :) Feel free to link this whenever it might become relevant for .NET 7+.

@ghost ghost locked as resolved and limited conversation to collaborators Feb 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Host untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

2 participants