silkfire / Pastel

Snazz up your console output!
MIT License
405 stars 24 forks source link

NETSTANDARD support please #29

Closed fluffynuts closed 1 year ago

fluffynuts commented 1 year ago

I use Pastel in a bunch of places, and I've forked and sub-moduled to do so, largely because the package doesn't directly support netstandard (2.0).

My fork is available here (https://github.com/fluffynuts/Pastel) if you'd like me to open a PR. Note that I've also auto-formatted the code and removed a block that looks like it's for testing (sets variables within the block which are never used). So I'm not sure if you want this - you could just add netstandard2.0 to the TargetFrameworks in Pastel.csproj.

Yes, I can see that you support net462, net6.0 and net7.0 - but if you support netstandard2.0, you can take away the net6.0 and net7.0 targets and guarantee that you won't have to update this for quite some time - netstandard is an api, not a runtime, so the runtimes have to support the standard going forward (:

silkfire commented 1 year ago

The reason why I'm no longer supporting .NET Standard is because there is no need to. .NET Standard is something that was used during the .NET Core era where there was a lot of API differences between the various .NET Core versions. Today, as the .NET ecosystem has matured, I only support the earliest supported .NET Framework and LTS .NET versions.

What use cases do you have for .NET Standard?

fluffynuts commented 1 year ago
.NET Standard is something that was used during the .NET Core era

I'm afraid you've been misled. netstandard targets provide an api to target in a dotnet runtime. All dotnet core and dotnet runtimes from 5 upward support netstandard targets, as do several netfx versions - see https://dotnet.microsoft.com/en-us/platform/dotnet-standard, in particular, see this matrix for netstandard2.0, from the prior link:

image

Essentially, targeting netstandard is like having a C project targeting some headers which the linker is supposed to resolve later - the headers say what the consuming code can do, the linker makes it so that the consuming code can do that stuff. Similarly, in the dotnet world, netstandard sets a target api level that the runtime will resolve if it supports that level of netstandard. This is resolved at build time in the consuming project - and if I try to consume a netstandard level which is not supported by my runtime, I'll get a build error - but since netstandard has been stable for a long time and supported by all modern dotnet versions, I haven't actually seen this build error in a long time.

So, in the above, we can see that the netstandard2.0 api is available in net461+ and dotnet core 2.0+. I understand some confusion considering that netstandard2.0 support first appears in net2.0 - but note that netstandard versions only go up to 2.1 (on that page anyway - personally, I've never needed > 2.0 for any of my libraries)

By choosing, say, netstandard2.0 over net6.0 and net7.0, you provide support for every dotnet runtime which supports netstandard2.0. Technically this means you could just have one target for your nuget package - netstandard2.0 (I've tested on lower numbers and the provided api doesn't support the features of Pastel - esp things like System.Drawing - until netstandard2.0).

In practice, however, I always include a net462 target in my packages (https://www.nuget.org/profiles/davydm) because I've seen msbuild pull in a bunch of nonsense when attempting to consume netstandard from netfx - it works, but there's a bunch of shim dlls that are included in the final result.

Each subsequent version of netstandard supports all of the features of the version before - subsequent versions promise to support more functionality, ie all the api of netstandard1.6 is in netstandard2.0 - but there's more stuff too (:

The whole point of netstandard is to say "my library requires these .net apis" and let the runtime take care of it, instead of directly targeting a specific runtime. There is no penalty - there's just a win in that you really only need to support 2 runtimes for a clean consumer experience - net462 (the lowest still-supported version of netfx, and what a lot of people are using when building against netfx) and netstandard (typically, it's a good idea to choose the lowest targeting version because that means your library can be used on the most dotnet runtimes possible, but I tend to stick to netstandard2.0 because those older core runtimes are deprecated anyway.

And why I want it? I'd like to have Pastel as a dependency of Quackers.TestLogger, which supports the short list netstandard2.0;net462 without issuing warnings during build or being unusable in, say, net5.0 (which you don't explicitly support) or net8.0 (which you will have to update the package to support). Right now, I'm pulling your project in via git and including the console extensions c# file in my build (with credit to your project in the README.md)

silkfire commented 1 year ago

There's not much I can do about other package authors not being up-to-date with the latest supported frameworks. If you look inside the source code of Pastel, you will see that I'm utilizing specific .NET 6 and .NET 7 features for improved performance.

I guess it's a good thing that you can fork the library and adjust it to your specific needs, but at the moment I have no intention of back-supporting .NET Standard.

Also see the following articles about the "future" of the NET Standard API:

And yes, I'll add .NET 8 as a target as soon as it's released.

fluffynuts commented 1 year ago

I can see you've made your mind up - it's your project, your direction, so be it.

I'd re-iterate that netstandard is not an "old runtime". It's an API specification that runtimes conform to (to differing degrees, depending on the runtime). Using #pragma's like you already have for net7.0-specific code can be done for netstandard as well. If you'd like, I can prepare a PR to illustrate. You can still keep on targeting specific runtimes, but not lose support for Mono or Xamarin. Even according to the author of that second link:

So, what should you do? My expectation is that widely used libraries will end up multi-targeting for both .NET Standard 2.0 and .NET 5: supporting .NET Standard 2.0 gives you the most reach while supporting .NET 5 ensures you can leverage the latest platform features for customers that are already on .NET 5.

netstandard isn't going away, since it's not a runtime, but an api spec; but I don't think I'm going to convince you otherwise, so I guess I'll just have to stick with my fork. By requiring certain runtimes instead of targeting the lowest-possible, you place the burden on consumers to do the same. The "negatives" in the first article are, imo, straw men:

  1. slow version evolution - who cares, if the api supports what you want to do? and if you can still #pragma your way into using newer features
  2. needs a decoder ring - no-one has needed to look up version compatibility since 2017 when netstandard2.0 came on the scene, supporting (albeit, imo, kludgingly) net462 as well as all current and future netX.Y versions
  3. exposes platform-specific apis - I think they actually phrased this incorrectly, considering the example (blazor) - those runtimes expose additional platform-specific apis - netstandard is meant to be platform-agnostic, as far as possible

Anyhoo, not trying to push the issue any more - I just believe that it's a mistake not to support netstandard if it can be done. If you are concerned about not receiving the optimisations you're wanting on a certain runtime, fear not - nuget / msbuild always chooses the most appropriate library for the consumer - which is why the net462 output is used on net462 targets; net7.0 targets will get your net7.0 binary, but fringe targets would get the netstandard one.

Thanks for your work on this at any rate - forking and maintaining a netstandard-compatible version is a lot less work than making one from scratch.