344 lines
11 KiB
C++
344 lines
11 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/StringFunc/StringFunc.h>
|
|
|
|
namespace AzFramework
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Specifier
|
|
//////////////////////////////////////////////////////////////////////////
|
|
template <size_t N>
|
|
Specifier<N>::Specifier(const AZ::Uuid& id, const Version<N>& version)
|
|
: m_id(id)
|
|
, m_version(version)
|
|
{}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Dependency::Bound
|
|
//////////////////////////////////////////////////////////////////////////
|
|
template <size_t N>
|
|
AZStd::string Dependency<N>::Bound::ToString() const
|
|
{
|
|
// use version string if it's set
|
|
// Dependency::ParseVersions set this value
|
|
// this is used to save user's version string configuration (eg ==1.8.01.001)
|
|
if (m_parsedString.empty() == false)
|
|
{
|
|
return m_parsedString;
|
|
}
|
|
|
|
// if there is no version string set, we generate it based on the m_version and m_comparison
|
|
if ((m_comparison& Comparison::TwiddleWakka) != Comparison::None)
|
|
{
|
|
AZ_Assert(m_parseDepth >= 2, "Internal Error: There should be "
|
|
"or more than 2 parts to a TwiddleWakka dependency.");
|
|
|
|
AZStd::string version = AZStd::string::format("~>%llu", m_version.m_parts[0]);
|
|
for (AZ::u8 i = 1; i < m_parseDepth; ++i)
|
|
{
|
|
version += AZStd::string::format(".%llu", m_version.m_parts[i]);
|
|
}
|
|
return version;
|
|
}
|
|
|
|
AZStd::string op = "";
|
|
|
|
if ((m_comparison& Comparison::GreaterThan) != Comparison::None)
|
|
{
|
|
op = ">";
|
|
}
|
|
else if ((m_comparison& Comparison::LessThan) != Comparison::None)
|
|
{
|
|
op = "<";
|
|
}
|
|
|
|
if ((m_comparison& Comparison::EqualTo) != Comparison::None)
|
|
{
|
|
op += op.length() == 0 ? "==" : "=";
|
|
}
|
|
|
|
return op + m_version.ToString();
|
|
}
|
|
|
|
template <size_t N>
|
|
bool Dependency<N>::Bound::MatchesVersion(const Version<N>& version) const
|
|
{
|
|
bool satisfies = false;
|
|
|
|
#define CHECK_OPERATOR(comp, OP) \
|
|
if (!satisfies && (m_comparison& Comparison::comp) != Comparison::None) \
|
|
{ \
|
|
satisfies = version OP m_version; \
|
|
}
|
|
|
|
CHECK_OPERATOR(EqualTo, == );
|
|
CHECK_OPERATOR(GreaterThan, > );
|
|
CHECK_OPERATOR(LessThan, < );
|
|
#undef CHECK_OPERATOR
|
|
|
|
return satisfies;
|
|
}
|
|
|
|
template <size_t N>
|
|
void Dependency<N>::Bound::SetVersion(const Version<N>& version)
|
|
{
|
|
m_parsedString = "";
|
|
m_version = version;
|
|
}
|
|
|
|
template <size_t N>
|
|
const Version<N>& Dependency<N>::Bound::GetVersion() const
|
|
{
|
|
return m_version;
|
|
}
|
|
|
|
template <size_t N>
|
|
void Dependency<N>::Bound::SetComparison(Comparison comp)
|
|
{
|
|
m_parsedString = "";
|
|
m_comparison = comp;
|
|
}
|
|
|
|
template <size_t N>
|
|
typename Dependency<N>::Bound::Comparison Dependency<N>::Bound::GetComparison() const
|
|
{
|
|
return m_comparison;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Dependency
|
|
//////////////////////////////////////////////////////////////////////////
|
|
template <size_t N>
|
|
Dependency<N>::Dependency()
|
|
: m_dependencyRegex("(?:(~>|[>=<]{1,2}) *([0-9]+(?:\\.[0-9]+)*))")
|
|
, m_versionRegex("([0-9]+)(?:\\.(.*)){0,1}")
|
|
{
|
|
}
|
|
|
|
template <size_t N>
|
|
Dependency<N>::Dependency(const Dependency& dep)
|
|
: m_id(dep.m_id)
|
|
, m_bounds(dep.m_bounds)
|
|
, m_dependencyRegex(dep.m_dependencyRegex)
|
|
, m_versionRegex(dep.m_versionRegex)
|
|
{
|
|
}
|
|
|
|
template <size_t N>
|
|
const AZ::Uuid& Dependency<N>::GetID() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
template <size_t N>
|
|
void Dependency<N>::SetID(const AZ::Uuid& id)
|
|
{
|
|
m_id = id;
|
|
}
|
|
|
|
template <size_t N>
|
|
const AZStd::vector<typename Dependency<N>::Bound>& Dependency<N>::GetBounds() const
|
|
{
|
|
return m_bounds;
|
|
}
|
|
|
|
template <size_t N>
|
|
bool Dependency<N>::IsFullfilledBy(const Specifier<N>& spec) const
|
|
{
|
|
using Comp = typename Dependency::Bound::Comparison;
|
|
|
|
if (!m_id.IsNull() && !spec.m_id.IsNull())
|
|
{
|
|
if (spec.m_id != m_id)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (auto && bound : m_bounds)
|
|
{
|
|
if (bound.m_comparison == Comp::TwiddleWakka)
|
|
{
|
|
// Lower bound
|
|
Bound lower;
|
|
lower.m_comparison = Comp::EqualTo | Comp::GreaterThan;
|
|
lower.m_version = bound.m_version;
|
|
lower.m_parseDepth = bound.m_parseDepth;
|
|
|
|
// Upper bound
|
|
Bound upper;
|
|
upper.m_comparison = Comp::LessThan;
|
|
upper.m_version = lower.m_version;
|
|
upper.m_parseDepth = bound.m_parseDepth;
|
|
// ~>1.0 becomes >=1.0 <2.0
|
|
// ~>1.2.0 becomes >=1.2.0 <1.3.0
|
|
// ~>1.2.3 becomes >=1.2.3 <1.3.0
|
|
upper.m_version.m_parts[lower.m_parseDepth - 1] = 0;
|
|
upper.m_version.m_parts[lower.m_parseDepth - 2]++;
|
|
|
|
if (!lower.MatchesVersion(spec.m_version)
|
|
|| !upper.MatchesVersion(spec.m_version))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bound.MatchesVersion(spec.m_version))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <size_t N>
|
|
AZ::Outcome<void, AZStd::string> Dependency<N>::ParseVersions(const AZStd::vector<AZStd::string>& deps)
|
|
{
|
|
using Comp = typename Dependency::Bound::Comparison;
|
|
|
|
AZStd::smatch match;
|
|
AZStd::string depStr;
|
|
|
|
for (const auto& depStrRaw : deps)
|
|
{
|
|
depStr = depStrRaw;
|
|
AZ::StringFunc::Strip(depStr, " \t");
|
|
|
|
if (depStr == "*")
|
|
{
|
|
// If * is in the constraints, allow ANY version of the dependency
|
|
m_bounds.clear();
|
|
return AZ::Success();
|
|
}
|
|
else if (AZStd::regex_match(depStr, match, m_dependencyRegex) && match.size() >= 3)
|
|
{
|
|
AZStd::string op = match[1].str();
|
|
AZStd::string versionStr = match[2].str();
|
|
|
|
static const AZStd::array<const char*, 4> invalid_operators{ {
|
|
"><", "<>", ">>", "<<"
|
|
} };
|
|
|
|
for (const char* invalid_op : invalid_operators)
|
|
{
|
|
if (op == invalid_op)
|
|
{
|
|
// invalid operators detected
|
|
goto invalid_version_str;
|
|
}
|
|
}
|
|
|
|
// Check for twiddle wakka, it's a special case
|
|
if (op == "~>")
|
|
{
|
|
// Lower bound
|
|
Bound bound;
|
|
bound.m_comparison = Comp::TwiddleWakka;
|
|
bound.m_parsedString = op + versionStr;
|
|
auto parseOutcome = ParseVersion(versionStr, bound.m_version);
|
|
if (!parseOutcome.IsSuccess() || parseOutcome.GetValue() < 2)
|
|
{
|
|
// ~>1 not allowed, must specifiy ~>1.0
|
|
goto invalid_version_str;
|
|
}
|
|
bound.m_parseDepth = parseOutcome.GetValue();
|
|
m_bounds.push_back(bound);
|
|
}
|
|
else
|
|
{
|
|
Bound current;
|
|
|
|
auto findOperator = [&op, ¤t](Comp comp, const char* str) -> void
|
|
{
|
|
if (op.find(str) != AZStd::string::npos)
|
|
{
|
|
current.m_comparison |= comp;
|
|
}
|
|
};
|
|
|
|
findOperator(Comp::EqualTo, "=");
|
|
findOperator(Comp::LessThan, "<");
|
|
findOperator(Comp::GreaterThan, ">");
|
|
|
|
auto parseOutcome = ParseVersion(versionStr, current.m_version);
|
|
if (!parseOutcome.IsSuccess())
|
|
{
|
|
goto invalid_version_str;
|
|
}
|
|
current.m_parseDepth = parseOutcome.GetValue();
|
|
|
|
// fix incomplete version parts
|
|
// 1.8 -> 1.8.0.0
|
|
for (AZ::u8 praseDepth = current.m_parseDepth; praseDepth < N; ++praseDepth)
|
|
{
|
|
versionStr += ".0";
|
|
}
|
|
|
|
// since "=" is a valid comparsion string, we want to standardize it to ==
|
|
if (current.m_comparison == Comp::EqualTo)
|
|
{
|
|
op = "==";
|
|
}
|
|
|
|
current.m_parsedString = op + versionStr;
|
|
|
|
m_bounds.push_back(current);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto invalid_version_str;
|
|
}
|
|
}
|
|
|
|
return AZ::Success();
|
|
|
|
invalid_version_str:
|
|
// Error message
|
|
m_bounds.clear();
|
|
return AZ::Failure(AZStd::string::format(
|
|
"Failed to parse dependency version string \"%s\": invalid format.",
|
|
depStr.c_str()));
|
|
}
|
|
|
|
template <size_t N>
|
|
AZ::Outcome<AZ::u8> Dependency<N>::ParseVersion(AZStd::string str, Version<N>& ver)
|
|
{
|
|
AZStd::smatch match;
|
|
AZ::u8 depth = 0;
|
|
|
|
while (!str.empty() && depth < N)
|
|
{
|
|
if (AZStd::regex_match(str, match, m_versionRegex))
|
|
{
|
|
ver.m_parts[depth++] = static_cast<typename Version<N>::ComponentType>(AZStd::stoull(match[1].str()));
|
|
str = match[2].str();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!str.empty())
|
|
{
|
|
return AZ::Failure();
|
|
}
|
|
|
|
return AZ::Success(depth);
|
|
}
|
|
}
|