dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.
https://docs.microsoft.com/visualstudio/msbuild/msbuild
MIT License
5.22k stars 1.35k forks source link

Need a performace and unified way to compare versions #3212

Closed wli3 closed 4 years ago

wli3 commented 6 years ago

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 commented 6 years ago

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

rainersigwald commented 6 years ago

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

wli3 commented 6 years ago

@nguerrera do you have concrete example?

nguerrera commented 6 years ago

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 commented 6 years ago

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

wli3 commented 6 years ago

(edit description according to above)

rainersigwald commented 6 years ago

The answer @wli3 figured out to 1 is:

https://github.com/dotnet/sdk/issues/2158#issuecomment-382610785

$([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 commented 6 years ago

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 commented 6 years ago

Try


$([System.Version]::Parse("2.1").CompareTo($([System.Version]::Parse("2.1.0")))) < 0
nguerrera commented 6 years ago

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

nguerrera commented 6 years ago

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 commented 6 years ago

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

rainersigwald commented 6 years ago

Ah, already proposed: dotnet/corefx#13526.

cdmihai commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

For 16.0, I propose

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

That:

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

clairernovotny commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

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

nguerrera commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

👍

nguerrera commented 5 years ago

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 commented 5 years ago

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

rainersigwald commented 5 years ago

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

EdwardBlair commented 4 years ago

All components of the version must be integers greater than or equal to 0. Metadata restricts the major, minor, build, and revision components for an assembly to a maximum value of UInt16.MaxValue - 1. If a component exceeds this value, a compilation error occurs.

This implementation does not have such restrictions and can be used to compare, e.g. AssemblyInformationalVersion semvers.