React to sem version 2.0 (#8725)

This commit is contained in:
Justin Kotalik 2019-03-24 14:17:06 -07:00 committed by GitHub
parent 23ad42e905
commit 7a1a53d76d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 291 additions and 54 deletions

View File

@ -1,11 +1,31 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// 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.
#include <cassert>
#include <string>
#include "fx_ver.h"
#include <Windows.h>
#include <sstream>
#include <cassert>
static bool validIdentifiers(const std::wstring& ids);
size_t index_of_non_numeric(const std::wstring& str, size_t i)
{
return str.find_first_not_of(TEXT("0123456789"), i);
}
bool try_stou(const std::wstring& str, unsigned* num)
{
if (str.empty())
{
return false;
}
if (index_of_non_numeric(str, 0) != std::wstring::npos)
{
return false;
}
*num = (unsigned)std::stoul(str);
return true;
}
fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build)
: m_major(major)
@ -14,9 +34,15 @@ fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre, con
, m_pre(pre)
, m_build(build)
{
// verify preconditions
assert(is_empty() || m_major >= 0);
assert(is_empty() || m_minor >= 0);
assert(is_empty() || m_patch >= 0);
assert(m_pre[0] == 0 || validIdentifiers(m_pre));
assert(m_build[0] == 0 || validIdentifiers(m_build));
}
fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre)
fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring & pre)
: fx_ver_t(major, minor, patch, pre, TEXT(""))
{
}
@ -26,32 +52,37 @@ fx_ver_t::fx_ver_t(int major, int minor, int patch)
{
}
bool fx_ver_t::operator ==(const fx_ver_t& b) const
fx_ver_t::fx_ver_t()
: fx_ver_t(-1, -1, -1, TEXT(""), TEXT(""))
{
}
bool fx_ver_t::operator ==(const fx_ver_t & b) const
{
return compare(*this, b) == 0;
}
bool fx_ver_t::operator !=(const fx_ver_t& b) const
bool fx_ver_t::operator !=(const fx_ver_t & b) const
{
return !operator ==(b);
}
bool fx_ver_t::operator <(const fx_ver_t& b) const
bool fx_ver_t::operator <(const fx_ver_t & b) const
{
return compare(*this, b) < 0;
}
bool fx_ver_t::operator >(const fx_ver_t& b) const
bool fx_ver_t::operator >(const fx_ver_t & b) const
{
return compare(*this, b) > 0;
}
bool fx_ver_t::operator <=(const fx_ver_t& b) const
bool fx_ver_t::operator <=(const fx_ver_t & b) const
{
return compare(*this, b) <= 0;
}
bool fx_ver_t::operator >=(const fx_ver_t& b) const
bool fx_ver_t::operator >=(const fx_ver_t & b) const
{
return compare(*this, b) >= 0;
}
@ -66,13 +97,34 @@ std::wstring fx_ver_t::as_str() const
}
if (!m_build.empty())
{
stream << TEXT("+") << m_build;
stream << m_build;
}
return stream.str();
}
std::wstring fx_ver_t::prerelease_glob() const
{
std::wstringstream stream;
stream << m_major << TEXT(".") << m_minor << TEXT(".") << m_patch << TEXT("-*");
return stream.str();
}
std::wstring fx_ver_t::patch_glob() const
{
std::wstringstream stream;
stream << m_major << TEXT(".") << m_minor << TEXT(".*");
return stream.str();
}
static std::wstring getId(const std::wstring & ids, size_t idStart)
{
size_t next = ids.find(TEXT('.'), idStart);
return next == std::wstring::npos ? ids.substr(idStart) : ids.substr(idStart, next - idStart);
}
/* static */
int fx_ver_t::compare(const fx_ver_t&a, const fx_ver_t& b)
int fx_ver_t::compare(const fx_ver_t & a, const fx_ver_t & b)
{
// compare(u.v.w-p+b, x.y.z-q+c)
if (a.m_major != b.m_major)
@ -90,37 +142,166 @@ int fx_ver_t::compare(const fx_ver_t&a, const fx_ver_t& b)
return (a.m_patch > b.m_patch) ? 1 : -1;
}
if (a.m_pre.empty() != b.m_pre.empty())
if (a.m_pre.empty() || b.m_pre.empty())
{
// Either a is empty or b is empty
return a.m_pre.empty() ? 1 : -1;
// Either a is empty or b is empty or both are empty
return a.m_pre.empty() ? !b.m_pre.empty() : -1;
}
// Either both are empty or both are non-empty (may be equal)
int pre_cmp = a.m_pre.compare(b.m_pre);
if (pre_cmp != 0)
// Both are non-empty (may be equal)
// First character of pre is '-' when it is not empty
assert(a.m_pre[0] == TEXT('-'));
assert(b.m_pre[0] == TEXT('-'));
// First idenitifier starts at position 1
size_t idStart = 1;
for (size_t i = idStart; true; ++i)
{
return pre_cmp;
if (a.m_pre[i] != b.m_pre[i])
{
// Found first character with a difference
if (a.m_pre[i] == 0 && b.m_pre[i] == TEXT('.'))
{
// identifiers both complete, b has an additional idenitifier
return -1;
}
if (b.m_pre[i] == 0 && a.m_pre[i] == TEXT('.'))
{
// identifiers both complete, a has an additional idenitifier
return 1;
}
// identifiers must not be empty
std::wstring ida = getId(a.m_pre, idStart);
std::wstring idb = getId(b.m_pre, idStart);
unsigned idanum = 0;
bool idaIsNum = try_stou(ida, &idanum);
unsigned idbnum = 0;
bool idbIsNum = try_stou(idb, &idbnum);
if (idaIsNum && idbIsNum)
{
// Numeric comparison
return (idanum > idbnum) ? 1 : -1;
}
else if (idaIsNum || idbIsNum)
{
// Mixed compare. Spec: Number < Text
return idbIsNum ? 1 : -1;
}
// Ascii compare
return ida.compare(idb);
}
else
{
// a.m_pre[i] == b.m_pre[i]
if (a.m_pre[i] == 0)
{
break;
}
if (a.m_pre[i] == TEXT('.'))
{
idStart = i + 1;
}
}
}
return a.m_build.compare(b.m_build);
return 0;
}
bool try_stou(const std::wstring& str, unsigned* num)
static bool validIdentifierCharSet(const std::wstring & id)
{
if (str.empty())
// ids must be of the set [0-9a-zA-Z-]
// ASCII and Unicode ordering
static_assert(TEXT('-') < TEXT('0'), "Code assumes ordering - < 0 < 9 < A < Z < a < z");
static_assert(TEXT('0') < TEXT('9'), "Code assumes ordering - < 0 < 9 < A < Z < a < z");
static_assert(TEXT('9') < TEXT('A'), "Code assumes ordering - < 0 < 9 < A < Z < a < z");
static_assert(TEXT('A') < TEXT('Z'), "Code assumes ordering - < 0 < 9 < A < Z < a < z");
static_assert(TEXT('Z') < TEXT('a'), "Code assumes ordering - < 0 < 9 < A < Z < a < z");
static_assert(TEXT('a') < TEXT('z'), "Code assumes ordering - < 0 < 9 < A < Z < a < z");
for (size_t i = 0; id[i] != 0; ++i)
{
return false;
if (id[i] >= TEXT('A'))
{
if ((id[i] > TEXT('Z') && id[i] < TEXT('a')) || id[i] > TEXT('z'))
{
return false;
}
}
else
{
if ((id[i] < TEXT('0') && id[i] != TEXT('-')) || id[i] > TEXT('9'))
{
return false;
}
}
}
if (str.find_first_not_of(TEXT("0123456789")) != std::wstring::npos)
{
return false;
}
*num = (unsigned)std::stoul(str);
return true;
}
bool parse_internal(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production)
static bool validIdentifier(const std::wstring & id, bool buildMeta)
{
if (id.empty())
{
// Identifier must not be empty
return false;
}
if (!validIdentifierCharSet(id))
{
// ids must be of the set [0-9a-zA-Z-]
return false;
}
if (!buildMeta && id[0] == TEXT('0') && id[1] != 0 && index_of_non_numeric(id, 1) == std::wstring::npos)
{
// numeric identifiers must not be padded with 0s
return false;
}
return true;
}
static bool validIdentifiers(const std::wstring & ids)
{
if (ids.empty())
{
return true;
}
bool prerelease = ids[0] == TEXT('-');
bool buildMeta = ids[0] == TEXT('+');
if (!(prerelease || buildMeta))
{
// ids must start with '-' or '+' for prerelease & build respectively
return false;
}
size_t idStart = 1;
size_t nextId;
while ((nextId = ids.find(TEXT('.'), idStart)) != std::wstring::npos)
{
if (!validIdentifier(ids.substr(idStart, nextId - idStart), buildMeta))
{
return false;
}
idStart = nextId + 1;
}
if (!validIdentifier(ids.substr(idStart), buildMeta))
{
return false;
}
return true;
}
bool parse_internal(const std::wstring & ver, fx_ver_t * fx_ver, bool parse_only_production)
{
size_t maj_start = 0;
size_t maj_sep = ver.find(TEXT('.'));
@ -133,6 +314,12 @@ bool parse_internal(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_p
{
return false;
}
if (maj_sep > 1 && ver[maj_start] == TEXT('0'))
{
// if leading character is 0, and strlen > 1
// then the numeric substring has leading zeroes which is prohibited by the specification.
return false;
}
size_t min_start = maj_sep + 1;
size_t min_sep = ver.find(TEXT('.'), min_start);
@ -146,16 +333,28 @@ bool parse_internal(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_p
{
return false;
}
if (min_sep - min_start > 1 && ver[min_start] == TEXT('0'))
{
// if leading character is 0, and strlen > 1
// then the numeric substring has leading zeroes which is prohibited by the specification.
return false;
}
unsigned patch = 0;
size_t pat_start = min_sep + 1;
size_t pat_sep = ver.find_first_not_of(TEXT("0123456789"), pat_start);
size_t pat_sep = index_of_non_numeric(ver, pat_start);
if (pat_sep == std::wstring::npos)
{
if (!try_stou(ver.substr(pat_start), &patch))
{
return false;
}
if (ver[pat_start + 1] != 0 && ver[pat_start] == TEXT('0'))
{
// if leading character is 0, and strlen != 1
// then the numeric substring has leading zeroes which is prohibited by the specification.
return false;
}
*fx_ver = fx_ver_t(major, minor, patch);
return true;
@ -171,24 +370,39 @@ bool parse_internal(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_p
{
return false;
}
if (pat_sep - pat_start > 1 && ver[pat_start] == TEXT('0'))
{
return false;
}
size_t pre_start = pat_sep;
size_t pre_sep = ver.find(TEXT('+'), pre_start);
if (pre_sep == std::wstring::npos)
size_t pre_sep = ver.find(TEXT('+'), pat_sep);
std::wstring pre = (pre_sep == std::wstring::npos) ? ver.substr(pre_start) : ver.substr(pre_start, pre_sep - pre_start);
if (!validIdentifiers(pre))
{
*fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start));
return true;
return false;
}
else
std::wstring build;
if (pre_sep != std::wstring::npos)
{
size_t build_start = pre_sep + 1;
*fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start, pre_sep - pre_start), ver.substr(build_start));
return true;
build = ver.substr(pre_sep);
if (!validIdentifiers(build))
{
return false;
}
}
*fx_ver = fx_ver_t(major, minor, patch, pre, build);
return true;
}
/* static */
bool fx_ver_t::parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production)
bool fx_ver_t::parse(const std::wstring & ver, fx_ver_t * fx_ver, bool parse_only_production)
{
bool valid = parse_internal(ver, fx_ver, parse_only_production);
assert(!valid || fx_ver->as_str() == ver);

View File

@ -1,29 +1,38 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// 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.
#pragma once
#ifndef __FX_VER_H__
#define __FX_VER_H__
#include <string>
// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not
// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11
// Note: This is intended to implement SemVer 2.0
struct fx_ver_t
{
fx_ver_t();
fx_ver_t(int major, int minor, int patch);
// if not empty pre contains valid prerelease label with leading '-'
fx_ver_t(int major, int minor, int patch, const std::wstring& pre);
// if not empty pre contains valid prerelease label with leading '-'
// if not empty build contains valid build label with leading '+'
fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build);
int get_major() const noexcept { return m_major; }
int get_minor() const noexcept { return m_minor; }
int get_patch() const noexcept { return m_patch; }
int get_major() const { return m_major; }
int get_minor() const { return m_minor; }
int get_patch() const { return m_patch; }
void set_major(int m) noexcept { m_major = m; }
void set_minor(int m) noexcept { m_minor = m; }
void set_patch(int p) noexcept { m_patch = p; }
void set_major(int m) { m_major = m; }
void set_minor(int m) { m_minor = m; }
void set_patch(int p) { m_patch = p; }
bool is_prerelease() const noexcept { return !m_pre.empty(); }
bool is_prerelease() const { return !m_pre.empty(); }
bool is_empty() const { return m_major == -1; }
std::wstring as_str() const;
std::wstring prerelease_glob() const;
std::wstring patch_glob() const;
bool operator ==(const fx_ver_t& b) const;
bool operator !=(const fx_ver_t& b) const;
@ -41,5 +50,7 @@ private:
std::wstring m_pre;
std::wstring m_build;
static int compare(const fx_ver_t&a, const fx_ver_t& b);
static int compare(const fx_ver_t& a, const fx_ver_t& b);
};
#endif // __FX_VER_H__

View File

@ -83,6 +83,18 @@ namespace GlobalVersionTests
EXPECT_STREQ(res.c_str(), L"2.1.0");
}
// Sem version 2.0 will not be used with ANCM out of process handler, but it's the most convenient way to test it.
TEST(FindHighestGlobalVersion, HighestVersionWithSemVersion20)
{
auto tempPath = TempDirectory();
EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0-preview"));
EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0-preview.1.1"));
auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.path().c_str());
EXPECT_STREQ(res.c_str(), L"2.1.0-preview.1.1");
}
TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersionsPreview)
{
auto tempPath = TempDirectory();