Skip to content

Sharing NuGet.Client libraries with Visual Studio components #9611

@nkolev92

Description

@nkolev92

NuGet.Client today

The NuGet tooling is multi-faceted.
The NuGet functionality lives in many 1st party products such as:

  • nuget.exe
  • dotnet.exe
  • VS

While is the NuGet.Client primarily tooling we do have a bunch of library packages that represent things like frameworks, versioning and concepts and models related to PackageReference restore and package reading/installation. , https://github.com/NuGet/NuGet.Client/blob/dev/docs/nuget-sdk.md

NuGet extensibility in VS

Today NuGet defines a set of extensibility APIs in VS.
In particular they should all be documented here: https://docs.microsoft.com/en-us/nuget/visual-studio-extensibility/nuget-api-in-visual-studio.
For the definition in code see https://github.com/NuGet/NuGet.Client/tree/dev/src/NuGet.Clients/NuGet.VisualStudio/Extensibility

This allows components to primarily manipulate and understand packages installed to a certain project.

Limitations

The NuGet IVs APIs are scoped to the most common customer scenarios. The focus is package manipulation. If one needs to deal with an actual compile reference, they'd be expected to do so through after tasks such as RAR run.

Challenging scenarios in the past

Walking the line between being a tool and an SDK is a very challenging endeavor.
One requires requires a lot forward movement and sometimes breaking the models in order to improve them while the other requires full backward compatibility.

There are a few scenarios where VS components have wanted to make use of NUGet functionality, but that's been difficult to do for various reasons.

Examples are:

  • NuGet SDK resolver (final solution ended up being moving and shipping the functionality from the NuGet code).
  • Solution explorer dependency tree (similarly the final solution was moving all functionality to the NuGet.Client code)

Alternatives and challenges summarized

When looking into the solution explorer changes which were happening on the project-system side, here was our process.

NuGet has 3 product scenarios and 2 dependency flows.

  • MSBuild.exe (Insertions into VS)

  • VS (Insertions into VS)

  • Dotnet.exe (Insertions into SDK, which are later followed by insertions of that SDK into VS, this is a 2 hop, so it’s kind of annoying)

  • There 2 components that depend on NuGet implementations beyond the interop assemblies and those are:

    • MSBuilld for GetReferenceNearestTargetFrameworkTask
    • .NET Core SDK (which has compile time dependencies).

Now we want to add a dependency to NuGet.ProjectModel for a similar reason as the SDK, read the assets file.

The reason why we can do a 2 hop into the SDK and why it works is because the SDK carries the NuGet assemblies with them. Given how infrequent the lock file changes are, they rarely run into functional breaks. The worst thing that can happen is that we end up with multiple versions of the NuGet assemblies loaded in VS. Not ideal, but not the worst issue.
The NuGet.*.dll have a version that usually goes from 5.4.0.0 to 5.4.0.6 by the time we ship, where 6 is really the number of insertions.

Back to the problem at hand & our possible solutions:

  • Binding redirects
    • I don’t think this is a viable option at this time. Given that you can use different SDK versions with one VS instance & the problems people with build tools & extensions would run into.
    • NuGet does a dual insertion like with the SDK.
    • This can become challenging to maintain and it puts strain on the project system. You’d need to insert every time NuGet inserts and there’ll be that window where things would not work.
  • NuGet does a dual insertion like with the SDK, the project-system ships NuGet.* assemblies in its payload.
    • This would make things work the exact same way as they do in the SDK from a correctness perspective, but this something we’ve avoided doing for a reason. I imagine this immediately gets flagged as a perf regression
  • Use an msbuild task instead.
    • Similar to the GetReferenceNearestTargetFrameworkTask approach. So the contract between PS & NuGet is the task instead of a compile time dependency. Not sure how feasible this is from your perspective.
  • Use your own parser
    • Easy to get out of date (we don’t really change the assets file that often, not in a way that requires consumers to react at least).
  • Alternatively you can use a submodule and compile it into your assembly, but you’d have to be careful with the public types.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions