omni/extras/Version.h
File members: omni/extras/Version.h
// Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "StringHelpers.h"
#include "../../carb/RString.h"
#include <string>
namespace omni
{
namespace extras
{
struct SemanticVersion
{
int32_t major{ -1 };
int32_t minor{ -1 };
int32_t patch{ -1 };
std::string prerelease;
std::string build;
SemanticVersion() = default;
std::string toString(bool includeBuildMetadata = true) const
{
if (this->major < 0 && this->minor < 0 && this->patch < 0)
return "";
else
{
std::ostringstream ss;
if (this->major >= 0)
ss << this->major;
if (this->minor >= 0)
ss << "." << this->minor;
if (this->patch >= 0)
ss << "." << this->patch;
if (!std::string(this->prerelease).empty())
ss << "-" << this->prerelease;
if (includeBuildMetadata && !std::string(this->build).empty())
ss << "+" << this->build;
return ss.str();
}
}
void replaceNegativesWithZeros() noexcept
{
if (this->major < 0)
this->major = 0;
if (this->minor < 0)
this->minor = 0;
if (this->patch < 0)
this->patch = 0;
}
bool isAllZeroes() const noexcept
{
return this->major == 0 && this->minor == 0 && this->patch == 0;
}
static SemanticVersion getDefault() noexcept
{
return {};
}
};
inline bool isAnyVersion(const SemanticVersion& v)
{
return v.major < 0 && v.minor < 0 && v.patch < 0;
}
inline int prereleaseCmp(const char* x, const char* y)
{
// SemVer prerelease part comparison according to: https://semver.org/#spec-item-11
const size_t xLen = strlen(x);
const size_t yLen = strlen(y);
// both empty
if (xLen == 0 && yLen == 0)
return 0;
// ("abc" < "") => -1
if (yLen == 0)
return -1;
// ("" < "abc") => 1
if (xLen == 0)
return 1;
const char* xPartFrom = x;
const char* yPartFrom = y;
while (1)
{
// Find next '.'. Chunks are: [xPartFrom, xPartTo] and [yPartFrom, yPartTo]
const char* xPartTo = strchr(xPartFrom, '.');
xPartTo = xPartTo ? xPartTo : x + xLen;
const char* yPartTo = strchr(yPartFrom, '.');
yPartTo = yPartTo ? yPartTo : y + yLen;
const size_t xPartLen = xPartTo - xPartFrom;
const size_t yPartLen = yPartTo - yPartFrom;
// Try to convert to integer until next '.'.
char* end;
const long xNum = strtol(xPartFrom, &end, 10);
const bool xIsNum = (end == xPartTo);
const long yNum = strtol(yPartFrom, &end, 10);
const bool yIsNum = (end == yPartTo);
// "123" < "abc"
if (xIsNum && !yIsNum)
return -1;
if (!xIsNum && yIsNum)
return 1;
if (xIsNum && yIsNum)
{
// numerical comparison
if (xNum != yNum)
return xNum < yNum ? -1 : 1;
}
else
{
// string comparison until next nearest '.'
const int res = strncmp(xPartFrom, yPartFrom, carb_min(xPartLen, yPartLen));
// if different "zzz" < "abc", return:
if (res != 0)
return res < 0 ? -1 : 1;
// they are the same, but one longer? "abc" < "abcdef"
if (xPartLen != yPartLen)
return xPartLen < yPartLen ? -1 : 1;
}
// Go to the next `.` part
xPartFrom = xPartTo + 1;
yPartFrom = yPartTo + 1;
// Reached the end of both? => they are equal
if (xPartFrom == x + xLen + 1 && yPartFrom == y + yLen + 1)
break;
// x ended first? "abc.def" < "abc.def.xyz"
if (xPartFrom == x + xLen + 1)
return -1;
// y ended first?
if (yPartFrom == y + yLen + 1)
return 1;
}
return 0;
}
inline int prereleaseCmp(const std::string& x, const std::string& y)
{
return prereleaseCmp(x.c_str(), y.c_str());
}
template <class VersionT>
inline int versionsCmp(const VersionT& lhs, const VersionT& rhs)
{
if (lhs.major != rhs.major)
return lhs.major < rhs.major ? -1 : 1;
if (lhs.minor != rhs.minor)
return lhs.minor < rhs.minor ? -1 : 1;
if (lhs.patch != rhs.patch)
return lhs.patch < rhs.patch ? -1 : 1;
return prereleaseCmp(lhs.prerelease, rhs.prerelease);
}
inline bool operator<(const SemanticVersion& lhs, const SemanticVersion& rhs)
{
return versionsCmp(lhs, rhs) < 0;
}
inline bool operator==(const SemanticVersion& lhs, const SemanticVersion& rhs)
{
// Notice that "build metadata" is ignored in version comparison
return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch && lhs.prerelease == rhs.prerelease;
}
inline bool stringToVersion(const std::string& str, SemanticVersion& outVersion)
{
outVersion = SemanticVersion::getDefault();
// [major].[minor].[patch]-[prerelease]+[build]
auto versionAndBuild = extras::split(str, '+');
// Only one `+` allowed in semver
if (versionAndBuild.size() > 2)
return false;
auto versionParts = extras::split(versionAndBuild[0], '-', 2);
// parse first part: [major].[minor].[patch]
{
auto parts = extras::split(versionParts[0], '.');
if (parts.empty())
return false;
// 1.2.3.4 is not semver
if (parts.size() > 3)
return false;
if (!extras::stringToInteger(parts[0], outVersion.major))
return false;
if (parts.size() > 1)
{
if (!extras::stringToInteger(parts[1], outVersion.minor))
return false;
}
if (parts.size() > 2)
{
if (!extras::stringToInteger(parts[2], outVersion.patch))
return false;
}
}
// parse second part: [prerelease]+[build]
if (versionParts.size() > 1)
outVersion.prerelease = versionParts[1];
if (versionAndBuild.size() > 1)
outVersion.build = versionAndBuild[1];
return true;
}
inline SemanticVersion stringToVersionOrDefault(const std::string& str)
{
SemanticVersion v;
if (!stringToVersion(str, v))
v = SemanticVersion::getDefault();
return v;
}
inline bool gitHashFromOmniverseVersion(const extras::SemanticVersion& version, std::string& outHash)
{
// Omniverse Flow format: "{version}+{gitbranch}.{number}.{githash}.{build_location}"
auto parts = extras::split(version.build, '.');
outHash = "00000000";
if (parts.size() > 2)
{
outHash = parts[2];
return true;
}
return false;
}
} // namespace extras
} // namespace omni