|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Modified from https://github.com/dotnet/roslyn/blob/91571a3bb038e05e7bf2ab87510273a1017faed0/src/Compilers/Core/Portable/VersionHelper.cs
#nullable enable
using System;
using System.Diagnostics;
using System.Globalization;
namespace MS.Internal
{
internal static class VersionHelper
{
static readonly Version NullVersion = new Version(0, 0, 0, 0);
/// <summary>
/// Parses a version string of the form "major [ '.' minor [ '.' build [ '.' revision ] ] ]".
/// </summary>
/// <param name="s">The version string to parse.</param>
/// <param name="version">If parsing succeeds, the parsed version. Otherwise a version that represents as much of the input as could be parsed successfully.</param>
/// <returns>True when parsing succeeds completely (i.e. every character in the string was consumed), false otherwise.</returns>
internal static bool TryParse(string s, out Version version)
{
return TryParse(s, allowWildcard: false, maxValue: ushort.MaxValue, allowPartialParse: true, version: out version, hasWildcard: out bool _);
}
/// <summary>
/// Parses a version string of the form "major [ '.' minor [ '.' ( '*' | ( build [ '.' ( '*' | revision ) ] ) ) ] ]"
/// as accepted by System.Reflection.AssemblyVersionAttribute.
/// </summary>
/// <param name="s">The version string to parse.</param>
/// <param name="allowWildcard">Indicates whether or not a wildcard is accepted as the terminal component.</param>
/// <param name="version">
/// If parsing succeeded, the parsed version. Otherwise a version instance with all parts set to zero.
/// If <paramref name="s"/> contains * the version build and/or revision numbers are set to <see cref="ushort.MaxValue"/>.
/// </param>
/// <param name="hasWildcard">If parsing succeeds, indicates if a wilcard character was found in the version.</param>
/// <returns>True when parsing succeeds completely (i.e. every character in the string was consumed), false otherwise.</returns>
internal static bool TryParseAssemblyVersion(string s, bool allowWildcard, out Version version, out bool hasWildcard)
{
return TryParse(s, allowWildcard: allowWildcard, maxValue: ushort.MaxValue - 1, allowPartialParse: false, version: out version, hasWildcard: out hasWildcard);
}
/// <summary>
/// Parses a version string of the form "major [ '.' minor [ '.' ( '*' | ( build [ '.' ( '*' | revision ) ] ) ) ] ]"
/// as accepted by System.Reflection.AssemblyVersionAttribute.
/// </summary>
/// <param name="s">The version string to parse.</param>
/// <param name="allowWildcard">Indicates whether or not we're parsing an assembly version string. If so, wildcards are accepted and each component must be less than 65535.</param>
/// <param name="maxValue">The maximum value that a version component may have.</param>
/// <param name="allowPartialParse">Allow the parsing of version elements where invalid characters exist. e.g. 1.2.2a.1</param>
/// <param name="version">
/// If parsing succeeded, the parsed version. When <paramref name="allowPartialParse"/> is true a version with values up to the first invalid character set. Otherwise a version with all parts set to zero.
/// If <paramref name="s"/> contains * and wildcard is allowed the version build and/or revision numbers are set to <see cref="ushort.MaxValue"/>.
/// </param>
/// <returns>True when parsing succeeds completely (i.e. every character in the string was consumed), false otherwise.</returns>
private static bool TryParse(string s, bool allowWildcard, ushort maxValue, bool allowPartialParse, out Version version, out bool hasWildcard)
{
Debug.Assert(!allowWildcard || maxValue < ushort.MaxValue);
if (string.IsNullOrWhiteSpace(s))
{
hasWildcard = false;
version = NullVersion;
return false;
}
string[] elements = s.Split('.');
// If the wildcard is being used, the first two elements must be specified explicitly, and
// the last must be a exactly single asterisk without whitespace.
hasWildcard = allowWildcard && elements[elements.Length - 1] == "*";
if ((hasWildcard && elements.Length < 3) || elements.Length > 4)
{
version = NullVersion;
return false;
}
ushort[] values = new ushort[4];
int lastExplicitValue = hasWildcard ? elements.Length - 1 : elements.Length;
bool parseError = false;
for (int i = 0; i < lastExplicitValue; i++)
{
if (!ushort.TryParse(elements[i], NumberStyles.None, CultureInfo.InvariantCulture, out values[i]) || values[i] > maxValue)
{
if (!allowPartialParse)
{
version = NullVersion;
return false;
}
parseError = true;
if (string.IsNullOrWhiteSpace(elements[i]))
{
values[i] = 0;
break;
}
if (values[i] > maxValue)
{
//The only way this can happen is if the value was 65536
//The old compiler would continue parsing from here
values[i] = 0;
continue;
}
bool invalidFormat = false;
System.Numerics.BigInteger number = 0;
//There could be an invalid character in the input so check for the presence of one and
//parse up to that point. examples of invalid characters are alphas and punctuation
for (var idx = 0; idx < elements[i].Length; idx++)
{
if (!char.IsDigit(elements[i][idx]))
{
invalidFormat = true;
TryGetValue(elements[i].Substring(0, idx), out values[i]);
break;
}
}
if (!invalidFormat)
{
//if we made it here then there weren't any alpha or punctuation chars in the input so the
//element is either greater than ushort.MaxValue or possibly a fullwidth unicode digit.
if (TryGetValue(elements[i], out values[i]))
{
//For this scenario the old compiler would continue processing the remaining version elements
//so continue processing
continue;
}
}
//Don't process any more of the version elements
break;
}
}
if (hasWildcard)
{
for (int i = lastExplicitValue; i < values.Length; i++)
{
values[i] = ushort.MaxValue;
}
}
version = new Version(values[0], values[1], values[2], values[3]);
return !parseError;
}
private static bool TryGetValue(string s, out ushort value)
{
System.Numerics.BigInteger number;
if (System.Numerics.BigInteger.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out number))
{
//The old compiler would take the 16 least significant bits and use their value as the output
//so we'll do that too.
value = (ushort)(number % 65536);
return true;
}
//One case that will cause us to end up here is when the input is a Fullwidth unicode digit
//so we'll always return zero
value = 0;
return false;
}
/// <summary>
/// If build and/or revision numbers are 65535 they are replaced with time-based values.
/// </summary>
public static Version? GenerateVersionFromPatternAndCurrentTime(DateTime time, Version pattern)
{
if (pattern == null || pattern.Revision != ushort.MaxValue)
{
return pattern;
}
// MSDN doc on the attribute:
// "The default build number increments daily. The default revision number is the number of seconds since midnight local time
// (without taking into account time zone adjustments for daylight saving time), divided by 2."
if (time == default(DateTime))
{
time = DateTime.Now;
}
int revision = (int)time.TimeOfDay.TotalSeconds / 2;
// 24 * 60 * 60 / 2 = 43200 < 65535
Debug.Assert(revision < ushort.MaxValue);
if (pattern.Build == ushort.MaxValue)
{
TimeSpan days = time.Date - new DateTime(2000, 1, 1);
int build = Math.Min(ushort.MaxValue, (int)days.TotalDays);
return new Version(pattern.Major, pattern.Minor, (ushort)build, (ushort)revision);
}
else
{
return new Version(pattern.Major, pattern.Minor, pattern.Build, (ushort)revision);
}
}
}
}
|