React to sem version 2.0 (#8725)
This commit is contained in:
parent
23ad42e905
commit
7a1a53d76d
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue