-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Adding System.SemanticVersion #19317
Comments
As an addition, I'd want to see an associated comparer that could properly sort |
Sounds like a good idea. Also quite a few votes (15) in just 5 hours ... Side question out of curiosity: Are all those 15 folks watching the whole repo or was the issue promoted somewhere (Twitter, MVP summit)? |
We need formal API proposal ... |
I'm talking with the NuGet team tomorrow, I'll try and follow-up on this after. They obviously have some good insights here that are excellent use cases and considerations. I'll try to pitch a formal API (or provide more info) as soon as time allows afterwards. |
Since a major aspects of the semantics of semantic versioning is about compatibility, it could be worth to also provide basic functionality to check whether an actual semantic version is expected to be compatible to a known version (simply put: same major version, same or larger minor, patch) |
Sounds good, let us know how that goes - assigning to you for now as you're working on it. Let me know if that changes. |
The issue was indeed shared via Twitter by @NickCraver |
@Structed that explains it, thanks! |
I talked with @yishaigalatzer and @rrelyea at the summit a little about this. I'm not sure they're confident NuGet can use a SemanticVersion in the BCL and I'm not sure either - but let's find out! Starting with NuGet's current implementation (linked in the issue), here's a first pass at what My hope is that NuGet's SemanticVersion could simply inherit from this at some point. I'm not sure if that's an achievable goal. namespace Sysem
{
public class SemanticVersion : IFormattable, IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>, IComparable<Version>, IEquatable<Version>
{
public SemanticVersion(SemanticVersion version); // Copy
public SemanticVersion(int major, int minor, int patch);
public SemanticVersion(int major, int minor, int patch, string releaseLabel);
public SemanticVersion(int major, int minor, int patch, string releaseLabel, string metadata);
public SemanticVersion(int major, int minor, int patch, IEnumerable<string> releaseLabels, string metadata);
protected SemanticVersion(int major, int minor, int patch, int revision, string releaseLabel, string metadata);
protected SemanticVersion(int major, int minor, int patch, int revision, IEnumerable<string> releaseLabels, string metadata);
protected SemanticVersion(Version version, string releaseLabel = null, string metadata = null);
protected SemanticVersion(Version version, IEnumerable<string> releaseLabels, string metadata);
public int Major { get; }
public int Minor { get; }
public int Patch { get; }
public IEnumerable<string> ReleaseLabels { get; }
public virtual bool IsPrerelease { get; }
public virtual string Metadata { get; }
public virtual bool HasMetadata { get; }
/// <summary>
/// Gives a normalized representation of the version, excluding metadata.
/// </summary>
public virtual string ToNormalizedString();
/// <summary>
/// Gives a full representation of the version including metadata.
/// </summary>
public virtual string ToFullString();
public virtual string ToString(string format, IFormatProvider formatProvider);
public virtual bool Equals(SemanticVersion other);
public virtual bool Equals(SemanticVersion other, SemanticVersionComparison versionComparison);
public virtual bool Equals(Version other);
public virtual int CompareTo(object obj);
public virtual int CompareTo(SemanticVersion other);
public virtual int CompareTo(SemanticVersion other, SemanticVersionComparison versionComparison);
public virtual int CompareTo(Version other);
public static bool operator ==(SemanticVersion version1, SemanticVersion version2);
public static bool operator !=(SemanticVersion version1, SemanticVersion version2);
public static bool operator <(SemanticVersion version1, SemanticVersion version2);
public static bool operator <=(SemanticVersion version1, SemanticVersion version2);
public static bool operator >(SemanticVersion version1, SemanticVersion version2);
public static bool operator >=(SemanticVersion version1, SemanticVersion version2);
public static explicit operator SemanticVersion(Version version);
public static bool operator ==(SemanticVersion version1, Version version2);
public static bool operator !=(SemanticVersion version1, Version version2);
public static bool operator <(SemanticVersion version1, Version version2);
public static bool operator <=(SemanticVersion version1, Version version2);
public static bool operator >(SemanticVersion version1, Version version2);
public static bool operator >=(SemanticVersion version1, Version version2);
}
public enum SemanticVersionComparison
{
/// <summary>
/// Semantic version 2.0.1-rc comparison.
/// </summary>
Default = 0,
/// <summary>
/// Compares only the version numbers.
/// </summary>
Version = 1,
/// <summary>
/// Include Version number and Release labels in the compare.
/// </summary>
VersionRelease = 2,
/// <summary>
/// Include all metadata during the compare.
/// </summary>
VersionReleaseMetadata = 3
}
} Some questions:
Thoughts? |
So anything that doesn't match the original Version is now a release-label? Can't we establish some commonalities around that? alpha, beta, pre, etc. By providing a text enum we start to solidify common business practices and get people on the same stage. You can still have populated release labels but how often are those strings plural? If you have I think that if we're gonna handle sem-ver, let's go ahead and do ranges. Those could be nullable |
|
I agree that this is a BCL must-have. I am a huge proponent of minimalism as long as people's needs aren't blocked. |
I came from Twitter but now that issues that I care about like this have been brought to my attention, I will be watching the repo from now on. |
@jcolebrand SemanticVersion is not constrained to those things, so this has to be more flexible per the rules at http://semver.org/ to be generally useful.
I don't disagree...but what's that look like, in API form? Pitch out some ideas? @justinvp Responses:
NuGet does additional comparisons in
Good idea, that's a better approach. Updated.
Yes, absolutely. I just left these off for conciseness and left them as assumptions - I'll add them back to be explicit this is happening.
IMO, neither of those are really clear, but if that's consistent elsewhere in the framework of course we should stay consistent. I'll update.
Well now I just look like an idiot for leaving that off. Adding. Here's a new revision (should we track this in one place at the top, or does that make comments confusing as it's edited? I can see it both ways): namespace Sysem
{
public class SemanticVersion : IFormattable, IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>, ICloneable
{
public SemanticVersion(int major, int minor, int patch);
public SemanticVersion(int major, int minor, int patch, string releaseLabel);
public SemanticVersion(int major, int minor, int patch, string releaseLabel, string metadata);
public SemanticVersion(int major, int minor, int patch, IEnumerable<string> releaseLabels, string metadata);
protected SemanticVersion(int major, int minor, int patch, int revision, string releaseLabel, string metadata);
protected SemanticVersion(int major, int minor, int patch, int revision, IEnumerable<string> releaseLabels, string metadata);
protected SemanticVersion(Version version, string releaseLabel = null, string metadata = null);
protected SemanticVersion(Version version, IEnumerable<string> releaseLabels, string metadata);
public int Major { get; }
public int Minor { get; }
public int Patch { get; }
public IEnumerable<string> ReleaseLabels { get; }
public virtual bool IsPrerelease { get; }
public virtual string Metadata { get; }
public virtual bool HasMetadata { get; }
/// <summary>
/// Gives a normalized representation of the version, excluding metadata.
/// </summary>
public virtual string ToNormalizedString();
/// <summary>
/// Gives a full representation of the version including metadata.
/// </summary>
public virtual string ToFullString();
public virtual string ToString(string format, IFormatProvider formatProvider);
public override string ToString();
public override int GetHashCode();
public virtual SemanticVersion Clone();
object ICloneable.Clone() => Clone();
public virtual bool Equals(SemanticVersion other);
public virtual bool Equals(SemanticVersion other, SemanticVersionComparison versionComparison);
public virtual bool Equals(Version other);
public virtual int CompareTo(object obj);
public virtual int CompareTo(SemanticVersion other);
public virtual int CompareTo(SemanticVersion other, SemanticVersionComparison versionComparison);
public virtual int CompareTo(Version other);
public static SemanticVersion Parse(string versionString);
public static bool TryParse(string versionString, out SemanticVersion version);
public static bool operator ==(SemanticVersion left, SemanticVersion right);
public static bool operator !=(SemanticVersion left, SemanticVersion right);
public static bool operator <(SemanticVersion left, SemanticVersion right);
public static bool operator <=(SemanticVersion left, SemanticVersion right);
public static bool operator >(SemanticVersion left, SemanticVersion right);
public static bool operator >=(SemanticVersion left, SemanticVersion right);
public static explicit operator SemanticVersion(Version version);
public static bool operator ==(SemanticVersion left, Version right);
public static bool operator !=(SemanticVersion left, Version right);
public static bool operator <(SemanticVersion left, Version right);
public static bool operator <=(SemanticVersion left, Version right);
public static bool operator >(SemanticVersion left, Version right);
public static bool operator >=(SemanticVersion left, Version right);
}
public enum SemanticVersionComparison
{
/// <summary>
/// Semantic version 2.0.1-rc comparison.
/// </summary>
Default = 0,
/// <summary>
/// Compares only the version numbers.
/// </summary>
Version = 1,
/// <summary>
/// Include Version number and Release labels in the compare.
/// </summary>
VersionRelease = 2,
/// <summary>
/// Include all metadata during the compare.
/// </summary>
VersionReleaseMetadata = 3
}
} |
@NickCraver The |
Oh yes, updating it that way. I bet we end up sealed here, but I can't be the one to do it, @davidfowl won't talk to me again. |
I think the |
Please don't ever seal, that's just an unnecessary constraint. Version ranges do not belong in dotnet core, they are a nuget feature that we plan to keep expanding. Either all of these go to a package from nuget we can keep revving without being tied to dotnet releases as we add new features or stick with just version. Metadata should not be part of the comparison nor should imho the enum be there. If you want to compare the core version just get the version component out of it. Keep it simple. Semver only supports 3 levels of version, but nuget always allowed 4 to match backward compatibility A semver1 vs semver2 class or distinction is required. |
One issue with the equality operators is this scenario: Version version = new Version(1, 0, 0);
SemanticVersion semver = new SemanticVersion(1, 0, 0);
var equal2 = semver == version; //Compiles
var equal1 = version == semver; //Does not compile I'm not a big fan of having equality operators having left and right be in a specific order, by type. This can be solved in a few ways.
Personally I like the idea of This also opens up the discussion that currently a I think it would make sense for that to all be a single proposal, otherwise it's difficult to see how all of the pieces fit together. |
@yishaigalatzer Good thoughts, much appreciated.
I agree on ranges (and didn't include them). If they are only NuGet specific then yes, we should leave them out and hopefully leave this extensible where NuGet can use the base A few followup questions:
After reading this, I agree. Thoughts from others on nuking the enum and overloads?
Perhaps a return of the version in enum form (which can be added to in a non-breaking way) would be the best option here? e.g.: public SemanticVersionSpecification VersionSpecification { get; };
...
public enum SemanticVersionSpecification {
Version1,
Version2
} ...these are terrible names, but you get the idea. We could determine the minimum (or maximum) level it's compatible with similar to how NuGet does this today. @vcsjones I looked at the first example I could think of being added later: |
That seems the best way to go. The more I think on it, the more I dislike the idea of public static bool operator ==(Version left, SemanticVersion right);
public static bool operator !=(Version left, SemanticVersion right);
public static bool operator <(Version left, SemanticVersion right);
public static bool operator <=(Version left, SemanticVersion right);
public static bool operator >(Version left, SemanticVersion right);
public static bool operator >=(Version left, SemanticVersion right); |
Looking at SemanticVersion as a long-term here, but until something like https://github.com/dotnet/corefx/issues/13526 is in place, reverting to strings to fix #220.
Addition: |
It would be super nice to be able to do this in one attribute instead of two: [assembly: AssemblyVersion("2.0.10.0")]
[assembly: AssemblyInformationalVersion("2.0.10")] // Or sometimes "2.0.10-rc.1" |
This implementation of SemVer might be worth a look: https://github.com/AndyPook/SemVer There are some unit tests based on the spec. |
❓ Why is the operator to convert ❓ Is there ever a case where |
Another question: SemVer does not have 4 components in its version number, it has What is the purpose of this 4th component? Are we just ignoring it? If so, then @sharwell's suggestion of an implicit cast is a no-go since the 4th component is ignored. If we aren't ignoring it, are we OK deviating from the SemVer spec? |
I'm not sure about the comparisons. Given 0.9, 0.9.1 and 1.0-beta, surely one always considers 1.0-beta the highest, but vary one whether or not one filters it out of consideration? |
The last previous 5 comments seem to be the most helpful and explain why this story has been going on for many years without progress. What do we see?
What conclusion could be drawn from this?We could close this discussion and do nothing - we (.Net ecosystem) have been living this way for the last 4 years and nothing terrible has happened, which means it will not happen. But a reasonable question arises - can we still do something useful for .Net ecosystem? what are we doing wrong if we can't move forward?The reasonable answer is: This answer seems counterintuitive, but it explains a lot. Let's see what all ecosystems have in common. what all ecosystems have in common (in the versioning context)?
The same for the .Net ecosystem
Now we know why here is no progress
Let's look PowerShell ecosystem. If we move PowerShellGet to Semantic Version we should:
So moving to Semantic Version causes an avalanche of changes - this is a catastrophe. We need a lot of effort not to ruin everything and find workarounds and tradeoffs for backward compatibility. What is the main conclusion?The solution that does not make breaking changes will take root. What are the requirements for enhancing System.Version class?
|
@iSazonov What if an application has logic that depends on this? var isTwoToFourPartDottedNumericVersion = Version.TryParse(someString, out _); |
First point in my proposal is "no breaking changes". My proposal is (1) to enhance Version class with new functionality, (2) to add a mapping of a classic versions to/from semantic versions based on a policies which can be standardized for some communities and can be custom for specific projects. This allows flexible and transparent adoptions to the semantic version standard. I see MSFT team moved this to P2 and I see no reason why not assign this to 6.0 and brainstorm now. |
I don't see how you avoid breaking this scenario if attributeSourceFile.Write("[assembly: AssemblyVersion(\"");
var isTwoToFourPartDottedNumericVersion = Version.TryParse(someString, out _);
if (isTwoToFourPartDottedNumericVersion)
{
attributeSourceFile.Write(someString);
}
else
{
// Do some special handling to reformat `someString` to make it acceptable to AssemblyVersionAttribute
}
attributeSourceFile.WriteLine("\")]"); |
A key in the proposal is that we don't change default behavior and you should explicitly enable new feature by assigning a semantic version policy in some way (global flag, specific build, new overload, config option and so on). |
I'm not seeing that in your proposal. Maybe you could spell it out there? |
Do you have any update to share on this proposal ? |
I'd love to see SemVer in a place that we could use it in MSBuild if nothing else. Doing comparisons on SDK versions and feature bands is so painful right now. |
@jeffhandley -- Can you re-assess this feature for .NET 8? |
I wish semantic versioning should be support in dotnet core. I think we should only pick 2-3 best and most widely used semantic versioning standard and only support that Maybe we could expand support in the future if we consider making arbitrary pattern in similar way as datetime parsing |
This proposal was shown to me yesterday, and I spent some time to read all the comments today. I have some feedback, in case it helps.
The second blog post I ever wrote was about invetigating a perf difference between a "clean room" NuGet version implementation, and NuGet's own While the class (or struct, if you all decide to do that) can "cheat" and use the backing field for the public property directly, but that would mean that anyone writing a custom comparer can't benefit (well, they could try casting, but that doesn't feel very safe).
The first blog post I ever wrote was about different implementations of a version class, and how quickly it can sort. The key here is that SemVer says that any pre-release segment (part of the string separated by If we don't care about perf of people writing custom I don't know how perf sensitive the runtime is. It does make the public API less clean, but it's something I wanted the relevant people to be aware about, so they can make an informed decision. But 10% might be small enough to ignore the perf improvement to keep a nicer public API.
A SemanticVersion without either prerelease labels or metadata labels can be entirely stack allocated. But as soon as a version string has either prerelease or metadata, you need some kind of collection (array, or list) to store the parsed segments. Perhaps I just don't know enough about .NET perf, but I don't think it's possible to have a struct with a stack allocated collection as a field or property, is it? Maybe it doesn't matter, because the collection can be allocated once and as a struct SemanticVersion is copied around it just keeps referencing the same collection, so it can be allocated just once. People more knowledgeable than me can make a decision, but I thought it's another thing that should be known by people making decisions.
The SemVer spec doesn't explicitly state whether comparison of prerelease labels should be case sensitive or not, only that it should be "compared lexically in ASCII sort order". I assume this to mean case sensitive. However, I imagine that NuGet isn't the only tool that uses the version string in directory names, and therefore "needs" to treat prerelease labels as case insensitive to work as expected on case insensitive file systems. This is another reason why prerelease labels being
Just an FYI, but a while ago I updated NuGet's docs to explicitly list all the ways I could determine that NuGet's versions differ from SemanticVersion. Probably not very relevant here, but these are the ways that NuGet would need to be capable of extending |
@ericstj and I discussed this topic again recently in response to the recent comments and activity here. We're glad to see folks from the NuGet team sharing more notes on if/how something in the core libraries could be used by NuGet (and other packaging systems build on .NET). Nonetheless, a viable path forward hasn't yet surfaced. The possible approaches that we discussed were:
Thus far, a clear path toward a viable yet reusable SemanticVersion is not revealing itself. I suspect we're stuck with the status quo where each system that deals with semantic versions carries its own domain-specific implementation where that system's quirks can be handled. Notes from the SemVer spec struggling with versioning itself:
|
If a new type is added, please don't name it SemanticVersion. There is so much confusion around this term ("semantic versioning") in the .NET ecosystem. We in the Azure SDK team run into many users who write multi-language apps and they have quite specific mental model of what semantic versioning means and does when they come for background of languages that actually do support semantic versioning. .NET does not! Semantic versioning depends on the ability to load more than one version of a package into the a process/context, and on the package/manger using the major/minor version numbers to decide whether to load side-by-side or not. We don't (and cannot) do it in .NET. Of course, we should (if possible) add the ability for the existing Version type to parse and store more complex version information. I would just not add a new type and especially I would not call it SemanticVersion |
As for Version enhancement, as implementation detail, we could use the same trick as DateTimeKind to distinguish "version" and "sematic version" states of Version class. It could eliminate changes in the behavior of existing code. |
@KrzysztofCwalina could you please expound on this part?
This is honestly the first time I'm ever hearing anything like that. As far as I've ever seen, the semantic versioning is purely for comparison and resolution (e.g. determining which NuGet package version to use), but nothing about side-by-side loading. FWIW, I totally agree this isn't right to land here, all of the comments above make great points. I'm just super curious to learn about that side-by-side requirement and where it comes from :) |
And this is at the root of the issue. In .NET, the algorithm which package managers use to select package versions is very simple: the latest out of the set of known dependencies. The reason is that CLR can only load one version and so loading any version but the latest would cause program failures. In other languages/runtimes, where the runtimes can load more than one version of a package, the algorithm is more elaborate, where the runtime might load one or more versions depending on whether the versions are compatible or not. And so, the whole semantic versioning scheme (minor/major/revision parts) is to allow packages to communicate to package managers how compatible they [think they] are. Since .NET picks the latest, there is nothing "semantic" (behavioral) about the version parts as the don't affect semantics/behavior. But, by us using "semantic versioning" in docs, APIs, etc. we create an impression that .NET versioning works like JS/Python versioning does, which is does not. In .NET, only one versioning approach works: be 100% API compatible (i.e. additions only). |
Should this therefore be split between a simple semantic version type that contains all the fields, and then separate comparers that implement the various different rules that consumers might require? It seems like that would fit far better than trying to make a single base class that suits all circumstances. |
This is one way of using semantic version, but the versioning scheme doesn't require this kind of behaviour. Semantic versioning is (to me at least) mainly a documentation scheme that describes the relationships between versions, and the urgency and risk of an upgrade. Patch update? Apply ASAP. Major upgrade? Better plan it properly and test it thoroughly. Tools that can parse and compare the version numbers, and workflows that can be built on top, are just nice-to-haves. I'm surprised anyone would conflate semantic versioning and side-by-side loading. I'm curious where the confusion comes from; the official spec certainly doesn't mention anything about the latter. |
…e versions when checking for updates Use `System.Version` to check for new versions instead of some complex version library for semantic versioning because 1. We do not advice updating to pre-release versions automatically (e.g. 1.0.0-beta or 1.12.1-pr0271-0047). 2. Should this change, we can use: - NuGet.Versioning (https://www.nuget.org/packages/NuGet.Versioning/) - Semver (https://www.nuget.org/packages/Semver/) - SemanticVersioning (https://www.nuget.org/packages/SemanticVersioning/) Official support was requested but has not been added yet: dotnet/runtime#19317
…e versions when checking for updates Use `System.Version` to check for new versions instead of some complex version library for semantic versioning because 1. We do not advice updating to pre-release versions automatically (e.g. 1.0.0-beta or 1.12.1-pr0271-0047). 2. Should this change, we can use: - NuGet.Versioning (https://www.nuget.org/packages/NuGet.Versioning/) - Semver (https://www.nuget.org/packages/Semver/) - SemanticVersioning (https://www.nuget.org/packages/SemanticVersioning/) Official support was requested but has not been added yet: dotnet/runtime#19317
…e versions when checking for updates (#276) Use `System.Version` to check for new versions instead of some complex version library for semantic versioning because 1. We do not advice updating to pre-release versions automatically (e.g. 1.0.0-beta or 1.12.1-pr0271-0047). 2. Should this change, we can use: - NuGet.Versioning (https://www.nuget.org/packages/NuGet.Versioning/) - Semver (https://www.nuget.org/packages/Semver/) - SemanticVersioning (https://www.nuget.org/packages/SemanticVersioning/) > Official support was requested but has not been added yet: dotnet/runtime#19317
AB#1115515
One of the issues I keep running into with the "default open" world: prereleases. As more and more projects move to semantic versioning, it'd be nice to have a built-in type to handle these. Today, this fails:
Because
System.Version
has no concept of pre-release suffixes. I'm not arguing it should be added (that's maybe a little to breaking).Instead how about a new type?
System.SemanticVersion
The main property most people would be interested in is the prerelease label, but other tenants of semantic versioning should be included. Comparisons being a big one. A lot of devs get this wrong on corner cases. I was looking for how to do best structure this and noticed: NuGet is already doing it. Here's theirSemanticVersion
class: (part 1, part2).The API likely needs a little refinement for general use in the BCL, but is there a desire for this from others? I have use cases from parsing package versions to determining which version of Elasticsearch a client is connected to. There's a broad spectrum of semantic versioning use cases now.
Suggestions from others (collating here):
Version
(additional operator overloads)API
The text was updated successfully, but these errors were encountered: