fsprojects / fsprojects/Paket

Don't generate redirects for framework assemblies on framework projects

Open
#3,976 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
F#
Stars
2.1k
Forks
528
Avg merge
1d 12m
Merged PRs (30d)
54

Description

After Microsoft started releasing out-of-band updates for packages which exists
in netframework, a lot of packages on nuget is no longer handled correctly. The
core of the problem is that the version on nuget and GAC doesn't necessarily
match. Additionally, a lot of packages on nuget is incorrectly referencing the
nuget version of packages for framework targets.

I propose that assembly redirects should not be generated for framework assemblies
when packages incorrectly reference the nuget versions for netframework.

Say we have a package P that relies on a netframework package F. Netframework
should be compatible with netstandard, so the package P had a nuspec file

<group targetFramework="netstandard2.0">
    <dependency id="F" version="2" />
</group>

When netframework starts the application, F.dll from GAC will be used instead of
the version from F.nupkg because F.nuspec has a section

<frameworkAssembly assemblyName="F" targetFramework=".NETFramework4.6.2" />

The problem here is that F in GAC might have version 1, and not version 2 as the
package P requests. This should still not be a problem as netframework should be
compatible with netstandard, and the framework version should work even though
the version differ on nuget.

But when generating redirects, a redirect for "F version 2" will be created. For
netframework though, the dll from the package will not be used because of the
frameworkAssembly, and version 1 will be loaded instead. The application will
then crash as it cannot find version 2 as the redicts forces it to load.

Lots of libraries supports "all" versions of .NET by including only a
netstandard section in the nuspec. These will then include things like
System.IO, System.Runtime, System.Net.Http etc. All these libraries will get
incorrect redirects unless the exact same version as exists in the GAC is
referenced. We've spent a lot of time patching such projects and manually
maintaining forks for some projects.

My hypothesis is that should avoid generating redirects for assemblies for
netframework projects iff the assembly is "tagged" with frameworkAssembly
somewhere. If an assembly is "tagged" as a framework assembly for a framework
project, there only exists one version, and that is the version in the
framework. If someone, somewhere, incorrectly referenced another version, it
should be safe (and actually correct) to ignore this.

This assumes that sooner or later, the framework assemblies will be marked as
such. Looking at some of the core assemblies on nuget, this seems to be
correct, for instance System.IO:

    <dependencies>
      <!-- Notice that netframework doesn't reference System.Runtime etc. -->
      <group targetFramework=".NETFramework4.6.2" />

      <!-- If the netframework section didn't exist, this would have been used,
           further referencing the System.Runtime package
      -->
      <group targetFramework=".NETStandard1.0">
        <dependency id="Microsoft.NETCore.Platforms" version="1.1.0" />
        <dependency id="Microsoft.NETCore.Targets" version="1.1.0" />
        <dependency id="System.Runtime" version="4.3.0" />
        <dependency id="System.Text.Encoding" version="4.3.0" />
        <dependency id="System.Threading.Tasks" version="4.3.0" />
      </group>
      <!-- all the other versions listed -->
    </dependencies>
    <frameworkAssemblies>
      <!-- Because these are tagged "frameworkAssembly", we shouldn't generate
           redirects for them when the targetFramework matches.
      -->
      <frameworkAssembly assemblyName="mscorlib" targetFramework=".NETFramework4.6.2" />
      <frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.6.2" />
    </frameworkAssemblies>

If System.IO didn't have a section netframework skipping System.Runtime etc (as is the problem in practice), System.Runtime would have been referenced, but again:

    <dependencies>
      <group targetFramework=".NETFramework4.6.2" />
      <group targetFramework=".NETStandard1.0">
        <dependency id="Microsoft.NETCore.Platforms" version="1.1.1" />
        <dependency id="Microsoft.NETCore.Targets" version="1.1.3" />
      </group>
    </dependencies>
    <frameworkAssemblies>
      <frameworkAssembly assemblyName="mscorlib" targetFramework=".NETFramework4.6.2" />
      <frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.6.2" />
      <frameworkAssembly assemblyName="System.ComponentModel.Composition" targetFramework=".NETFramework4.6.2" />
      <frameworkAssembly assemblyName="System.Core" targetFramework=".NETFramework4.6.2" />
    </frameworkAssemblies>

For an example of the problem in practice, heres Neo4j.Driver.Signed:

    <dependencies>
      <group targetFramework=".NETStandard2.0">
        <dependency id="System.Net.NameResolution" version="4.3.0" exclude="Build,Analyzers" />
        <dependency id="System.Net.Security" version="4.3.2" exclude="Build,Analyzers" />
        <dependency id="System.Net.Sockets" version="4.3.0" exclude="Build,Analyzers" />
        <dependency id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" exclude="Build,Analyzers" />
        <dependency id="System.Runtime.Serialization.Primitives" version="4.3.0" exclude="Build,Analyzers" />
        <dependency id="System.Threading.Thread" version="4.3.0" exclude="Build,Analyzers" />
        <dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
      </group>
    </dependencies>

On the surface, it looks correct, but all these packages has to be loaded from netframework..
System.Runtime.InteropServices.RuntimeInformation.dll in the nuget package 4.3
is 4.0.1.0. The version included in net472 is 4.0.2.0. Paket will create an
assemblyRedirct to load 4.0.1.0, which isn't the version in netframework.

So everything is "correct" here.. The Neo4j.Driver.Signed is following the
specification, as is System.Runtime.InteropServices.RunitemInformation, as is
Paket. The problem is that the migration from netframework to netcore and
netstandard is a clusterf*ck. Rather than manually patching Neo4j.Driver.Signed
(and all other packages on nuget), I believe that avoiding assembly redirects
for framework assemblies is a better workaround. Netframework isn't going away,
but this issue is only getting worse as less and less projects use netframework.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Trace the assembly-redirect generation path and how NuGet frameworkAssembly metadata is represented for .NET Framework targets; the issue provides no file or test names, so begin by locating those entry points. Done means matching framework assemblies no longer receive redirects, while unrelated assemblies retain them, with regression coverage for the described case.

Written by the indexing model from the issue text.

Assessment

Tech stack
fsharp
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.