NuGet / Home

Repo for NuGet Client issues
Other
1.5k stars 252 forks source link

Feature Suggestion - Capabilities #5901

Closed dazinator closed 7 years ago

dazinator commented 7 years ago

I will outline the scenario and suggestion..

The Problem

I would like to maintain a single nuget package that can be installed into asp.net core projects consuming:-

I am mentioning asp.net core as a concrete example, but this suggested feature, generalises.

I say "consuming" rather than targeting, because these are not TFM's - they represent a corresponding release of asp.net core packages.

I can already create a single nuget package that can be installed into all of these projects, by targeting my nuget package to all of the TFMs that the asp.net core packages also target.

However I can't:

  1. Pivot my package installation (dependencies etc) based on which version of asp.net core is being consumed by the target project. For example, if my package is installed into asp.net core 1.0 project, I don't need to install a dependency, which is needed when installed into an asp.net core 1.1 project.

  2. Prevent my package from being listed and installed into projects that are not relevant / compatible. For example, a developer with a console application, not interested in asp.net core, will open the package manager and search for Foo. They might see my package listed Foo.AspNetCore.

With 1) the only way to pivot the installation currently, is to create seperate nuget packages:

  1. Foo.AspNetCoreV10
  2. Foo.AspNetCoreV11
  3. Foo.AspNetCoreV20

This has the following problems:

  1. It adds a lot of overhead to the development process, as we have to to set up new build configurations for each seperate package, along with new / duplicate nuspec files etc.
  2. For consistency, we are forced to create a new nuget package with each release of asp.net core - even if its not necessary. For example the Foo.AspNetCoreV11 package may work perfectly fine for asp.net core v2 as well - but it we want to remain consistent we would have to create a Foo.AspNetCoreV20 anyway.
  3. It complicates the upgrade process for consumers - when consumers upgrade from asp.net core 1.0 to 1.1 they can no longer remain on the same package - they would have to swap to the AspNetCoreV11 package - but there is no way to surface that action to them, unless the TFM has also changed.
  4. The volume of packages listed on nuget.org grows with each supported asp.net core release. This obviously has the implication that search results will become more and more noisy as what should / could be a single package for a single purpose, has to be published as multiple packages with names like .AspNetCoreV1, .AspNetCoreV2.

Multiply this problem for all of the other platforms out there that are not formal TFM's but on which an ecosystem of nuget packages are published for - a few examples might be: every cms (dotnetnuke, drupal, etc etc), nancy, aspnet core etc)

Feature Suggestion

  1. Allow nuget packages to declare "capabilities" that they are dependent upon, in their nuspec.
  2. Allow nuget packages to declare "capabilities" that they will add to a project, when installed.

A capability would just be a string.

For example, declared inside the nuspec for the microsoft,aspnetcore.mvc 1.0.0 package could be capabilities it will add to a project once installed:

<capabilties>aspnetcore, aspnetcore1.0</capabilities>

Inside my package that Foo.AspNetCore I can depend on this capability:

<appliesToCapabilities>aspnetcore</appliesToCapabilities>

I haven't given a great deal of thought around the best way to express these within the nuspec prepcisely, but the idea behind the feature is that when managing packages for a project, Foo.AspNetCore would not show up in search results anymore, unless the aspnetcore capability that it applies to, was part of my solution (i.e I have added the microsoft,aspnetcore.mvc nuget package which adds that capability). This is analogous to how Item Templates work in Visual Studio project system extensibility - i.e they only show up based on capabilities of the project.

When checking to see if a nuget package is compatible, in addition to checking the TFM is compatible, if the nuspec depends on capabilities then a capability check will restrict compatibility further.

The desire would also be to allow a nuget package author so scope dependencies of their nuget package - i.e Foo.AspNetCore based on present project capability - so for example:

 <dependencies>
      <group targetFramework=".NETStandard1.3">
        <dependency id="Foo" version="1.2.0" appliesTo="aspnetcore1.0" exclude="Build,Analyzers" />      
        <dependency id="Foo" version="1.3.0" appliesTo="aspnetcore2.0" exclude="Build,Analyzers" />
        <dependency id="Foo.Bar" version="1.3.0" appliesTo="aspnetcore2.0" exclude="Build,Analyzers" />      
      </group>
    </dependencies>

So when the above package is installed into an project with asp.net core 1 capability it carries Foo version 1.2.0 but when installed into a project with asp.net core 2, it carries Foo version 1.3.0 and Foo.Bar version 1.3.0

When managing packages for a project, the search results would show all legacy packages (i.e packages not making use of this capabilities feature in the nuspec file) as before. However for packages that make use of this feature and have declared "capabiltiies" that they depend on, in their nuspec - these packages would only show up in the search results when the project / solution has that capability present.

nkolev92 commented 7 years ago

Q1

NuGet does not have any context to understand that this is an ASP.NET project. The only thing it's concerned with is the target framework.

I'm not sure what you're suggesting scales well though, as we're only talking about 1 package here. Any thoughts @emgarten

Q2 We have story about listing/displaying only compatible packages here: https://github.com/NuGet/Home/issues/4071 Upvote and follow to stay up-to-date on that work.

emgarten commented 7 years ago

@dazinator any reason you can't use?

   <dependencies>
      <group targetFramework="netcoreapp1.0">
        <dependency id="Foo" version="1.2.0" exclude="Build,Analyzers" />      
      </group>
      <group targetFramework="netcoreapp2.0">
        <dependency id="Foo" version="1.3.0" exclude="Build,Analyzers" />
        <dependency id="Foo.Bar" version="1.3.0" exclude="Build,Analyzers" />      
      </group>
    </dependencies>

NuGet uses the nearest compatible group, and ignores all other groups.

dazinator commented 7 years ago

@emgarten - those groups target tfm's - but aspnetcore1.0 isnt a tfm (each version of aspnetcore can run on potentially multiple tfm's). So targetimg a tfm is not enough to target a particular aspnet core version. For example netcoreapp2.0 can can run netcoreapp1.1 packages, so a netcoreapp20 project can be running aspnet core 1.1 not 2. Or a net46 tfm can be running aspnet core 1.0 or 1.1 etc

emgarten commented 7 years ago

@dazinator there would be an ordering problem here first off. When walking this package restore would need to know the capabilities from all packages, and that wouldn't be known until the end. Each time a package changed and the capabilities changed everything depending on them would need to be re-walked, which ends up being a very time consuming and difficult issue. The project framework used to pivot currently does not change during the walk.

Having capabilities like this would also make it difficult for restore to understand what the intent of the package is and fail when needed. Things might be working fine in a project because Foo is there, but after upgrading a seemingly unrelated package a capability could change that would make other packages work differently. And the primary issue there is that it would be hard to track down what caused it all.

Having different package ids, or different versions of the package with exact dependencies on the dependency packages seems needed here. It might help if you give more details on why this pivot is important, I understand the feature request from the examples you gave, but not the high level goals and the uses.

dazinator commented 7 years ago

@nkolev92 - the search results / compatibility feature you linked - thank you, but its decribing compat based on TFM (nuget already has that nailed for the large part). This request is describing compat via new concept to further restrict results after TFM is applied

nkolev92 commented 7 years ago

@dazinator Yeah that's clear. That issue is about "listing" package based on compatibility, whatever that might be. Currently NuGet does not do any project compatibility checks when presenting package search results.

dazinator commented 7 years ago

@emgarten - ouch yes I can see that would be nasty. Well thanks for considering it and I appreciate your response. An example is that, in asp.net core 1, the way you set up logging and DI is different than asp.net core 2. My library wants to support both asp.net core 1 and 2. But it relies on a dependency (nuget package) to set up the logging. Because of precisely this issue, if my package is installed into an asp.net core 1 projec, I should include the logging dependency for asp.net core 1. If its installed into asp.net core 2 I should include the logging dependency for asp.net core 2.0. At the moment these are two seperate package id's, and I therefore have to create two seperate packages of my own. This problem is only going to get worse from release to release so I was trying to think of possibilities.

emgarten commented 7 years ago

That does sound painful! At one point aspnetcore was a TFM which enabled this, but it was later simplified to use just netcoreapp. I would think to some extent that TFM would still be useful, you could check later in a props/targets file if the package was installed where it couldn't work and then fail.

dazinator commented 7 years ago

Currently NuGet does not do any project compatibility checks when presenting package search results.

Ha.. ok yes that definately gets my vote then.. i thought it atleast filtered some results based on tfm's in the solution.

dazinator commented 7 years ago

Ok thanks, well I'll close this for now, but keep me informed if you come up with any bright ideas around this in the future! :-)

dazinator commented 7 years ago

Fyi another example of the problem can be seen via: https://github.com/aspnet/EntityFrameworkCore/issues/8498 The guidance there is for all dependencies to upgrade to referencing microsoft.extensions.dependencyinjection version 2 so that they will work in asp.net core 2 apps. But they cant do this without dropping support for asp.net core 1 apps. So dependencies that wish to be used by both aspnet core 1 and 2 will now have create 2 seperate packages. @davidfowl is that correct?

And an example of an impacted dependency: https://github.com/autofac/Autofac.Extensions.DependencyInjection/issues/24