From f784fb200034629318df122b0651a00de2d7007e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Rold=C3=A1n=20Etcheverry?= Date: Tue, 6 Sep 2022 03:23:55 +0200 Subject: C#: Replace libnethost dependency to find hostfxr We want to replace libnethost as it gives us issues with some compilers. Our implementation tries to mimic libnethost's hostfxr_resolver search logic. We try to use the same function names for easier comparing in case we need to update this in the future. --- modules/mono/editor/hostfxr_resolver.cpp | 335 +++++++++++++++++++++++++++++++ modules/mono/editor/hostfxr_resolver.h | 45 +++++ modules/mono/editor/semver.cpp | 149 ++++++++++++++ modules/mono/editor/semver.h | 106 ++++++++++ 4 files changed, 635 insertions(+) create mode 100644 modules/mono/editor/hostfxr_resolver.cpp create mode 100644 modules/mono/editor/hostfxr_resolver.h create mode 100644 modules/mono/editor/semver.cpp create mode 100644 modules/mono/editor/semver.h (limited to 'modules/mono/editor') diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp new file mode 100644 index 0000000000..bdc8fac8b5 --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -0,0 +1,335 @@ +/*************************************************************************/ +/* hostfxr_resolver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/* +Adapted to Godot from the nethost library: https://github.com/dotnet/runtime/tree/main/src/native/corehost +*/ + +/* +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "hostfxr_resolver.h" + +#include "core/config/engine.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/os/os.h" + +#ifdef WINDOWS_ENABLED +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "../utils/path_utils.h" +#include "semver.h" + +// We don't use libnethost as it gives us issues with some compilers. +// This file tries to mimic libnethost's hostfxr_resolver search logic. We try to use the +// same function names for easier comparing in case we need to update this in the future. + +namespace { + +String get_hostfxr_file_name() { +#if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED) + return "hostfxr.dll"; +#elif defined(OSX_ENABLED) || defined(IOS_ENABLED) + return "libhostfxr.dylib"; +#else + return "libhostfxr.so"; +#endif +} + +bool get_latest_fxr(const String &fxr_root, String &r_fxr_path) { + godotsharp::SemVerParser sem_ver_parser; + + bool found_ver = false; + godotsharp::SemVer latest_ver; + String latest_ver_str; + + Ref da = DirAccess::open(fxr_root); + da->list_dir_begin(); + for (String dir = da->get_next(); !dir.is_empty(); dir = da->get_next()) { + if (!da->current_is_dir() || dir == "." || dir == "..") { + continue; + } + + String ver = dir.get_file(); + + godotsharp::SemVer fx_ver; + if (sem_ver_parser.parse(ver, fx_ver)) { + if (!found_ver || fx_ver > latest_ver) { + latest_ver = fx_ver; + latest_ver_str = ver; + found_ver = true; + } + } + } + + if (!found_ver) { + return false; + } + + String fxr_with_ver = path::join(fxr_root, latest_ver_str); + String hostfxr_file_path = path::join(fxr_with_ver, get_hostfxr_file_name()); + + ERR_FAIL_COND_V_MSG(!FileAccess::exists(hostfxr_file_path), false, "Missing hostfxr library in directory: " + fxr_with_ver); + + r_fxr_path = hostfxr_file_path; + + return true; +} + +#ifdef WINDOWS_ENABLED +typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); + +BOOL is_wow64() { + BOOL wow64 = FALSE; + + LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); + + if (fnIsWow64Process) { + if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) { + wow64 = FALSE; + } + } + + return wow64; +} +#endif + +static const char *arch_name_map[][2] = { + { "arm32", "arm" }, + { "arm64", "arm64" }, + { "rv64", "riscv64" }, + { "x86_64", "x64" }, + { "x86_32", "x86" }, + { nullptr, nullptr } +}; + +String get_dotnet_arch() { + String arch = Engine::get_singleton()->get_architecture_name(); + + int idx = 0; + while (arch_name_map[idx][0] != nullptr) { + if (arch_name_map[idx][0] == arch) { + return arch_name_map[idx][1]; + } + idx++; + } + + return ""; +} + +bool get_default_installation_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String program_files_env; + if (is_wow64()) { + // Running x86 on x64, looking for x86 install + program_files_env = "ProgramFiles(x86)"; + } else { + program_files_env = "ProgramFiles"; + } + + String program_files_dir = OS::get_singleton()->get_environment(program_files_env); + + if (program_files_dir.is_empty()) { + return false; + } + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(program_files_dir, "dotnet", "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet.exe"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + r_dotnet_root = path::join(program_files_dir, "dotnet"); + return true; +#elif defined(TARGET_OSX) + r_dotnet_root = "/usr/local/share/dotnet"; + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(r_dotnet_root, "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + return true; +#else + r_dotnet_root = "/usr/share/dotnet"; + return true; +#endif +} + +bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) { + Error err = OK; + Ref f = FileAccess::open(p_file_path, FileAccess::READ, &err); + + if (f.is_null() || err != OK) { + return false; + } + + String line = f->get_line(); + + if (line.is_empty()) { + return false; + } + + r_dotnet_root = line; + return true; +} + +bool get_dotnet_self_registered_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String sub_key = "SOFTWARE\\dotnet\\Setup\\InstalledVersions\\" + get_dotnet_arch(); + Char16String value = String("InstallLocation").utf16(); + + HKEY hkey = NULL; + LSTATUS result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(sub_key.utf16().get_data()), 0, KEY_READ | KEY_WOW64_32KEY, &hkey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD size = 0; + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, nullptr, &size); + if (result != ERROR_SUCCESS || size == 0) { + RegCloseKey(hkey); + return false; + } + + Vector buffer; + buffer.resize(size / sizeof(WCHAR)); + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size); + if (result != ERROR_SUCCESS) { + RegCloseKey(hkey); + return false; + } + + r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()); + RegCloseKey(hkey); + return true; +#else + String install_location_file = path::join("/etc/dotnet", "install_location_" + get_dotnet_arch().to_lower()); + if (get_install_location_from_file(install_location_file, r_dotnet_root)) { + return true; + } + + if (FileAccess::exists(install_location_file)) { + // Don't try with the legacy location, this will fall back to the hard-coded default install location + return false; + } + + String legacy_install_location_file = path::join("/etc/dotnet", "install_location"); + return get_install_location_from_file(legacy_install_location_file, r_dotnet_root); +#endif +} + +bool get_file_path_from_env(const String &p_env_key, String &r_dotnet_root) { + String env_value = OS::get_singleton()->get_environment(p_env_key); + + if (!env_value.is_empty()) { + env_value = path::realpath(env_value); + + if (DirAccess::exists(env_value)) { + r_dotnet_root = env_value; + return true; + } + } + + return false; +} + +bool get_dotnet_root_from_env(String &r_dotnet_root) { + String dotnet_root_env = "DOTNET_ROOT"; + String arch_for_env = get_dotnet_arch(); + + if (!arch_for_env.is_empty()) { + // DOTNET_ROOT_ + if (get_file_path_from_env(dotnet_root_env + "_" + arch_for_env.to_upper(), r_dotnet_root)) { + return true; + } + } + +#ifdef WINDOWS_ENABLED + // WoW64-only: DOTNET_ROOT(x86) + if (is_wow64() && get_file_path_from_env("DOTNET_ROOT(x86)", r_dotnet_root)) { + return true; + } +#endif + + // DOTNET_ROOT + return get_file_path_from_env(dotnet_root_env, r_dotnet_root); +} + +} //namespace + +bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) { + String fxr_dir = path::join(p_dotnet_root, "host", "fxr"); + ERR_FAIL_COND_V_MSG(!DirAccess::exists(fxr_dir), false, "The host fxr folder does not exist: " + fxr_dir); + return get_latest_fxr(fxr_dir, r_fxr_path); +} + +bool godotsharp::hostfxr_resolver::try_get_path(String &r_dotnet_root, String &r_fxr_path) { + if (!get_dotnet_root_from_env(r_dotnet_root) && + !get_dotnet_self_registered_dir(r_dotnet_root) && + !get_default_installation_dir(r_dotnet_root)) { + return false; + } + + return try_get_path_from_dotnet_root(r_dotnet_root, r_fxr_path); +} diff --git a/modules/mono/editor/hostfxr_resolver.h b/modules/mono/editor/hostfxr_resolver.h new file mode 100644 index 0000000000..0f029ab7ae --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* hostfxr_resolver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef HOSTFXR_RESOLVER_H +#define HOSTFXR_RESOLVER_H + +#include "core/string/ustring.h" + +namespace godotsharp { +namespace hostfxr_resolver { + +bool try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_out_fxr_path); +bool try_get_path(String &r_out_dotnet_root, String &r_out_fxr_path); + +} //namespace hostfxr_resolver +} //namespace godotsharp + +#endif // HOSTFXR_RESOLVER_H diff --git a/modules/mono/editor/semver.cpp b/modules/mono/editor/semver.cpp new file mode 100644 index 0000000000..1656d0932f --- /dev/null +++ b/modules/mono/editor/semver.cpp @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* semver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "semver.h" + +bool godotsharp::SemVer::parse_digit_only_field(const String &p_field, uint64_t &r_result) { + if (p_field.is_empty()) { + return false; + } + + int64_t integer = 0; + + for (int i = 0; i < p_field.length(); i++) { + char32_t c = p_field[i]; + if (is_digit(c)) { + bool overflow = ((uint64_t)integer > UINT64_MAX / 10) || ((uint64_t)integer == UINT64_MAX / 10 && c > '5'); + ERR_FAIL_COND_V_MSG(overflow, false, "Cannot represent '" + p_field + "' as a 64-bit unsigned integer, since the value is too large."); + integer *= 10; + integer += c - '0'; + } else { + return false; + } + } + + r_result = (uint64_t)integer; + return true; +} + +int godotsharp::SemVer::cmp(const godotsharp::SemVer &p_a, const godotsharp::SemVer &p_b) { + if (p_a.major != p_b.major) { + return p_a.major > p_b.major ? 1 : -1; + } + + if (p_a.minor != p_b.minor) { + return p_a.minor > p_b.minor ? 1 : -1; + } + + if (p_a.patch != p_b.patch) { + return p_a.patch > p_b.patch ? 1 : -1; + } + + if (p_a.prerelease.is_empty() && p_b.prerelease.is_empty()) { + return 0; + } + + if (p_a.prerelease.is_empty() || p_b.prerelease.is_empty()) { + return p_a.prerelease.is_empty() ? 1 : -1; + } + + if (p_a.prerelease != p_b.prerelease) { + // This could be optimized, but I'm too lazy + + Vector a_field_set = p_a.prerelease.split("."); + Vector b_field_set = p_b.prerelease.split("."); + + int a_field_count = a_field_set.size(); + int b_field_count = b_field_set.size(); + + int min_field_count = MIN(a_field_count, b_field_count); + + for (int i = 0; i < min_field_count; i++) { + const String &a_field = a_field_set[i]; + const String &b_field = b_field_set[i]; + + if (a_field == b_field) { + continue; + } + + uint64_t a_num; + bool a_is_digit_only = parse_digit_only_field(a_field, a_num); + + uint64_t b_num; + bool b_is_digit_only = parse_digit_only_field(b_field, b_num); + + if (a_is_digit_only && b_is_digit_only) { + // Identifiers consisting of only digits are compared numerically. + + if (a_num == b_num) { + continue; + } + + return a_num > b_num ? 1 : -1; + } + + if (a_is_digit_only || b_is_digit_only) { + // Numeric identifiers always have lower precedence than non-numeric identifiers. + return b_is_digit_only ? 1 : -1; + } + + // Identifiers with letters or hyphens are compared lexically in ASCII sort order. + return a_field > b_field ? 1 : -1; + } + + if (a_field_count != b_field_count) { + // A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. + return a_field_count > b_field_count ? 1 : -1; + } + } + + return 0; +} + +bool godotsharp::SemVerParser::parse(const String &p_ver_text, godotsharp::SemVer &r_semver) { + if (!regex.is_valid() && regex.get_pattern().is_empty()) { + regex.compile("^(?P0|[1-9]\\d*)\\.(?P0|[1-9]\\d*)\\.(?P0|[1-9]\\d*)(?:-(?P(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?P[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + ERR_FAIL_COND_V(!regex.is_valid(), false); + } + + Ref match = regex.search(p_ver_text); + + if (match.is_valid()) { + r_semver = SemVer( + match->get_string("major").to_int(), + match->get_string("minor").to_int(), + match->get_string("patch").to_int(), + match->get_string("prerelease"), + match->get_string("buildmetadata")); + return true; + } + + return false; +} diff --git a/modules/mono/editor/semver.h b/modules/mono/editor/semver.h new file mode 100644 index 0000000000..48ea8b043e --- /dev/null +++ b/modules/mono/editor/semver.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* semver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SEMVER_H +#define SEMVER_H + +#include "core/string/ustring.h" +#include "modules/regex/regex.h" + +// is included somewhere, which defines major(dev) to gnu_dev_major(dev) +#if defined(major) +#undef major +#endif +#if defined(minor) +#undef minor +#endif + +namespace godotsharp { + +struct SemVer { +private: + static bool parse_digit_only_field(const String &p_field, uint64_t &r_result); + + static int cmp(const SemVer &p_a, const SemVer &p_b); + +public: + int major = 0; + int minor = 0; + int patch = 0; + String prerelease; + String build_metadata; + + bool operator==(const SemVer &b) const { + return cmp(*this, b) == 0; + } + + bool operator!=(const SemVer &b) const { + return !operator==(b); + } + + bool operator<(const SemVer &b) const { + return cmp(*this, b) < 0; + } + + bool operator>(const SemVer &b) const { + return cmp(*this, b) > 0; + } + + bool operator<=(const SemVer &b) const { + return cmp(*this, b) <= 0; + } + + bool operator>=(const SemVer &b) const { + return cmp(*this, b) >= 0; + } + + SemVer() {} + + SemVer(int p_major, int p_minor, int p_patch, + const String &p_prerelease, const String &p_build_metadata) : + major(p_major), + minor(p_minor), + patch(p_patch), + prerelease(p_prerelease), + build_metadata(p_build_metadata) { + } +}; + +struct SemVerParser { +private: + RegEx regex; + +public: + bool parse(const String &p_ver_text, SemVer &r_semver); +}; + +} //namespace godotsharp + +#endif // SEMVER_H -- cgit v1.2.3