Skip to content

Instantly share code, notes, and snippets.

@JamesNK
Last active March 22, 2021 04:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamesNK/56b1c3b90b2a8d21c73d to your computer and use it in GitHub Desktop.
Save JamesNK/56b1c3b90b2a8d21c73d to your computer and use it in GitHub Desktop.
The Great Newtonsoft.Json.nuspec Collaboration
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Newtonsoft.Json</id>
<version></version>
<title>Json.NET</title>
<description>Json.NET is a popular high-performance JSON framework for .NET</description>
<authors>James Newton-King</authors>
<language>en-US</language>
<projectUrl>http://www.newtonsoft.com/json</projectUrl>
<iconUrl>http://www.newtonsoft.com/content/images/nugeticon.png</iconUrl>
<licenseUrl>https://raw.github.com/JamesNK/Newtonsoft.Json/master/LICENSE.md</licenseUrl>
<tags>json</tags>
<dependencies>
<group targetFramework="net45" />
<group targetFramework="wp8" />
<group targetFramework="win8" />
<group targetFramework="wpa81" />
<group targetFramework="xamarin.ios" />
<group targetFramework="monotouch" />
<group targetFramework="monoandroid" />
<group targetFramework=".NETPlatform5.0">
<dependency id="Microsoft.CSharp" version="4.0.0" />
<dependency id="System.Collections" version="4.0.10" />
<dependency id="System.Collections.Concurrent" version="4.0.10" />
<dependency id="System.ComponentModel.TypeConverter" version="4.0.0" />
<dependency id="System.Diagnostics.Debug" version="4.0.10" />
<dependency id="System.Diagnostics.Tools" version="4.0.0" />
<dependency id="System.Dynamic.Runtime" version="4.0.10" />
<dependency id="System.Globalization" version="4.0.10" />
<dependency id="System.IO" version="4.0.10" />
<dependency id="System.Linq" version="4.0.0" />
<dependency id="System.Linq.Expressions" version="4.0.10" />
<dependency id="System.ObjectModel" version="4.0.10" />
<dependency id="System.Reflection" version="4.0.10" />
<dependency id="System.Reflection.Extensions" version="4.0.0" />
<dependency id="System.Runtime" version="4.0.20" />
<dependency id="System.Runtime.Extensions" version="4.0.10" />
<dependency id="System.Runtime.Numerics" version="4.0.0" />
<dependency id="System.Runtime.Serialization.Primitives" version="4.0.10" />
<dependency id="System.Text.Encoding" version="4.0.10" />
<dependency id="System.Text.Encoding.Extensions" version="4.0.10" />
<dependency id="System.Text.RegularExpressions" version="4.0.10" />
<dependency id="System.Threading" version="4.0.10" />
<dependency id="System.Threading.Tasks" version="4.0.10" />
<dependency id="System.Xml.ReaderWriter" version="4.0.10" />
<dependency id="System.Xml.XDocument" version="4.0.10" />
</group>
</dependencies>
</metadata>
</package>
@JamesNK
Copy link
Author

JamesNK commented Aug 11, 2015

Welcome to The Great Newtonsoft.Json.nuspec Collaboration!

Background: JamesNK/Newtonsoft.Json#618

tl;dr; a nuspec needs lots of dependencies now are and there are no samples on how to actually do it 💩


The Json.NET NuGet package has:

net45
net40
net35
net20
portable-net45+wp80+win8+wpa81
portable-net40+sl5+wp80+win8+wpa81
dotnet

dotnet should be used by DNX. I'm guessing Win10 UAP projects should use it as well, although I'm not familiar with them. Will win8 clash anywhere? I don't know. Do you?!?!?!!111one

What needs to change to get everyone playing along? Comment and I'll edit the nuspec 👍

@clairernovotny
Copy link

One thing to keep in mind: specific TFM > dotnet > portable-*
Resolution of the \lib folders is independent from the dependency groups
Dotnet is evaluated for any System.Runtime based platform unless a more specific TFM is present. This means .NET 4.5, wp8, wpa81, win8, Xamarin.ios, MonoAndroid, MonoTouch, MonoMac, Xamarin.Mac (so far!). Older runtimes, like .net 4 and sl won't even try to eval/match dotnet.

System.Runtime 4.0.0 = .NET 4.5 equivalency (plus win8, wp8, mono*)
System.Runtime 4.0.10 = .NET 4.5.1 equiv (plus win81, wpa81, and Mono-based things, I think)
System.Runtime 4.0.20 = .NET 4.6 equiv (does NOT currently include Mono-based things, but hopefully by end of year?)

This means that depending on what's in your dotnet sections, you may need blank groups to prevent other system.runtime based platforms from using that package if they're older than your minimum versions. Starting with .NET 4.6, these versions should not matter as the newer BCL packages should work there too.

Here's what I make of it so far given your dependency and \lib structure:

  • DNX will use the dotnet dependency section.
  • Win10 will use the win8 dependency section
  • DNX will use the dotnet library
  • Win10 will use the dotnet library (because there's no \lib\win8)
    • In this case, Win10's dependencies would not be fully documented. You'd need a uap10.0 dependency group with the same items that is in the dotnet one.

@bradwilson
Copy link

I take it back, there's one thing that's probably broken: dnx451.

The issue is that your dotnet dependencies require .NET 4.6 (for example, System.Runtime 4.0.20). With things they way they are, dnx < 4.6 would've chosen dotnet but ended up with incompatible references.

Since dnx451 can use whatever net45 library you have, then adding an empty group should be fine (and also make sure you have a lib\dnx451 library, presumably a duplicate of your lib\net45 library).

@JamesNK
Copy link
Author

JamesNK commented Aug 12, 2015

@bradwilson Will that issue go away if I reference System.Runtime 4.0.0 instead of 4.0.20? (and 4.0.0 for everything else). Since Json.NET really just uses things from .NET 4.5 I'm guessing I don't need anything higher than 4.0.0. I just referenced the latest versions of everything because I could.

@JamesNK
Copy link
Author

JamesNK commented Aug 12, 2015

@onovotny Would using uap for the targetFramework be preferable over uap10.0? Keeping the version number out of it should hopefully keep future versions of uap happy.

System.Runtime 4.0.0 = .NET 4.5 equivalency (plus win8, wp8, mono*)
System.Runtime 4.0.10 = .NET 4.5.1 equiv (plus win81, wpa81, and Mono-based things, I think)
System.Runtime 4.0.20 = .NET 4.6 equiv (does NOT currently include Mono-based things, but hopefully by end of year?)

I didn't know that the version numbers matched up in that way. Useful info!

@bradwilson
Copy link

@JamesNK Yes, but to do that the right way, you need to stop using the meta-packages in project.json and use the individual packages with the specific versions you need. You need to use NuGet to look at every reference you're taking, to make sure you're choosing a .NET 4.5-compatible version (like System.Runtime 4.0.0).

Note that the compiler will complain at you about the conflicting references (f.e., System.Linq 4.0.0 "requires" System.Runtime 4.0.20 when you're linking against dotnet), but you can safely ignore those warnings, because the compiler will use your explicitly chosen down-level version.

Don't be surprised if you find things missing. xUnit.net had to forego access to a lot of System.IO, and ExecutionContext, to link against the .NET 4.5 contract assemblies. It might make more sense in that scenario to duplicate the net45 library instead. You'll just have to try it and see.

@bradwilson
Copy link

I didn't know that the version numbers matched up in that way. Useful info!

You have to check NuGet for every package for the supported frameworks. It doesn't always work like that.

@JamesNK
Copy link
Author

JamesNK commented Aug 12, 2015

@bradwilson I built a NuGet package with the nuspec above and tested it in a DNX app:

{
  "version": "1.0.0-*",
  "description": "ConsoleApp1 Console Application",
  "authors": [ "James" ],
  "tags": [ "" ],
  "projectUrl": "",
  "licenseUrl": "",

  "dependencies": {
    "Newtonsoft.Json": "7.0.2-*"
  },

  "commands": {
    "ConsoleApp1": "ConsoleApp1"
  },

  "frameworks": {
    "dnx451": { }
  }
}

It ran without issue. Am I not seeing any problems because I have .NET 4.6 on my computer?

How can I see what .NET version a package requires?

@JamesNK
Copy link
Author

JamesNK commented Aug 12, 2015

Since dnx451 can use whatever net45 library you have, then adding an empty group should be fine (and also make sure you have a lib\dnx451 library, presumably a duplicate of your lib\net45 library).

Shouldn't an empty dnx451 group be enough? dnx451 would then fall back to net45 instead of dotnet.

@JamesNK
Copy link
Author

JamesNK commented Aug 12, 2015

@clairernovotny
Copy link

Just thought of one thing given the above structure...

If you're in VS 2015 with NuGet v3, then the version in \lib\dotnet will be used by Win8, wp8, wpa81 and the Xamarin ones because dotnet > portable-*.

That may or may not be an issue depending on what's actually in dotnet (is it the Profile 259 PCL or a newer project.json modern lib)?

@bradwilson
Copy link

Shouldn't an empty dnx451 group be enough? dnx451 would then fall back to net45 instead of dotnet.

That hasn't been my experience. Don't forget that there's a different selection logic for "what I link against" vs. "whose dependencies I absorb".

@bradwilson
Copy link

BTW, I agree that the strategy is different for a dotnet library which is really a PCL259 vs. one which is actually built for dotnet. Obviously, every PCL259 is compatible with dnx451, but you have to work hard to ensure that a built-for-dotnet library is actually down-level compatible with dnx451.

@JamesNK
Copy link
Author

JamesNK commented Aug 13, 2015

@onovotny
@bradwilson

Are you guys sure dotnet will always be used ahead of portable-* and dnx451? (I would test it myself but I don't have VS2015 on my current machine)

The impression I get from here - http://blog.nuget.org/20150729/Introducing-nuget-uwp.html - is the dotnet target's dependencies will be analyzed and the client will determine whether dotnet is compatible with the project's framework capabilities.

This moniker is not directly tied to any specific version or framework capabilities, but rather is an indirect reference that tells NuGet: “this is the reference you should use if it supports the framework and runtime capabilities that you have”. The NuGet client will then investigate that reference to determine what features and frameworks it supports. This process continues until the NuGet client resolves the exact features supported by the dotnet reference and will then apply it if and only if it matches the features and requirements of your project. The dotnet moniker can be referenced by .NET Framework 4.5 and later derived framework versions including Xamarin Android and Xamarin iOS.

So for a win8 project referencing Json.NET the NuGet client will look at Json.NET's dotnet target, see that it depends on System.Runtime 4.0.20, know that 4.0.20 isn't supported in win8 and fall back to portable-net45+wp80+win8+wpa81 (aka PCL259).

@clairernovotny
Copy link

So I thought that dotnet should work like that but that's not how it appears to work. It dosn't evaluate dotnet and then fallback to portable-* if dotnet isn't compatible (though an argument could be made that it should!)

To the very best of my knowledge, my understanding is specific TFM > dotnet > portable-*. So if you have dnx451 as a TFM in either the lib or the dependencies, that'll be used instead of dotnet for dnx451->dnx46 since that'd be "more sepcific." dotnet though would be used over portable-* for NuGet v3, (so not VS 2013).

So as above, with the current library layout from the beginning of this thread, win8 would get the dotnet target and fail because System.Runtime 4.0.20 isn't compatible. You'd need a \lib\win8 for it to use (and ditto for all of the there frameworks that can't use the .net core package versions (pre 4.6).

As @bradwilson said, you can get away with a lot more with a Profile259 PCL in the dotnet folder because that would use the 4.0.0 contract package references, which are supported by all of the frameworks. Where it gets hairy is if what you put in dotnet requires dependencies (like system.runtime 4.0.20) that don't support older pre-4.6 runtimes. Like a System.Runtime 4.0.10 dependency would not support .net 4.5 but would support .net 4.5.1.

@yishaigalatzer
Copy link

@JamesNK its going to take me some time to process a reasonable and coherent answer. I get the frustration this is causing, and I want to take the time to alleviate this pain properly. So excuse me for not jumping into this long thread with a magic bullet, I'll work on it tomorrow.

@bradwilson
Copy link

It doesn't evaluate dotnet and then fallback to portable-* if dotnet isn't compatible (though an argument could be made that it should!)

+1. Despite what we had all hoped, it does not do this (and at this point, we shouldn't expect that it ever will, because that would fundamentally change the design of the system). This is the key takeaway:

By default, anything which is eligible to use System.Runtime will be assumed to be compatible with dotnet. This includes all the way back to net45, win8, wp8, and wpa81 (plus the net45-alikes, like Xamarin).

The only way to tell NuGet that your dotnet library is not compatible with these things, but an alternative exists, is to list it explicitly. This means you need at a bare minimum a platform-specific library; also, if the dependencies listed for dotnet are not appropriate, you also need a platform-specific dependency group.

IMO, this is all made worse by the Visual Studio tooling pretty much making you assume that dotnet == net46+uwp10.0+dnxcore50. You can make your dotnet library actually compatible with the all the down-level platforms (we did it with xUnit.net's execution library) by throwing away the tooling default project.json and being explicit about your dependencies.

@yishaigalatzer
Copy link

@bradwilson I generally agree with your assesment, we are looking at ways to
a. Clearly document what needs to be done
b. Improve the system so its not so sucky.

So bare with us for a few more days until we can figure out how to make it better

@yishaigalatzer
Copy link

@JamesNK and about testing - we should provide an easier way to test at least the restore using nuget.exe. It will be kinda hard to verify 100% because with project.json you depend on msbuild to do the right thing as well.

@JamesNK
Copy link
Author

JamesNK commented Aug 14, 2015

So this isn't true then...

This moniker is not directly tied to any specific version or framework capabilities, but rather is an indirect reference that tells NuGet: “this is the reference you should use if it supports the framework and runtime capabilities that you have”. The NuGet client will then investigate that reference to determine what features and frameworks it supports. This process continues until the NuGet client resolves the exact features supported by the dotnet reference and will then apply it if and only if it matches the features and requirements of your project. The dotnet moniker can be referenced by .NET Framework 4.5 and later derived framework versions including Xamarin Android and Xamarin iOS.

http://blog.nuget.org/20150729/Introducing-nuget-uwp.html

As far as I can tell dotnet is useless. It hides portable-* for win8/wp8/wpa81/net45 so the assembly in dotnet has to be compatible with all those targets.

@yishaigalatzer
Copy link

Going to fix the blog

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment