Skip to content
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

Need a performace and unified way to compare versions #3212

Closed
wli3 opened this issue Apr 19, 2018 · 27 comments · Fixed by #4911
Closed

Need a performace and unified way to compare versions #3212

wli3 opened this issue Apr 19, 2018 · 27 comments · Fixed by #4911
Assignees
Labels

Comments

@wli3
Copy link

wli3 commented Apr 19, 2018

In msbuild condition. msbuild build will try to parse it as dec or hex first. So 2.11 will be smaller than 2.2.

<Project ToolsVersion="15.0" DefaultTargets="Try">
  <Target Name="Try">
   <Error Condition=" '2.11' &lt; 2.2 "
           Text='unexpected' />
  </Target>
</Project>

At the same time. If appending 0 at the end as in 2.2.0, it will cause parse as number fail and consider it as Version. However, Version class's comparison does not consider 2.1 and 2.1.0 to be equal. In fact 2.1 < 2.1.0. This will also occur in Property Function.

<Project ToolsVersion="15.0" DefaultTargets="Try">
  <Target Name="Try">
   <Error Condition=" '2.1' &lt; 2.1.0 "
           Text='unexpected' />
  </Target>
</Project>

It is different behavior of NuGet version.

Also there is performance concern.

@wli3
Copy link
Author

wli3 commented Apr 19, 2018

cc @dsplaisted @nguerrera @rainersigwald I hope we can discuss it on next Monday's sync meeting

@rainersigwald
Copy link
Member

What's the performance concern? Something you've measured, or just theoretical?

@wli3
Copy link
Author

wli3 commented Apr 19, 2018

@nguerrera do you have concrete example?

@nguerrera
Copy link
Contributor

nguerrera commented Apr 19, 2018

Before we can say what the performance issue is (if any), I think we have to say what the code we would write would be.

  1. Is there an evaluation-friendly way to compare versions without falling prey to 2.1 < 2.1.0 (System.Version annoyance) or 2.11 < 2.2 (Version interpreted as float).
  2. If it exists, is (1) reasonable to maintain?
  3. If it exists, is (1) reasonably fast compared to what we have now.

@dsplaisted
Copy link
Member

@wli3 This is on the agenda for Monday's meeting

@wli3
Copy link
Author

wli3 commented Apr 19, 2018

(edit description according to above)

@rainersigwald
Copy link
Member

The answer @wli3 figured out to 1 is:

dotnet/sdk#2158 (comment)

$([System.Version]::Parse("2.11").CompareTo($([System.Version]::Parse("2.2")))) < 0

This seems pretty reasonable to me, and I have no reason to suspect it'd be a significant performance bottleneck--that's why I was asking "measured or theoretical?".

@nguerrera
Copy link
Contributor

If the answers to my questions: are:

  1. yes, use this...
  2. yes
  3. yes

Then, great we'll change our code to that. If there's a no in there, then we may need an msbuild feature.

@nguerrera
Copy link
Contributor

Try

$([System.Version]::Parse("2.1").CompareTo($([System.Version]::Parse("2.1.0")))) < 0

@nguerrera
Copy link
Contributor

nguerrera commented Apr 19, 2018

I think we first have to normalize both sides to four parts.

@nguerrera
Copy link
Contributor

Somewhat separate, but we would also like there to be a way to compare semantic/nuget versions. We set $(NETCoreSdkVersion) currently based on that and we would like customers to be able to reason about it with minimum versions. Currently, Roslyn has code that is enforcing it as an exact version, and I'd like to get them to change it to a min version, but it occurs to me that I don't know how to write that.

@rainersigwald
Copy link
Member

Can we get NuGet's SemanticVersion put into System? I don't want to add another dependency or reimplement semver in MSBuild :(

@rainersigwald
Copy link
Member

Ah, already proposed: dotnet/corefx#13526.

@cdmihai
Copy link
Contributor

cdmihai commented Apr 19, 2018

If we had System.SemanticVersion, would we want to hardcode it in, as general enough? Or provide a way in which different package managers could plugin their own version comparers? I think there's little chance of having other package managers (using something else other than semver) with tight msbuild integration right? :)

@rainersigwald
Copy link
Member

I'd say we expose the ".NET Framework way". That's what we've done historically with System.Version but it doesn't behave according to modern expectations (witness 2.1 vs 2.1.0). If we get System.SemanticVersion, that would just be a continuation. We'd probably have to add some syntax to invoke it directly since we can't hijack the existing direct-comparison syntax, since 4-part versions aren't allowed in semver.

@nguerrera
Copy link
Contributor

Pragmatically, I'd want a single type / msbuild expression that supports 4 parts as an extension to semver. But I am not inclined to get into that debate. :(

@rainersigwald rainersigwald added this to the MSBuild 16.0 milestone Aug 17, 2018
@rainersigwald
Copy link
Member

For 16.0, I propose

$([MSBuild]::CompareVersions($(left), $(right)))

That:

  • Version.Parse()s each side
  • Pads the resulting object with .0 to reach 4 parts
  • Returns System.Version.CompareTo() on the results.

Think this would be totally useless without support for prerelease semvers? Speak up, please!

@clairernovotny
Copy link
Member

clairernovotny commented Aug 17, 2018

I think this would be helpful without the prerelease semvers, but adding prerelease semvers is even better. Otherwise, we have to split, and if it's a property that might be, we always have to copy/test for it. Think .NET Core SDK version, which has prereleases and not.

@dsplaisted
Copy link
Member

I think it would be helpful if CompareVersions would first drop the prerelease specifiers before parsing versions. That way all prereleases of a given release are treated as equivalent in version to the RTM, but that may actually be a good thing. It means you can't write logic testing if you are on a given prerelease or later, but it also means that you can test that the version is greater than or equal to the RTM version, and still have that logic work when that version is in prerelease.

@rainersigwald
Copy link
Member

rainersigwald commented Aug 17, 2018

How about CompareVersions(string left, string right, bool stripAfterHypen = false)?

But maybe it's sufficient to always do that, and doc the behavior.

@nguerrera
Copy link
Contributor

But maybe it's sufficient to always do that, and doc the behavior.

I think I'd prefer that or something that will read better than a literal true in code review. CompareVersionsIgnoringPrerelease or something.

@nguerrera
Copy link
Contributor

Do we also need to worry about versions with '+' and no '-', such as 15.8.166+gd4e8d81a88

Could we get away with stripping after first non-digit-or-dot?

Also, can you allow leading v so that we can compare directly against TargetFrameworkVersion without our ugly _TargetFrameworkVersionWithoutV?

@dsplaisted
Copy link
Member

Also, can you allow leading v so that we can compare directly against TargetFrameworkVersion without our ugly _TargetFrameworkVersionWithoutV?

👍

@nguerrera
Copy link
Contributor

nguerrera commented Jun 7, 2019

Putting a note here that we will support $(_TargetFrameworkVersionWithoutV) outside the SDK. People use it already. But when we do a feature that compare against $(TargetFrameworkVersion) directly, we should encourage folks to do that.

@nguerrera
Copy link
Contributor

(Putting that note here because I just approved a PR that uses it and points here.)

@rainersigwald
Copy link
Member

@nguerrera was frustrated by this again volunteered to take a look at this.

nguerrera added a commit that referenced this issue Nov 14, 2019
Add intrinsic property functions to compare versions

Usage: 
```
$([MSBuild]::VersionEquals(a, b))
$([MSBuild]::VersionNotEquals(a, b))
$([MSBuild]::VersionLessThan(a, b))
$([MSBuild]::VersionLessThanOrEquals(a, b))
$([MSBuild]::VersionLessThan(a, b))
$([MSBuild]::VersionGreaterThanOrEquals(a, b))
```

Versions are parsed like System.Version, with the following exceptions:

 * Leading v or V is ignored. Allows comparison to $(TargetFrameworkVersion)

 * Everything from first '-' or '+' to end of string is ignored. Allows passing
   in semantic versions, though the order is not the same as semver. Instead, 
   prerelease specified and build metadata do not have any sorting weight. This 
   can be useful, for example, to turn on a feature for >= x.y and have it kick
   in on x.y.z-pre.

 * Remaining part of string from above is parsed with Version.Parse, then 
   normalized to have 4 explicit parts to make x == x.0 == x.0.0 == x.0.0.0.

 * Whitespace is not allowed in integer components

 * major version only is valid ("3" is equal to "3.0.0.0")

 * `+`  is not allowed as positive sign in integer components (it is treated as 
   semver metadata and ignored per above)

The parser is adapted from System.Version source code, but simplified and tweaked
to handle quirks above, with trivial opportunities to improve perf / reduce 
allocations taken. Tests are also adapted from corefx.

Fix #3212
@EdwardBlair
Copy link

EdwardBlair commented Apr 24, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants