Merge pull request #595 from kotisoff/main

Проверка версий зависимостей
This commit is contained in:
MihailRis 2025-09-14 12:42:28 +03:00 committed by GitHub
commit 94989ce292
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 284 additions and 5 deletions

View File

@ -30,6 +30,11 @@ If prefix is not specified, '!' level will be used.
Example: '~randutil' - weak dependency 'randutil'.
Dependency version is indicated after '@' symbol and have operators to restrict acceptable versions.
If version is not specified, '\*' (any) version will be used.
Example: 'randutil@>=1.0' - dependency 'randutil' which requires version 1.0 or newer.
Example:
```json
{

View File

@ -32,6 +32,11 @@
Пример: '~randutil' - слабая зависимость 'randutil'.
Версии зависимостей указываются после '@' и имеют операторы для ограничения допустимых версий.
Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия.
Пример: 'randutil@>=1.0' - зависимость 'randutil' версии 1.0 и старше.
Пример:
```json
{

View File

@ -176,18 +176,105 @@ function place_pack(panel, packinfo, callback, position_func)
end
end
local Version = {};
function Version.matches_pattern(version)
for _, letter in string.gmatch(version, "%.+") do
if type(letter) ~= "number" or letter ~= "." then
return false;
end
local t = string.split(version, ".");
return #t == 2 or #t == 3;
end
end
function Version.__equal(ver1, ver2)
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3];
end
function Version.__more(ver1, ver2)
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end;
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end;
return ver1[3] > ver2[3];
end
function Version.__less(ver1, ver2)
return Version.__more(ver2, ver1);
end
function Version.__more_or_equal(ver1, ver2)
return not Version.__less(ver1, ver2);
end
function Version.__less_or_equal(ver1, ver2)
return not Version.__more(ver1, ver2);
end
function Version.compare(op, ver1, ver2)
ver1 = string.split(ver1, ".");
ver2 = string.split(ver2, ".");
if op == "=" then return Version.__equal(ver1, ver2);
elseif op == ">" then return Version.__more(ver1, ver2);
elseif op == "<" then return Version.__less(ver1, ver2);
elseif op == ">=" then return Version.__more_or_equal(ver1, ver2);
elseif op == "<=" then return Version.__less_or_equal(ver1, ver2);
else return false; end
end
function Version.parse(version)
local op = string.sub(version, 1, 2);
if op == ">=" or op == "=>" then
return ">=", string.sub(version, #op + 1);
elseif op == "<=" or op == "=<" then
return "<=", string.sub(version, #op + 1);
end
op = string.sub(version, 1, 1);
if op == ">" or op == "<" then
return op, string.sub(version, #op + 1);
end
return "=", version;
end
local function compare_version(dependent_version, actual_version)
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
local op, dep_ver = Version.parse_version(dependent_version);
Version.compare(op, dep_ver, actual_version);
elseif dependent_version == "*" or dependent_version == actual_version then
return true;
else
return false;
end
end
function check_dependencies(packinfo)
if packinfo.dependencies == nil then
return
end
for i,dep in ipairs(packinfo.dependencies) do
local depid = dep:sub(2,-1)
if dep:sub(1,1) == '!' then
local depid, depver = unpack(string.split(dep:sub(2,-1), "@"))
if dep:sub(1,1) == '!' then
if not table.has(packs_all, depid) then
return string.format(
"%s (%s)", gui.str("error.dependency-not-found"), depid
)
end
local dep_pack = pack.get_info(depid);
if not compare_version(depver, dep_pack.version) then
local op, ver = Version.parse(depver);
print(string.format("%s: %s !%s %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, op, ver, depid));
return string.format("%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid);
end
if table.has(packs_installed, packinfo.id) then
table.insert(required, depid)
end

View File

@ -7,6 +7,7 @@ world.convert-block-layouts=Blocks fields have changes! Convert world files?
pack.remove-confirm=Do you want to erase all pack(s) content from the world forever?
error.pack-not-found=Could not to find pack
error.dependency-not-found=Dependency pack is not found
error.dependency-version-not-met=Dependency pack version is not met.
world.delete-confirm=Do you want to delete world forever?
world.generators.default=Default
world.generators.flat=Flat

View File

@ -32,6 +32,7 @@ devtools.output=Вывод
error.pack-not-found=Не удалось найти пакет
error.dependency-not-found=Используемая зависимость не найдена
error.dependency-version-not-met=Версия зависимости не соответствует необходимой
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?
# Подсказки

View File

@ -132,7 +132,44 @@ ContentPack ContentPack::read(const io::path& folder) {
level = DependencyLevel::weak;
break;
}
pack.dependencies.push_back({level, depName});
std::string depVer = "*";
std::string depVerOperator = "=";
size_t versionPos = depName.rfind("@");
if (versionPos != std::string::npos) {
depVer = depName.substr(versionPos + 1);
depName = depName.substr(0, versionPos);
if (depVer.size() >= 2) {
std::string op = depVer.substr(0, 2);
std::uint8_t op_size = 0;
// Two symbol operators
if (op == ">=" || op == "=>" || op == "<=" || op == "=<") {
op_size = 2;
depVerOperator = op;
}
// One symbol operators
else {
op = depVer.substr(0, 1);
if (op == ">" || op == "<") {
op_size = 1;
depVerOperator = op;
}
}
depVer = depVer.substr(op_size);
} else {
if (depVer == ">" || depVer == "<"){
depVer = "*";
}
}
}
pack.dependencies.push_back({level, depName, depVer, depVerOperator});
}
}

View File

@ -20,11 +20,16 @@ public:
io::path folder,
const std::string& message
);
std::string getPackId() const;
io::path getFolder() const;
};
enum class DependencyVersionOperator {
equal, more, less,
more_or_equal, less_or_equal
};
enum class DependencyLevel {
required, // dependency must be installed
optional, // dependency will be installed if found
@ -35,6 +40,8 @@ enum class DependencyLevel {
struct DependencyPack {
DependencyLevel level;
std::string id;
std::string version;
std::string op;
};
struct ContentPackStats {

View File

@ -0,0 +1,66 @@
#include "ContentPackVersion.hpp"
#include <algorithm>
#include <iostream>
#include <sstream>
#include "coders/commons.hpp"
Version::Version(const std::string& version) {
major = 0;
minor = 0;
patch = 0;
std::vector<int> parts;
std::stringstream ss(version);
std::string part;
while (std::getline(ss, part, '.')) {
if (!part.empty()) {
parts.push_back(std::stoi(part));
}
}
if (parts.size() > 0) major = parts[0];
if (parts.size() > 1) minor = parts[1];
if (parts.size() > 2) patch = parts[2];
}
DependencyVersionOperator Version::string_to_operator(const std::string& op) {
if (op == "=")
return DependencyVersionOperator::equal;
else if (op == ">")
return DependencyVersionOperator::more;
else if (op == "<")
return DependencyVersionOperator::less;
else if (op == ">=" || op == "=>")
return DependencyVersionOperator::more_or_equal;
else if (op == "<=" || op == "=<")
return DependencyVersionOperator::less_or_equal;
else return DependencyVersionOperator::equal;
}
bool isNumber(const std::string& s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit);
}
bool Version::matches_pattern(const std::string& version) {
for (char c : version) {
if (!isdigit(c) && c != '.') {
return false;
}
}
std::stringstream ss(version);
std::vector<std::string> parts;
std::string part;
while (std::getline(ss, part, '.')) {
if (part.empty()) return false;
if (!isNumber(part)) return false;
parts.push_back(part);
}
return parts.size() == 2 || parts.size() == 3;
}

View File

@ -0,0 +1,51 @@
#include <string>
#include "content/ContentPack.hpp"
class Version {
public:
int major;
int minor;
int patch;
Version(const std::string& version);
bool operator==(const Version& other) const {
return major == other.major && minor == other.minor && patch == other.patch;
}
bool operator<(const Version& other) const {
if (major != other.major) return major < other.major;
if (minor != other.minor) return minor < other.minor;
return patch < other.patch;
}
bool operator>(const Version& other) const {
return other < *this;
}
bool operator>=(const Version& other) const {
return !(*this < other);
}
bool operator<=(const Version& other) const {
return !(*this > other);
}
bool process_operator(const std::string& op, const Version& other) const {
auto dep_op = Version::string_to_operator(op);
switch(dep_op) {
case DependencyVersionOperator::equal: return *this == other;
case DependencyVersionOperator::more: return *this > other;
case DependencyVersionOperator::less: return *this < other;
case DependencyVersionOperator::less_or_equal: return *this <= other;
case DependencyVersionOperator::more_or_equal: return *this >= other;
default: return false;
}
}
static DependencyVersionOperator string_to_operator(const std::string& op);
static bool matches_pattern(const std::string& version);
};

View File

@ -3,6 +3,7 @@
#include <queue>
#include <sstream>
#include "ContentPackVersion.hpp"
#include "util/listutil.hpp"
PacksManager::PacksManager() = default;
@ -106,6 +107,23 @@ static bool resolve_dependencies(
continue;
}
auto dep_pack = found -> second;
if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version)
&& Version(dep_pack.version)
.process_operator(dep.op, Version(dep.version))
) {
// dependency pack version meets the required one
continue;
} else if (dep.version == "*" || dep.version == dep_pack.version){
// fallback: dependency pack version also meets required one
continue;
} else {
throw contentpack_error(
dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'"
);
}
if (!util::contains(allNames, dep.id) &&
dep.level != DependencyLevel::weak) {
allNames.push_back(dep.id);

View File

@ -114,7 +114,8 @@ static int l_pack_get_info(
default:
throw std::runtime_error("");
}
lua::pushfstring(L, "%s%s", prefix.c_str(), dpack.id.c_str());
lua::pushfstring(L, "%s%s@%s%s", prefix.c_str(), dpack.id.c_str(), dpack.op.c_str(), dpack.version.c_str());
lua::rawseti(L, i + 1);
}
lua::setfield(L, "dependencies");