Runtime: Proposal: Adding System.SemanticVersion

Created on 9 Nov 2016  ·  76Comments  ·  Source: dotnet/runtime

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:

```C#
Version.Parse("1.0.0-beta1");

Because [`System.Version`](https://msdn.microsoft.com/en-us/library/system.version_properties.aspx) 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 their `SemanticVersion` class: ([part 1](https://github.com/NuGet/NuGet.Client/blob/4cccb13833ad29d6a0bcff055460d964f1b49cfe/src/NuGet.Core/NuGet.Versioning/SemanticVersion.cs), [part2](https://github.com/NuGet/NuGet.Client/blob/4cccb13833ad29d6a0bcff055460d964f1b49cfe/src/NuGet.Core/NuGet.Versioning/SemanticVersionBase.cs)).

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):  
- It should be comparable to `Version` (additional operator overloads)

## API

```c#
namespace System 
{
    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 prereleaseLabel);
        public SemanticVersion(int major, int minor, int patch, string prereleaseLabel, string metadata);
        public SemanticVersion(int major, int minor, int patch, IEnumerable<string> prereleaseLabel, IEnumerable<string> metadata);
        public SemanticVersion(string version);

        protected SemanticVersion(Version version, string prereleaseLabel = null, string metadata = null);
        protected SemanticVersion(Version version, IEnumerable<string> prereleaseLabels, IEnumerable<string> metadata);

        public int Major { get; }
        public int Minor { get; }
        public int Patch { get; }
        public virtual IEnumerable<string> PrereleaseLabels { get; }
        public virtual bool IsPrerelease { get; }
        public virtual IEnumerable<string> Metadata { get; }
        public virtual bool HasMetadata { get; }

        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(Version other);

        public virtual int CompareTo(object obj);
        public virtual int CompareTo(SemanticVersion other);
        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 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);

        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);
    }
}
Design Discussion api-needs-work area-System.Runtime

Most helpful comment

As an addition, I'd want to see an associated comparer that could properly sort SemanticVersion and Version values together using semantic versioning rules.

All 76 comments

As an addition, I'd want to see an associated comparer that could properly sort SemanticVersion and Version values together using semantic versioning rules.

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 System.SemanticVersion could look like. The changes are including Version comparison operators and conversions, renaming the comparison enum (this will be something we'll have to find common ground on for NuGet to use those overloads, or add methods). I left updated comments on members I thought may be a little confusing.

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.

``` C#
namespace Sysem
{
public class SemanticVersion : IFormattable, IComparable, IComparable, IEquatable, IComparable, IEquatable
{
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 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:

  • Obvious: what's missing?
  • Overloads: NuGet goes above and beyond this on comparisons, do we need more extension points?
  • Should an implicit operator to Version be included? I'd think this does more harm than good.
  • Should conversions to Version fail if prerelease labels are present? If so, how?
  • Version ranges are a thing, should that be handled in another class? I'd learn towards yes.
  • What variety of formats should be allowed? I think NuGet's implementation has this pretty well covered already, that may be a lift, functionality wise. There are some allocations we can eliminate though.

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 Version 1.0.9-1pre1 what does that parse into? Are we assuming that 1pre1 is by itself a single release label?

I think that if we're gonna handle sem-ver, let's go ahead and do ranges. Those could be nullable LowerMajor, LowerMinor, LowerPatch as additional fields, and then we could incorporate those into the operator methods. This is because we would assume that your two options are "upper - lower" when you parse things, and that you won't have an array of versions. That would need to be handled with an array of Version or SemVer.

  1. What are the scenarios for subclassing SemanticVersion? Why not make it sealed like Version and be fully immutable like Version?
  2. Consider a strongly-typed Clone() method that returns SemanticVersion instead of having a copy constructor. SemanticVersion could also implement ICloneable explicitly (privately) with it's ICloneable.Clone() implementation returning the result of calling the strongly-typed Clone(). This would be more consistent with Version which has a Clone() (albeit, not strongly typed) instead of a copy constructor.
  3. Override GetHashCode() (since it overrides Equals() and implements IEquatable<SemanticVersion>)?
  4. Override ToString()?
  5. The operator parameter names should be left and right instead of version1 and version2 according to the framework design guidelines.
  6. What about Parse/TryParse?

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.

@karelz

Side question out of curiosity: Are all those 15 folks watching the whole repo or was the issue promoted somewhere (Twitter, MVP summit)?

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 think that if we're gonna handle sem-ver, let's go ahead and do ranges.

I don't disagree...but what's that look like, in API form? Pitch out some ideas?

@justinvp Responses:

What are the scenarios for subclassing SemanticVersion? Why not make it sealed like Version and be fully immutable like Version?

NuGet does additional comparisons in NuGetVersion, you can find it here. Whether it's best for them to subclass this or not is of course up for debate. Maybe sealing it is better, I'm not sure.

Consider a strongly-typed Clone() method that returns SemanticVersion instead of having a copy constructor.

Good idea, that's a better approach. Updated.

Override GetHashCode() (since it overrides Equals() and implements IEquatable<SemanticVersion>)?
Override ToString()?

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.

The operator parameter names should be left and right instead of version1 and version2 according to the framework design guidelines.

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.

What about Parse/TryParse?

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):

``` C#
namespace Sysem
{
public class SemanticVersion : IFormattable, IComparable, IComparable, IEquatable, 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 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 public SemanticVersion Clone() method would need to be virtual based on the current pattern.

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 ReleaseLabels needs a way to be set from a derived class as well. Not sure if it should just be an IList<string> or be virtual?

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.

  1. Implement the same operators on Version.
  2. Implement the operators on SemanticVersion with left and right swapped, e.g.:

csharp public static bool operator ==(SemanticVersion left, Version right); public static bool operator ==(Version left, SemanticVersion right); public static bool operator !=(SemanticVersion left, Version right); public static bool operator !=(Version left, SemanticVersion right); //etc...

  1. Decide it is not a problem that needs to be solved.

Personally I like the idea of Version also having matching operators. I suppose there is probably precedent somewhere in the framework that should be followed for how this might be solved.

This also opens up the discussion that currently a SemanticVersion knows how to compare itself to a Version, but a Version doesn't know how to compare itself to a SemanticVersion.

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.

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.

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 SemanticVersion, if that can be reasonably done. I'm not sure I follow on the second half. Many more people than NuGet have a need for SemanticVersion (hence this proposal), it should be more widely available that that, and IMO, a BCL include as Version is. I'm not 100% against it being in another library, e.g. System.SemanticVersioning, but can you provide some examples of how NuGet has evolved this?

A few followup questions:

  1. A concern: will anyone find it? Or will we have many developers creating it? I hope tooling solves this and makes it a non-issue.
  2. The arguments here are that some pieces are NuGet specific and have no place in a base version. Totally valid. What would be in this package then? A base version that NuGet can inherit in Nuget.Versioning? If so, have the bits in this base version revved any, or just the inheritor NuGet would maintain? I'm trying to ascertain the split of where the changes are happening.
  3. Does the trend towards fewer packages in .NET 2.0 not go against this approach of smaller ones for everything? Maybe @terrajobst can comment on suggestions there?

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.

After reading this, I agree. Thoughts from others on nuking the enum and overloads?

A semver1 vs semver2 class or distinction is required.

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.:

C# 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: BigInterger, and it implements both directions, though it's in another library so I think it's viewable to do either way given the inclusion situation.

@NickCraver

I looked at the first example I could think of being added later: BigInterger, and it implements both directions

That seems the best way to go. The more I think on it, the more I dislike the idea of Version knowing anything about SemanticVersion. I would then suggest that the following be added to the proposal:

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);

@NickCraver

  1. I believe the metadata should not be a single string field but rather an IEnumerable<string> just like ReleaseLabels. The semver "spec" has specific requirements about how these have to be constructed so it would seem like a good place to put the logic and not require users to look up and implement it themselves.
  2. Does the type have to be class or could it be a struct? That way, no Clone() is needed.
    I don't really see a benefit for using a class here since it is meant to be an immutable structure judging from the signatures. One would loose the ability to override the virtual members though (so it's just like sealed)
  3. On the sealed topic: If i build an API with it, i would expect the behaviour to be the one defined by the BCL and not from a different user's subclass - having an overwritten CompareTo() on an instance passed to an API method, this can potentially blow up. Or a subclass that returns a different metadata string on every call because someone really tries to break my code :trollface: .
    I do believe that there are benefits for implementing custom comparisons but then the user should make use of custom comparers and be aware of the context of the custom logic.
    The only use case i can think of is when a new SemVer version is released and there is a class SemanticVersion3 : SemanticVersion. But that would break any expectation about the behaviour just as much as a custom subclasses would.

Addition:
I would also very much like to see an AssemblySemanticVersionAttribute that makes use of the proposed SemanticVersion. Would be a piece of 🍰 to generate using the new msbuild system and be of great use for diagnostic / logging purposes.

I would also very much like to see an AssemblySemanticVersionAttribute that makes use of the proposed SemanticVersion. Would be a piece of 🍰 to generate using the new msbuild system and be of great use for diagnostic / logging purposes.

It would be super nice to be able to do this in one attribute instead of two:

c# [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.

:question: Why is the operator to convert Version to SemanticVersion an explicit operator instead of implicit? I'm generally a fan of avoiding implicit operators, but this seems like a case where the conversion would always succeed without loss of information or broken behavior.

:question: Is there ever a case where semVer {operator} (SemanticVersion)ver is not the same as semVer {operator} ver (where ver is a Version)?

Another question:

SemVer does not have 4 components in its version number, it has major, minor, patch. However, this API proposal allows creating an instance from a Version, either by cast or through the constructor. There are also constructor overloads that accept revision. However, it isn't exposed as a property like the other components.

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?

Nuget respects all 4 due to historical reasons and hence it can't be dropped (at least in the nuget type)


From: Jon Hanna notifications@github.com
Sent: Dec 2, 2016 11:32 AM
To: dotnet/corefx
Cc: Yishai Galatzer; Mention
Subject: Re: [dotnet/corefx] Proposal: Adding System.SemanticVersion (#13526)

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?

-
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdotnet%2Fcorefx%2Fissues%2F13526%23issuecomment-264541657&data=02%7C01%7Cyigalatz%40microsoft.com%7C35425a522d964b53c7ab08d41ae9e551%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636163039256770159&sdata=TpOf31p%2FFKiBgbDDucfbgrwSfJ7PnjPffBSPPdJ%2BGXQ%3D&reserved=0, or mute the threadhttps://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FABLmt9tG6qPjiygl6FsuOAz5yKC0zeLzks5rEHIygaJpZM4Kt7_i&data=02%7C01%7Cyigalatz%40microsoft.com%7C35425a522d964b53c7ab08d41ae9e551%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636163039256770159&sdata=Ok6ojCfSCQfi%2FaBVvyvL58dOpnFa2mMWsmbz2zDazfk%3D&reserved=0.

@NickCraver I have updated your initial comment to contain the proposed API. Please take a look and let me know if it looks right.

@yishaigalatzer can you please review the API proposal as well?

@yishaigalatzer is OOF, he suggested to ask @rrelyea and @emgarten from NuGet team for the API review.

@karelz @AlexGhiondea I feel pretty strongly that these should also be part of the proposal: https://github.com/dotnet/corefx/issues/13526#issuecomment-260192413. Otherwise, the equality operators are not symmetric.

This is the same design as BigInteger as @NickCraver pointed out, and solves the problem where version == symanticVersion would not compile but symanticVersion == version would.

@vcsjones agreed. I updated the comment at the top with those APIs.

@rrelyea, @emgarten did you get chance to review the API? We have BCL API review tomorrow morning and would like to make the call ideally.

  1. I would remove ToNormalizedString and ToFullString, they exist in NuGet for legacy reasons. NuGet allows versions with leading zeros (ex: 1.0.0001) and ToString returns it verbatim. For this class I would expect it to throw during parse if an invalid version were passed in, which would eliminate the need for ToNormalizedString. NuGet uses ToFullString to make the caller opt in to getting the build metadata, but for this new class I would expect it to be the default ToString behavior, ignoring metadata makes sense for NuGet, but I don't think other users would want that opinion.
  2. Metadata as IEnumerable<string> makes sense as @dasMulli pointed out, it should be the same as ReleaseLabels, semver has similar rules for them. NuGet just didn't have a reason to read them individually.
  3. The constructors with revision can be removed as @vcsjones said. In NuGet the SemanticVersion class holds onto this for the NuGetVersion class that extends it and compares with the 4th digit. Here it would never be used.
  4. It is a bit unclear how a System.Version with a non-zero revision would be parsed here, would this throw as invalid?
  5. A Release property that joins the release labels together might be helpful, but this could also be done with the format provider. Same for Metadata if it is moved to IEnumerable.
  6. The 2.0.1-rc comparison mentioned in the comments refers to comparing release labels case-insensitively, which was never approved. For NuGet treating 1.0.0-BETA and 1.0.0-beta as completely different versions does not work well for URLs, and it causes problems when trying to extract them to disk on a case-insensitive file system. I think it is worth discussing what the behavior should be for System.SemanticVersion, and if it would be confusing to differ from NuGet.
  7. Metadata and ReleaseLabels should be the same in terms of virtual or non-virtual.

NuGet could build on this class and expose the 4th digit for legacy. Much of the logic is in version comparer and formatter, if those could also be overridden in places to allow keeping the difficult release label comparing the same, but allowing extra comparing on the revision it would be perfect. 🎉

@karelz @emgarten Can we clarify if this proposal is going to have 4 components to cover NuGet's needs, since they do require 4 version components, or that this is supposed to be a true reflection of SemVer?

I'm holding my breath and hoping you'll do the best thing for the wider BCL consumer base, even if that isn't ideal for NuGet.

@NickCraver do you want to incorporate NuGet team feedback from @emgarten above?

Can we clarify if this proposal is going to have 4 components to cover NuGet's needs, since they do require 4 version components, or that this is supposed to be a true reflection of SemVer?

I would expect System.SemanticVersion to be 3 parts and follow the SemVer rules. NuGet can continue supporting the legacy 4 part versions separately.

Here's a revised API based on feedback that mostly removes the NuGet specific pieces. I want to sanity check here before updating the top (since GitHub doesn't show history):

```c#
namespace System
{
public class SemanticVersion : IFormattable, IComparable, IComparable, IEquatable, 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 releaseLabels, IEnumerable metadata);
public SemanticVersion(string version);

    protected SemanticVersion(Version version, string releaseLabel = null, string metadata = null);
    protected SemanticVersion(Version version, IEnumerable<string> releaseLabels, IEnumerable<string> metadata);

    public int Major { get; }
    public int Minor { get; }
    public int Patch { get; }
    public virtual IEnumerable<string> ReleaseLabels { get; }
    public virtual bool IsPrerelease { get; }
    public virtual IEnumerable<string> Metadata { get; }
    public virtual bool HasMetadata { get; }

    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(Version other);

    public virtual int CompareTo(object obj);
    public virtual int CompareTo(SemanticVersion other);
    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 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);

    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);
}

}
```
Open questions:

  • Are these the proper constructors?
  • What does System.Version with a Revision > 0 do?
  • Should we add Metadata/Release properties which are string representations?
  • No one noticed I misspelled "Sysem"? Really?

@NickCraver looks good!

I would not add the string representations for Metadata/Release -- do you have a use case in mind where that would be useful?

What do you mean by

What does System.Version with a Revision > 0 do?

I am curious about the protected method that has optional parameters on it -- is that needed in the implementation?

I ended up writing my own SemanticVersion, so would love to see one in the BCL.

A question on HasMetadata; does it need to be virtual, any reason is can't simply be:
public bool HasMetadata => Metadata != null; (?)

@NickCraver

Simplifying interoperability with Version

I think the most important question is what to do with a System.Version where Revision > 0. If at all possible, I would want the answer to this question to have the following characteristic:

For all SemanticVersion s and Version v, and operator op in (==, !=, <, <=, >, >=):

  1. The expression s op v is true if and only if s op (SemanticVersion)v is true
  2. The expression v op s is true if and only if (SemanticVersion)v op s is true

:bulb: If the characteristic holds, I believe we can make the following changes:

 public virtual bool Equals(SemanticVersion other);
-public virtual bool Equals(Version other);

 public virtual int CompareTo(object obj);
 public virtual int CompareTo(SemanticVersion other);
-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 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);

-public static explicit operator SemanticVersion(Version version); 
+public static implicit 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);

Inheritance

:bulb: At this point, I would recommend making the type sealed. In particular, I don't find the use case of attempting to layer NuGet's pseudo-semver handling on top of the proposed SemanticVersion class to be a compelling reason to make the new SemanticVersion class inheritable.

Cloning

:question: Why are we implementing ICloneable here?

:bulb: Since SemanticVersion is immutable, I would suggest removing the ICloneable interface and both Clone methods.

-public class SemanticVersion : IFormattable, IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>, ICloneable
+public class SemanticVersion : IFormattable, IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>
 {
     ...

-    public virtual SemanticVersion Clone();
-    object ICloneable.Clone() => Clone();

Labels

:question: Is there any reason not to provide access to a list of information instead of just an enumerable?

-public virtual IEnumerable<string> ReleaseLabels { get; }
+public virtual IReadOnlyList<string> ReleaseLabels { get; }
 public virtual bool IsPrerelease { get; }
-public virtual IEnumerable<string> Metadata { get; }
+public virtual IReadOnlyList<string> Metadata { get; }
 public virtual bool HasMetadata { get; }

@sharwell I'm still not sure how revision should behave, but any exceptional behavior is made far less clear with the implicit operator, IMO.

To address points specifically:

  • Implicit operator changes:

    • On errors (re: Revision): it's not the comparison that failed, but a cast I didn't make. That's unintuitive for any use case. It also means we can't add any intellisense to those specific overloads telling the user why it'd blow up for the Revision cases (if that ends up being the behavior).

    • It is, by necessity, an allocation of this SemanticVersion on every comparison. That's a pretty bad inefficiency.

    • Inheritance: maybe so, I'd love for the NuGet team to chime in, and it's worth noting they may be one of potentially more use cases. SemVer has gone from 1.0 to 2.0, so the inheritable nature was more so with that in mind. That's a messy road in any situation, I don't see a clear path other than inheriting.

  • Cloning: it's mirroring System.Version here (and I assume: so is Nuget). I can't think of daily uses - perhaps someone can comment on System.Version's ICloneable history?
  • Labels: if we're sealing, I agree, read only collections as the accessor make sense. We'd need to make sure these aren't an allocation on access either and are backed as slim as possible.

@AlexGhiondea

What do you mean by "What does System.Version with a Revision > 0 do?"

The issue there is say we have a System.Version with a Revision of 6, what do we compare that to? It's basically a loss of precision issue since that 4th field is not a concept in semantic versioning.

I am curious about the protected method that has optional parameters on it -- is that needed in the implementation?

Depends if we seal, if it's sealed, that certainly changes.

@Worthaboutapig: Same story, depends on sealed or not. And the backer. I assume we'll likely store as a minimal ReadOnlyList<T> type on the backend, and assume null would be the case for no items passed to the constructor, yes. There's no reason to allocate if we have no members on the passed enumerable.

@NickCraver

  • Implicit operator changes:

    • "On errors" assumes errors. One way to convert Version to SemanticVersion is to use the format Major.Minor.Build+Revision. I believe this conversion also meets all of the conditions described in my previous comment.

    • The allocation for operators is not likely to be a problem in practice. Typically people will be working with either Version or SemanticVersion, but not both. I would actually find it acceptable to keep the operator explicit and still remove all the other items, forcing the user to cast if they plan to use Version.

@sharwell While I agree that conversion may meet your conditions, it doesn't fit the scenario. You're effectively tossing away the revision precision (the root issue), or we decide to compare metadata as numbers for all comparisons...which also doesn't make much sense in a global approach.

As for allocations - we're shying away from that across the framework. I agree an explicit cast and API reduction is probably the happy medium there. Or, a constructor that takes Version simply being public...since that'd have to be the underlying implementation anyway.

@sharwell if we just add Build and Revision you can get the wrong answer.
Consider the case where you have 2 versions: 1.0.0.4 and 1.0.3.0 If you convert them to SemanticVersion you are going to get 1.0.4 and 1.0.3 which is not what you want.

If we want to normalize Build and Revision we can do something like Build*100 + Revision.
To take the example from above, using this transformation you will get 1.0.4 and 1.0.300 which will maintain the relationship between the two versions.

The downside of this is that they are now looking rather ugly... and you might end up with an overflow when doing the transformation.

Stepping back, I think this type is going to be useful as a whole so I would like to move forward with a formal API review and discuss these few remaining open issues during the meeting. The behavior of the Revision != 0 conversion is an implementation detail and should not block progress on the API shape.

Looking at the API shape, the only think I am not sure of is the use of optional parameters.

@NickCraver - other than the behavior for Revision != 0 question is there anything else we need to figure out before we submit this for API review ?

@AlexGhiondea I think it's ready for review and most productive in a call. The Revision detail may affect API shape though (the necessary comparison operators to independently handle it may or may not be required). Let's see what a live discussion yields :)

@NickCraver great! I have marked it as such!

Would you mind updating the top post with the most recent shape of the API to make it easy to find during the review?

@AlexGhiondea absolutely, done!

Since only pre-release versions have labels, would it make sense to rename ReleaseLabels to PrereleaseLabels?
Since there is constructor that takes a string releaseLabel parameter, it may mislead users unfamiliar with the semver spec to assume this parameter to be any form of metadata (e.g. "RTM") and then be surprised that IsPrerelease is true. This would be different if the parameter (and the property) was string prereleaseLabel.

Since only pre-release versions have labels, would it make sense to rename ReleaseLabels to PrereleaseLabels?

Yes. Please.

PrereleaseLabels makes a lot of sense to me, it's clearer and they can't be used any other way.

Serious question: how is this going to be used in practice? I fully understand the lack of ability to store pre-release info is a problem with System.Version, no questions there. The problem I see a bit is that an assembly currently has two version info elements: a System.Version instance and a FileVersionInfo element of its file (on windows at least). These often differ: the System.Version part is kept the same to please strong naming and the FileVersion is updated to have a way to differ the real version (something the .NET full framework has used for years, if I might add).

Adding SemanticVersion adds a third version to an assembly. If we see an assembly as a nuget package, it has 4: the nuget package can be versioned separately.

Aren't we creating more confusion by trying to get rid of confusion? I mean: if an assembly has a SemanticVersion property, why look at it if there's a Version property too, and vice versa? Doesn't an assembly have just one version? Or more importantly: isn't SemanticVersion a specific 'Version' (and therefore should be a subtype) ? Or is SemanticVersion the same as FileVersionInfo, an informative element that you can use if you want to to inform yourself about the real version, but isn't really used in code / machinery?

@FransBouma I don't understand tying all reasoning to .NET assemblies here, SemanticVersion is used in far more places than that. What initiated this was me parsing Elasticsearch versions, as an example. Tens of thousands of software projects, package management systems, etc. use this.

Even in the .NET case, this reasoning is not correct. Semantic versioning is applied to a package, not an assembly. It's already in use this way today, right now. It's simply tucked away in the NuGet code and unavailable for the many thousands of other use cases we have. This proposal aims to resolve all that, and open it up to those additional use cases, via availability in the BCL.

As for the difference: Version is sealed, and semantic versioning has no 4th (build) number. They are not compatible. The comparison semantics would become quite complicated as well.

Ah ok, then I misunderstood, as System.Version's usage is on assemblies (and elsewhere) and I wondered where this is going to be stored, as it was unclear to me it would be used to deal with nuget packages, not assemblies. Thanks for clearing that up!

@FransBouma I use it for database and file format versioning, as well as AssemblyInformationalVersion.

Note: I updated ReleaseLabel to PrereleaseLabel (and corresponding arguments) per discussion above - top post has the updated API.

Shouldn't this be a struct? It's naturally a value type in terms of equality, it's immutable, and it has all of the typical interfaces/operators of a value type. And who needs virtual members on it anyway? For example, does anybody really need to derive from SemanticVersion to override the Boolean IsPrerelease property? :-) Instead, how about a static factory method and a builder pattern with params arrays to make it declarative?

SemanticVersion alpha150 = SemanticVersion.Prerelease(1, 5, "alpha", "label2", "label3")
                                          .WithMetadata("meta1", "meta2", "meta3");

Shouldn't this be a struct? It's naturally a value type in terms of equality, it's immutable

I couldn't agree more.
I think the type should be "reasonably immutable" meaning it should be either a struct or a sealed class with read-only properties only.

I don't think it should be valid to subclass SemanticVersion as it would break any assumption one would have about it's equality and the behaviour of the getters / methods. (So no one can "sneak in" unexpected behaviour)
For cases when you'd need a custom version (e.g. company-specific versioning that has e.g. ?signedOff=true) it is perfectly reasonable to copy the code and modify it, creating a new MyCompanyVersion type and implementing conversions wherever possible.

I hate sealing things just because someone _might_ do something naughty. Why bother with interfaces and polymorphism at all? If you want to be certain that you are really working with an unaltered SemanticVersion then do some type checking.

@dasMulli I don't think having to copy/paste/change a type is a good path to go down. I would like us to come up with a type that is extensible in meaningful ways and enables as many people as possible to use it.

@AlexGhiondea Why would you have to copy/paste/change this type? It should be a type that does one thing and does it well (single responsibility principle); namely, it implements the "semver" spec. If you need it to do anything else, then why not just use object composition instead? That way you'll avoid confusing people, rather than changing the meaning of members on a standard type.

public class MyFunFlexibleType
{
    public SemanticVersion SemVer { get; set; }

    public virtual bool IsPrereleaseForRealzOrSomethingElseMaybe { get; set; }
}

Yes copy&modify would be pretty extreme.
@somewhatabstract i share your concern that classes should not be sealed by default. But here we need to distinguish between "logic" classes and types for fixed data representation.
One could even argue that the proposed type can be split into a type storing the fields and types doing the semver interpretation.. (but I would then argue that the "semver spec" defines both and a possible "semver 3.0.0" could require changes to both aspects).

I would instead look for compelling reasons to subclass that do not violate the semver spec. If we truly care about polymorphism, then: is a type that for example implements IsPrerelease differently still behaving as a "semantic version"? I'd argue that this is not the case and either create an extension method (IsSafeToDeployToProduction()), another type through composition as @RxDave showed or make an IPrereleaseVersioningStrategy a thing for my logic (like implementing the linux kernel versioning strategy where all odd minor versions were considered development builds).
I'd say it is like DateTime and DateTimeOffset. DateTimeOffset is not a DateTime from a type system perspective but offers appropriate conversions (.DateTime, .UtcDateTime). Also, no one would should consider subclassing DateTime to make .Year return a value using a different calendar and use a Calendar instance instead where this special year value is necessary (not discussing that having .Year and friends on DateTime was probably a bad idea anyway).

We talked about two major aspects of this proposal offline while I was on campus this week. They were:

  • Is this worth it to include in CoreFX?
  • If so, where would it live, package-wise?

The first needs community help. A decent addition to the BCL includes usage in the BCL, or a large set of known usages outside the BCL (e.g. being a handy class for many consuming the BCL).

What are your use cases?

Chiming in here would help frame the worth greatly. Add them up and we can maintain a distinct list at the top. Use cases inside the BCL (or not) also help the decision of where it lives.

As an example, one use case I'd like is [assembly:SemanticVersion("1.0.0-alpha1")], so that we can determine which DLLs are pre-release. Production apps don't have packages in most scenarios, they have a directory of DLLs. Currently you cannot lineup the DLL version inside a pre-release package with the package. So you either have a string version in the informational attribute, or something that doesn't match (usually the same version without the pre-release suffixes, which is not correct). A standard place would be an improvement here, IMO.

Where does it go?

The second question of where does it live is a little more complicated. It doesn't have a set of sibling classes that'd denote a namespace (e.g. System.Versioning). So do we put it in the BCL as System.Version? Do we put it in a NuGet package by itself? If so, who maintains it? Me or Microsoft. If Microsoft, where does the repo live so that it can be updated? Is this a good strategy to put all one-offs in their own package? What about a CodeFX.Contrib repo? Would it be one package or many? There's a lot on the discussion table here and we agreed there's no clear win scenario. No one wants to see CoreFX be a dumping ground of one-off classes, but where do useful things go?

If we don't pass the bar for number 1, then 2 doesn't matter. So let's see where the first stands with feedback here.

cc @terrajobst @karelz in case they want to expand on any of the above

Thanks @NickCraver for writing it up!
For context: We have been discussing the general topic internally since start of December (and there is no obvious consensus). I was trying to find best way to bring the discussion to the open - this particular issue/API looks like a good candidate for that.
Regarding 2 - it is interesting general discussion regardless how 1 ends. At minimum I expect it will keep coming back in future API proposals.

cc: @danmosemsft @stephentoub @KrzysztofCwalina @weshaggard

I know that we would use this extensively in PowerShell scripts for builds and deployments, both locally in our dev environments and on CI servers. It would also be incredibly helpful to us in our production deployments. We work in healthcare and being able to tag versions with metadata would be very helpful in tracking what has been deployed to which customers and in turn, using that for audit logs of usage.

That said, the most value for this is in the management and interaction with contemporary package management systems, which use semantic versioning. This probably narrows the audience down to mostly those in the development arena, which may make the bar too low for inclusion in the BCL since production usage seems less likely, though I would hope that is not the case.

I know we would use this a lot (it would certainly tidy up some of our hacks that fill in the gap where it should be). I do worry about how these things get packaged if not in the BCL since one package per feature would make package restore a horrible experience in the long term but bundling too many things together also feels wrong (shipping tons of code that doesn't get used). That's an age old problem though and I don't know what the right answer is.

Besides covering vast consumer requirements, do we need to consider SemVer's own Spec versions?

  1. would this library be compatible with both SemVer 1 and SemVer 2 specs: http://semver.org/ (diff: https://github.com/mojombo/semver/compare/v1.0.0...v2.0.0#files_bucket)?

    • Shortly, from this comment https://github.com/mojombo/semver/issues/231#issuecomment-278617758, there are at least two breaking changes between spec v1 and v2:

      • leading zeros are not accepted (this one matters for library implementation viewpoint)
      • minor version numbers MUST be incremented if API is marked as deprecated (this is more of the usage behavior), so doesn't effect the library implementation
    • One way could be to add an overloaded ctor:
      ```c#
      ...
      ...
      public SemanticVersion(string version) : this(version, SemVerSpecVersion.Version1)

    public SemanticVersion(string version, SemVerSpecVersion version)
    // this should throw if there is leading zero, e.g.
    // SemanticVersion("1.02.3", SemVerSpecVersion.Version2)
    ```

  2. how would this library keep up with the future (v3, v4) SemVer specs?

how would this library keep up with the future (v3, v4) SemVer specs?

Since any new major version of SemVer will be a breaking change by definition, it will likely be a breaking change to pass a SemVer3 version to a piece of code expecting a SemVer2 version.

As much as I hate numbers on type names, future variant names like SemanticVersion3, SemanticVersion4 could make sense since here. As long as there are conversion methods/constructors/operators(/interfaces?), it is probably more manageable from a type safety perspective than adding features to SemanticVersion and having a weird version property on a version type.

For all the pkg management eco-systems that have adopted semver (NuGet, npm, PowerShell, Conan, etc) it is a major bummer that .NET Core still does not provide support for semantic versions.

You mean all of those ecosystems have first class support in their BCL for parsing semantic versions? Is that true? Can you point me at the examples?

@davidfowl no they don’t. And even npm is not “in node” anyway.

NuGet:
https://github.com/NuGet/NuGet2/blob/2.13/src/Core/SemanticVersion.cs

npm - well this one claims to be "the semantic versioner for npm"
https://github.com/npm/node-semver

PowerShell:
https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.semanticversion?view=pscore-6.2.0

Conan - OK Conan's a bit weird - I'd say they have quasi-semver support. The version numbers we use for our Conan pkgs are semver and they support specifying version ranges in conanfile.txt/py that appear to obey semver rules.
https://github.com/conan-io/conan/blob/b38f6c5a6de87f9bf63f481a1d7dfbcf1abd87fd/conans/model/version.py

Ah, sorry in their "BCL equivalent". OK fair point but I thought frameworks were about providing common functionality so that it doesn't get re-invented over and over (like in NuGet and PowerShell)?

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?

  1. There are many _ecosystems_.
  2. Each ecosystem uses the Semantic Version standard in some way.
  3. No ecosystem has a reference implementation of the standard as first class support in their BCL.

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?_
Since .Net developers use Semantic Version standard and communicate with other ecosystems which use Semantic Version then the reasonable answer is yes.
But that would take us 4 years back to the beginning of this discussion - and this begs the next question -

what are we doing wrong if we can't move forward?

The reasonable answer is: _System.SemanticVersion class is not what we need._

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)?

  1. They use some many years traditions for versioning
  2. Now they try to use Semantic Version standard
  3. They use some Semantic Version _rules_ for versioning - we can say they use _ specific for the ecosystem a subset of Semantic Version standard_.
  4. Whole ecosystem (all projects, services and so on) depends on the rules.
  5. They embed the Semantic Version in their traditional versioning in some way - _they defines specific rules for mapping Semantic Version and traditional versioning_.
  6. _They interact with other ecosystems_.

The same for the .Net ecosystem

  1. Uses System.Version class and AssemblyVersion attribute and the old school versioning schema
  2. Uses Semantic Version in some places.
  3. Has specific rules. Ex. for .Net Core https://docs.microsoft.com/en-us/dotnet/core/versions/
  4. Yes, changing something in .Net Core versioning could break all third-party projects and services.
  5. Yes, see p.3 for .Net Core, PowerShell also has simple mapping rules.
  6. Yes, thus PowerShell distributes packages for same Linux distributives with slightly different rules for versioning.

Now we know why here is no progress

  1. Whole ecosystem (all projects, services and so on) depends on the rules.
  2. Yes, changing something in .Net Core versioning could break all third-party projects and services.

Let's look PowerShell ecosystem. If we move PowerShellGet to Semantic Version we should:

  • implement new PowerShellGet site
  • create new PowerShellGet PowerShell module
  • enhance PowerShell itself
  • all PoweShell module developers should update their modules.

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.
Then it becomes obvious that the only solution that will make progress is to enhancement the System.Version class itself.

What are the requirements for enhancing System.Version class?

  1. No breaking changes
    Really this class is so simple that I hope it is not difficult to add new functionality without breaking changes
  2. Obviously we need to add Semantic Version specific properties.
  3. Support generic behavior
    Since every ecosystem can have specific rules for versioning (parsing, mapping, comparing and so on) the new design should support these customizations.
  4. Support defaults and changing defaults
    Since every ecosystem can have specific rules for versioning they could implement these rules and share its for whole ecosystem.
    So the new design should support implementations of the rules _out of_ the System.Version class, the class should accept the implementations by arguments and has global defaults.
  5. Design and implement defaults for .Net ecosystem. I believe most of .Net projects could benefit from this as a _best practice_.
Was this page helpful?
0 / 5 - 0 ratings