From 23ffc4d8b91e9b2acd249c6fde323ef4d577780e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 7 Jul 2024 02:10:22 +0300 Subject: [PATCH 001/106] Refactor ResourcePack Signed-off-by: Trial97 --- launcher/minecraft/mod/DataPack.h | 4 +- launcher/minecraft/mod/ResourcePack.cpp | 50 +---- launcher/minecraft/mod/ResourcePack.h | 37 +--- .../minecraft/mod/ResourcePackFolderModel.cpp | 4 +- .../mod/tasks/LocalDataPackParseTask.cpp | 103 ++++++--- .../mod/tasks/LocalDataPackParseTask.h | 17 +- .../mod/tasks/LocalResourcePackParseTask.cpp | 206 +----------------- .../mod/tasks/LocalResourcePackParseTask.h | 34 +-- .../mod/tasks/LocalWorldSaveParseTask.cpp | 4 +- tests/DataPackParse_test.cpp | 6 +- tests/ResourcePackParse_test.cpp | 7 +- 11 files changed, 114 insertions(+), 358 deletions(-) diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index 4855b0203..266c2061b 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -35,8 +35,6 @@ class Version; class DataPack : public Resource { Q_OBJECT public: - using Ptr = shared_qobject_ptr; - DataPack(QObject* parent = nullptr) : Resource(parent) {} DataPack(QFileInfo file_info) : Resource(file_info) {} @@ -59,6 +57,8 @@ class DataPack : public Resource { [[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; + virtual QString directory() { return "/data"; } + protected: mutable QMutex m_data_lock; diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 81fb91485..4ed3c67e3 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -42,13 +42,6 @@ void ResourcePack::setPackFormat(int new_format_id) m_pack_format = new_format_id; } -void ResourcePack::setDescription(QString new_description) -{ - QMutexLocker locker(&m_data_lock); - - m_description = new_description; -} - void ResourcePack::setImage(QImage new_image) const { QMutexLocker locker(&m_data_lock); @@ -90,7 +83,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const } // Imaged got evicted from the cache. Re-process it and retry. - ResourcePackUtils::processPackPNG(*this); + ResourcePackUtils::processPackPNG(this); return image(size); } @@ -102,44 +95,3 @@ std::pair ResourcePack::compatibleVersions() const return s_pack_format_versions.constFind(m_pack_format).value(); } - -int ResourcePack::compare(const Resource& other, SortType type) const -{ - auto const& cast_other = static_cast(other); - switch (type) { - default: - return Resource::compare(other, type); - case SortType::PACK_FORMAT: { - auto this_ver = packFormat(); - auto other_ver = cast_other.packFormat(); - - if (this_ver > other_ver) - return 1; - if (this_ver < other_ver) - return -1; - break; - } - } - return 0; -} - -bool ResourcePack::applyFilter(QRegularExpression filter) const -{ - if (filter.match(description()).hasMatch()) - return true; - - if (filter.match(QString::number(packFormat())).hasMatch()) - return true; - - if (filter.match(compatibleVersions().first.toString()).hasMatch()) - return true; - if (filter.match(compatibleVersions().second.toString()).hasMatch()) - return true; - - return Resource::applyFilter(filter); -} - -bool ResourcePack::valid() const -{ - return m_pack_format != 0; -} diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 2254fc5c4..bb5aeecb5 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -1,6 +1,7 @@ #pragma once #include "Resource.h" +#include "minecraft/mod/DataPack.h" #include #include @@ -14,51 +15,27 @@ class Version; * Store localized descriptions * */ -class ResourcePack : public Resource { +class ResourcePack : public DataPack { Q_OBJECT public: - using Ptr = shared_qobject_ptr; + ResourcePack(QObject* parent = nullptr) : DataPack(parent) {} + ResourcePack(QFileInfo file_info) : DataPack(file_info) {} - ResourcePack(QObject* parent = nullptr) : Resource(parent) {} - ResourcePack(QFileInfo file_info) : Resource(file_info) {} - - /** Gets the numerical ID of the pack format. */ - [[nodiscard]] int packFormat() const { return m_pack_format; } /** Gets, respectively, the lower and upper versions supported by the set pack format. */ [[nodiscard]] std::pair compatibleVersions() const; - /** Gets the description of the resource pack. */ - [[nodiscard]] QString description() const { return m_description; } - /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; - /** Thread-safe. */ - void setPackFormat(int new_format_id); - - /** Thread-safe. */ - void setDescription(QString new_description); - /** Thread-safe. */ void setImage(QImage new_image) const; - bool valid() const override; + /** Thread-safe. */ + void setPackFormat(int new_format_id); - [[nodiscard]] int compare(Resource const& other, SortType type) const override; - [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; + virtual QString directory() { return "/assets"; } protected: - mutable QMutex m_data_lock; - - /* The 'version' of a resource pack, as defined in the pack.mcmeta file. - * See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta - */ - int m_pack_format = 0; - - /** The resource pack's description, as defined in the pack.mcmeta file. - */ - QString m_description; - /** The resource pack's image file cache key, for access in the QPixmapCache global instance. * * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true), diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index e106a2be9..94774c81f 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -44,7 +44,7 @@ #include "Application.h" #include "Version.h" -#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) @@ -191,5 +191,5 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const Task* ResourcePackFolderModel::createParseTask(Resource& resource) { - return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast(resource)); + return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast(&resource)); } diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 19b15709d..530ce44cc 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -23,6 +23,8 @@ #include "FileSystem.h" #include "Json.h" +#include "minecraft/mod/ResourcePack.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" #include #include @@ -32,9 +34,9 @@ namespace DataPackUtils { -bool process(DataPack& pack, ProcessingLevel level) +bool process(DataPack* pack, ProcessingLevel level) { - switch (pack.type()) { + switch (pack->type()) { case ResourceType::FOLDER: return DataPackUtils::processFolder(pack, level); case ResourceType::ZIPFILE: @@ -45,16 +47,16 @@ bool process(DataPack& pack, ProcessingLevel level) } } -bool processFolder(DataPack& pack, ProcessingLevel level) +bool processFolder(DataPack* pack, ProcessingLevel level) { - Q_ASSERT(pack.type() == ResourceType::FOLDER); + Q_ASSERT(pack->type() == ResourceType::FOLDER); auto mcmeta_invalid = [&pack]() { - qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional }; - QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); + QFileInfo mcmeta_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.mcmeta")); if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) { QFile mcmeta_file(mcmeta_file_info.filePath()); if (!mcmeta_file.open(QIODevice::ReadOnly)) @@ -72,7 +74,7 @@ bool processFolder(DataPack& pack, ProcessingLevel level) return mcmeta_invalid(); // mcmeta file isn't a valid file } - QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data")); + QFileInfo data_dir_info(FS::PathCombine(pack->fileinfo().filePath(), pack->directory())); if (!data_dir_info.exists() || !data_dir_info.isDir()) { return false; // data dir does not exists or isn't valid } @@ -80,22 +82,46 @@ bool processFolder(DataPack& pack, ProcessingLevel level) if (level == ProcessingLevel::BasicInfoOnly) { return true; // only need basic info already checked } + if (auto rp = dynamic_cast(pack)) { + auto png_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + + QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png")); + if (image_file_info.exists() && image_file_info.isFile()) { + QFile pack_png_file(image_file_info.filePath()); + if (!pack_png_file.open(QIODevice::ReadOnly)) + return png_invalid(); // can't open pack.png file + + auto data = pack_png_file.readAll(); + + bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data)); + + pack_png_file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // pack.png does not exists or is not a valid file. + } + } return true; // all tests passed } -bool processZIP(DataPack& pack, ProcessingLevel level) +bool processZIP(DataPack* pack, ProcessingLevel level) { - Q_ASSERT(pack.type() == ResourceType::ZIPFILE); + Q_ASSERT(pack->type() == ResourceType::ZIPFILE); - QuaZip zip(pack.fileinfo().filePath()); + QuaZip zip(pack->fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) return false; // can't open zip file QuaZipFile file(&zip); auto mcmeta_invalid = [&pack]() { - qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional }; @@ -119,7 +145,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level) } QuaZipDir zipDir(&zip); - if (!zipDir.exists("/data")) { + if (!zipDir.exists(pack->directory())) { return false; // data dir does not exists at zip root } @@ -127,21 +153,49 @@ bool processZIP(DataPack& pack, ProcessingLevel level) zip.close(); return true; // only need basic info already checked } + if (auto rp = dynamic_cast(pack)) { + auto png_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return png_invalid(); + } + + auto data = file.readAll(); + + bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data)); + + file.close(); + zip.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + zip.close(); + return png_invalid(); // could not set pack.mcmeta as current file. + } + } zip.close(); return true; } // https://minecraft.wiki/w/Data_pack#pack.mcmeta -bool processMCMeta(DataPack& pack, QByteArray&& raw_data) +// https://minecraft.wiki/w/Raw_JSON_text_format +// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +bool processMCMeta(DataPack* pack, QByteArray&& raw_data) { try { auto json_doc = QJsonDocument::fromJson(raw_data); auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); - pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - pack.setDescription(Json::ensureString(pack_obj, "description", "")); + pack->setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); + pack->setDescription(ResourcePackUtils::processComponent(pack_obj.value("description"))); } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; @@ -152,26 +206,19 @@ bool processMCMeta(DataPack& pack, QByteArray&& raw_data) bool validate(QFileInfo file) { DataPack dp{ file }; - return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid(); + return DataPackUtils::process(&dp, ProcessingLevel::BasicInfoOnly) && dp.valid(); } } // namespace DataPackUtils -LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(false), m_token(token), m_data_pack(dp) {} - -bool LocalDataPackParseTask::abort() -{ - m_aborted = true; - return true; -} +LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack* dp) : Task(false), m_token(token), m_data_pack(dp) {} void LocalDataPackParseTask::executeTask() { - if (!DataPackUtils::process(m_data_pack)) + if (!DataPackUtils::process(m_data_pack)) { + emitFailed("process failed"); return; + } - if (m_aborted) - emitAborted(); - else - emitSucceeded(); + emitSucceeded(); } diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h index 12fd8c82c..7355783df 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h @@ -32,12 +32,12 @@ namespace DataPackUtils { enum class ProcessingLevel { Full, BasicInfoOnly }; -bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); +bool process(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full); -bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); -bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); +bool processZIP(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full); +bool processFolder(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full); -bool processMCMeta(DataPack& pack, QByteArray&& raw_data); +bool processMCMeta(DataPack* pack, QByteArray&& raw_data); /** Checks whether a file is valid as a data pack or not. */ bool validate(QFileInfo file); @@ -47,10 +47,7 @@ bool validate(QFileInfo file); class LocalDataPackParseTask : public Task { Q_OBJECT public: - LocalDataPackParseTask(int token, DataPack& dp); - - [[nodiscard]] bool canAbort() const override { return true; } - bool abort() override; + LocalDataPackParseTask(int token, DataPack* dp); void executeTask() override; @@ -59,7 +56,5 @@ class LocalDataPackParseTask : public Task { private: int m_token; - DataPack& m_data_pack; - - bool m_aborted = false; + DataPack* m_data_pack; }; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 0b80db82d..db4b2e55c 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -20,6 +20,7 @@ #include "FileSystem.h" #include "Json.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" #include #include @@ -29,155 +30,6 @@ namespace ResourcePackUtils { -bool process(ResourcePack& pack, ProcessingLevel level) -{ - switch (pack.type()) { - case ResourceType::FOLDER: - return ResourcePackUtils::processFolder(pack, level); - case ResourceType::ZIPFILE: - return ResourcePackUtils::processZIP(pack, level); - default: - qWarning() << "Invalid type for resource pack parse task!"; - return false; - } -} - -bool processFolder(ResourcePack& pack, ProcessingLevel level) -{ - Q_ASSERT(pack.type() == ResourceType::FOLDER); - - auto mcmeta_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; - return false; // the mcmeta is not optional - }; - - QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); - if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) { - QFile mcmeta_file(mcmeta_file_info.filePath()); - if (!mcmeta_file.open(QIODevice::ReadOnly)) - return mcmeta_invalid(); // can't open mcmeta file - - auto data = mcmeta_file.readAll(); - - bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data)); - - mcmeta_file.close(); - if (!mcmeta_result) { - return mcmeta_invalid(); // mcmeta invalid - } - } else { - return mcmeta_invalid(); // mcmeta file isn't a valid file - } - - QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets")); - if (!assets_dir_info.exists() || !assets_dir_info.isDir()) { - return false; // assets dir does not exists or isn't valid - } - - if (level == ProcessingLevel::BasicInfoOnly) { - return true; // only need basic info already checked - } - - auto png_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; - return true; // the png is optional - }; - - QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); - if (image_file_info.exists() && image_file_info.isFile()) { - QFile pack_png_file(image_file_info.filePath()); - if (!pack_png_file.open(QIODevice::ReadOnly)) - return png_invalid(); // can't open pack.png file - - auto data = pack_png_file.readAll(); - - bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data)); - - pack_png_file.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - return png_invalid(); // pack.png does not exists or is not a valid file. - } - - return true; // all tests passed -} - -bool processZIP(ResourcePack& pack, ProcessingLevel level) -{ - Q_ASSERT(pack.type() == ResourceType::ZIPFILE); - - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file - - QuaZipFile file(&zip); - - auto mcmeta_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; - return false; // the mcmeta is not optional - }; - - if (zip.setCurrentFile("pack.mcmeta")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return mcmeta_invalid(); - } - - auto data = file.readAll(); - - bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data)); - - file.close(); - if (!mcmeta_result) { - return mcmeta_invalid(); // mcmeta invalid - } - } else { - return mcmeta_invalid(); // could not set pack.mcmeta as current file. - } - - QuaZipDir zipDir(&zip); - if (!zipDir.exists("/assets")) { - return false; // assets dir does not exists at zip root - } - - if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); - return true; // only need basic info already checked - } - - auto png_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; - return true; // the png is optional - }; - - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } - - auto data = file.readAll(); - - bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - zip.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - zip.close(); - return png_invalid(); // could not set pack.mcmeta as current file. - } - - zip.close(); - return true; -} - QString buildStyle(const QJsonObject& obj) { QStringList styles; @@ -259,30 +111,11 @@ QString processComponent(const QJsonValue& value, bool strikethrough, bool under return {}; } -// https://minecraft.wiki/w/Raw_JSON_text_format -// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta -bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) -{ - try { - auto json_doc = QJsonDocument::fromJson(raw_data); - auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); - - pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - - pack.setDescription(processComponent(pack_obj.value("description"))); - - } catch (Json::JsonException& e) { - qWarning() << "JsonException: " << e.what() << e.cause(); - return false; - } - return true; -} - -bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data) +bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data) { auto img = QImage::fromData(raw_data); if (!img.isNull()) { - pack.setImage(img); + pack->setImage(img); } else { qWarning() << "Failed to parse pack.png."; return false; @@ -290,16 +123,16 @@ bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data) return true; } -bool processPackPNG(const ResourcePack& pack) +bool processPackPNG(const ResourcePack* pack) { auto png_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png"; return false; }; - switch (pack.type()) { + switch (pack->type()) { case ResourceType::FOLDER: { - QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png")); if (image_file_info.exists() && image_file_info.isFile()) { QFile pack_png_file(image_file_info.filePath()); if (!pack_png_file.open(QIODevice::ReadOnly)) @@ -319,7 +152,7 @@ bool processPackPNG(const ResourcePack& pack) return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 } case ResourceType::ZIPFILE: { - QuaZip zip(pack.fileinfo().filePath()); + QuaZip zip(pack->fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) return false; // can't open zip file @@ -353,28 +186,7 @@ bool processPackPNG(const ResourcePack& pack) bool validate(QFileInfo file) { ResourcePack rp{ file }; - return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid(); + return DataPackUtils::process(&rp, DataPackUtils::ProcessingLevel::BasicInfoOnly) && rp.valid(); } } // namespace ResourcePackUtils - -LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) : Task(false), m_token(token), m_resource_pack(rp) {} - -bool LocalResourcePackParseTask::abort() -{ - m_aborted = true; - return true; -} - -void LocalResourcePackParseTask::executeTask() -{ - if (!ResourcePackUtils::process(m_resource_pack)) { - emitFailed("this is not a resource pack"); - return; - } - - if (m_aborted) - emitAborted(); - else - emitSucceeded(); -} diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 97bf7b2ba..6b4378aa6 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -23,44 +23,14 @@ #include "minecraft/mod/ResourcePack.h" -#include "tasks/Task.h" - namespace ResourcePackUtils { -enum class ProcessingLevel { Full, BasicInfoOnly }; - -bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); - -bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); -bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); - QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false); -bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); -bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); +bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data); /// processes ONLY the pack.png (rest of the pack may be invalid) -bool processPackPNG(const ResourcePack& pack); +bool processPackPNG(const ResourcePack* pack); /** Checks whether a file is valid as a resource pack or not. */ bool validate(QFileInfo file); } // namespace ResourcePackUtils - -class LocalResourcePackParseTask : public Task { - Q_OBJECT - public: - LocalResourcePackParseTask(int token, ResourcePack& rp); - - [[nodiscard]] bool canAbort() const override { return true; } - bool abort() override; - - void executeTask() override; - - [[nodiscard]] int token() const { return m_token; } - - private: - int m_token; - - ResourcePack& m_resource_pack; - - bool m_aborted = false; -}; diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index 74d8d8d60..d45f537fa 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -180,8 +180,10 @@ bool LocalWorldSaveParseTask::abort() void LocalWorldSaveParseTask::executeTask() { - if (!WorldSaveUtils::process(m_save)) + if (!WorldSaveUtils::process(m_save)) { + emitFailed("this is not a world"); return; + } if (m_aborted) emitAborted(); diff --git a/tests/DataPackParse_test.cpp b/tests/DataPackParse_test.cpp index cd6ae8e8f..14f80858f 100644 --- a/tests/DataPackParse_test.cpp +++ b/tests/DataPackParse_test.cpp @@ -38,7 +38,7 @@ class DataPackParseTest : public QObject { QString zip_dp = FS::PathCombine(source, "test_data_pack_boogaloo.zip"); DataPack pack{ QFileInfo(zip_dp) }; - bool valid = DataPackUtils::processZIP(pack); + bool valid = DataPackUtils::processZIP(&pack); QVERIFY(pack.packFormat() == 4); QVERIFY(pack.description() == "Some data pack 2 boobgaloo"); @@ -52,7 +52,7 @@ class DataPackParseTest : public QObject { QString folder_dp = FS::PathCombine(source, "test_folder"); DataPack pack{ QFileInfo(folder_dp) }; - bool valid = DataPackUtils::processFolder(pack); + bool valid = DataPackUtils::processFolder(&pack); QVERIFY(pack.packFormat() == 10); QVERIFY(pack.description() == "Some data pack, maybe"); @@ -66,7 +66,7 @@ class DataPackParseTest : public QObject { QString folder_dp = FS::PathCombine(source, "another_test_folder"); DataPack pack{ QFileInfo(folder_dp) }; - bool valid = DataPackUtils::process(pack); + bool valid = DataPackUtils::process(&pack); QVERIFY(pack.packFormat() == 6); QVERIFY(pack.description() == "Some data pack three, leaves on the tree"); diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp index e1092167d..17c0482fc 100644 --- a/tests/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -18,6 +18,7 @@ #include #include +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" #include @@ -35,7 +36,7 @@ class ResourcePackParseTest : public QObject { QString zip_rp = FS::PathCombine(source, "test_resource_pack_idk.zip"); ResourcePack pack{ QFileInfo(zip_rp) }; - bool valid = ResourcePackUtils::processZIP(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly); + bool valid = DataPackUtils::processZIP(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly); QVERIFY(pack.packFormat() == 3); QVERIFY(pack.description() == @@ -51,7 +52,7 @@ class ResourcePackParseTest : public QObject { QString folder_rp = FS::PathCombine(source, "test_folder"); ResourcePack pack{ QFileInfo(folder_rp) }; - bool valid = ResourcePackUtils::processFolder(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly); + bool valid = DataPackUtils::processFolder(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly); QVERIFY(pack.packFormat() == 1); QVERIFY(pack.description() == "Some resource pack maybe"); @@ -65,7 +66,7 @@ class ResourcePackParseTest : public QObject { QString folder_rp = FS::PathCombine(source, "another_test_folder"); ResourcePack pack{ QFileInfo(folder_rp) }; - bool valid = ResourcePackUtils::process(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly); + bool valid = DataPackUtils::process(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly); QVERIFY(pack.packFormat() == 6); QVERIFY(pack.description() == "o quartel pegou fogo, policia deu sinal, acode acode acode a bandeira nacional"); From 998bc660fa97f4527443dd826eede5e8eab23651 Mon Sep 17 00:00:00 2001 From: sticks Date: Fri, 28 Feb 2025 15:05:30 -0600 Subject: [PATCH 002/106] feat: add updater dialogues Signed-off-by: sticks --- .../ui/pages/instance/ManagedPackPage.cpp | 38 ++++++++++++------- launcher/ui/pages/instance/ManagedPackPage.h | 2 + 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index a909d10d1..f44e211f6 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -245,7 +245,6 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin } // MODRINTH - void ModrinthManagedPackPage::parseManagedPack() { qDebug() << "Parsing Modrinth pack"; @@ -338,6 +337,24 @@ void ModrinthManagedPackPage::suggestVersion() ManagedPackPage::suggestVersion(); } +/// @brief Called when the update task has completed. +/// Internally handles the closing of the instance window if the update was successful and shows a message box. +/// @param did_succeed Whether the update task was successful. +void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const +{ + // Close the window if the update was successful + if (m_instance_window && did_succeed) { + m_instance_window->close(); + CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) + ->show(); + } else if (!did_succeed) { + CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. -- Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) + ->show(); + qWarning() << "onUpdateTaskCompleted: unknown state encountered: did_succeed=" << did_succeed << " m_instance_window=" << m_instance_window; + } + +} + void ModrinthManagedPackPage::update() { auto index = ui->versionsComboBox->currentIndex(); @@ -363,10 +380,9 @@ void ModrinthManagedPackPage::update() extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); + // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } void ModrinthManagedPackPage::updateFromFile() @@ -386,14 +402,12 @@ void ModrinthManagedPackPage::updateFromFile() extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); + // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } // FLAME - FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent) : ManagedPackPage(inst, instance_window, parent) { @@ -531,9 +545,7 @@ void FlameManagedPackPage::update() extracted->setConfirmUpdate(false); auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } void FlameManagedPackPage::updateFromFile() @@ -555,8 +567,6 @@ void FlameManagedPackPage::updateFromFile() extracted->setConfirmUpdate(false); auto did_succeed = runUpdateTask(extracted); - - if (m_instance_window && did_succeed) - m_instance_window->close(); + onUpdateTaskCompleted(did_succeed); } #include "ManagedPackPage.moc" diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index c44f77070..e8d304c6b 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -94,6 +94,8 @@ class ManagedPackPage : public QWidget, public BasePage { BaseInstance* m_inst; bool m_loaded = false; + + void onUpdateTaskCompleted(bool did_succeed) const; }; /** Simple page for when we aren't a managed pack. */ From ea5458ed2253a4e8a9ef50d076fe72c57d2cb669 Mon Sep 17 00:00:00 2001 From: Sticks Date: Fri, 28 Feb 2025 21:06:34 -0600 Subject: [PATCH 003/106] fix: Grammar correction in failed message for update dialogues Co-authored-by: TheKodeToad Signed-off-by: Sticks --- launcher/ui/pages/instance/ManagedPackPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index f44e211f6..ce68c24d3 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -348,7 +348,7 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) ->show(); } else if (!did_succeed) { - CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. -- Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) + CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) ->show(); qWarning() << "onUpdateTaskCompleted: unknown state encountered: did_succeed=" << did_succeed << " m_instance_window=" << m_instance_window; } From 3da94eeea287237a348c9c1b3b8686fb17d85246 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Nov 2024 18:13:47 +0200 Subject: [PATCH 004/106] removed some unused QNetworkAccessManager references Signed-off-by: Trial97 --- launcher/minecraft/auth/AuthFlow.cpp | 1 - launcher/minecraft/auth/AuthSession.h | 3 --- launcher/modplatform/flame/FileResolvingTask.cpp | 5 +---- launcher/modplatform/flame/FileResolvingTask.h | 5 +---- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 5 +---- launcher/modplatform/flame/FlameModIndex.h | 6 +----- launcher/modplatform/modrinth/ModrinthPackIndex.h | 1 - .../ui/pages/modplatform/flame/FlameResourceModels.cpp | 8 ++++---- 9 files changed, 9 insertions(+), 27 deletions(-) diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp index 19fbe15dd..287831b2f 100644 --- a/launcher/minecraft/auth/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -1,5 +1,4 @@ #include -#include #include #include diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index 54e7d69e0..cbe604805 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -1,12 +1,9 @@ #pragma once -#include #include #include -#include "QObjectPtr.h" class MinecraftAccount; -class QNetworkAccessManager; struct AuthSession { bool MakeOffline(QString offline_playername); diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 7ff38d57e..df737d048 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -20,7 +20,6 @@ #include #include "Json.h" -#include "QObjectPtr.h" #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" @@ -33,9 +32,7 @@ static const FlameAPI flameAPI; static ModrinthAPI modrinthAPI; -Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess) - : m_network(network), m_manifest(toProcess) -{} +Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) : m_manifest(toProcess) {} bool Flame::FileResolvingTask::abort() { diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index edd9fce9a..3fe8dfb1a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -17,8 +17,6 @@ */ #pragma once -#include - #include "PackManifest.h" #include "tasks/Task.h" @@ -26,7 +24,7 @@ namespace Flame { class FileResolvingTask : public Task { Q_OBJECT public: - explicit FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess); + explicit FileResolvingTask(Flame::Manifest& toProcess); virtual ~FileResolvingTask() = default; bool canAbort() const override { return true; } @@ -44,7 +42,6 @@ class FileResolvingTask : public Task { void getFlameProjects(); private: /* data */ - shared_qobject_ptr m_network; Flame::Manifest m_manifest; std::shared_ptr m_result; Task::Ptr m_task; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 6073e90a2..4ed1a2624 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -437,7 +437,7 @@ bool FlameCreationTask::createInstance() instance.setName(name()); - m_modIdResolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); + m_modIdResolver.reset(new Flame::FileResolvingTask(m_pack)); connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) { m_modIdResolver.reset(); diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 7de05f177..e98fec3d2 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,10 +76,7 @@ static QString enumToString(int hash_algorithm) } } -void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, - QJsonArray& arr, - [[maybe_unused]] const shared_qobject_ptr& network, - const BaseInstance* inst) +void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst) { QVector unsortedVersions; for (auto versionIter : arr) { diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index 1bcaa44ba..a449569c4 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -6,7 +6,6 @@ #include "modplatform/ModIndex.h" -#include #include "BaseInstance.h" namespace FlameMod { @@ -14,10 +13,7 @@ namespace FlameMod { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); -void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, - QJsonArray& arr, - const shared_qobject_ptr& network, - const BaseInstance* inst); +void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion; } // namespace FlameMod \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 93f91eec2..aa568ba13 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -19,7 +19,6 @@ #include "modplatform/ModIndex.h" -#include #include "BaseInstance.h" namespace Modrinth { diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index ae4562be4..d737d6050 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -32,7 +32,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); } auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion @@ -65,7 +65,7 @@ void FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJso void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); } bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const @@ -93,7 +93,7 @@ void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJson void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); QVector filtered_versions(m.versions.size()); @@ -157,7 +157,7 @@ void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonO void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); } bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const From 22e0659a5f84802af626c5383c9623aee851607e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Nov 2024 19:01:59 +0200 Subject: [PATCH 005/106] removed unused BaseInstance argument from some API functions Signed-off-by: Trial97 --- launcher/modplatform/flame/FlameModIndex.cpp | 2 +- launcher/modplatform/flame/FlameModIndex.h | 6 +++--- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 2 +- launcher/modplatform/modrinth/ModrinthPackIndex.h | 2 +- .../ui/pages/modplatform/flame/FlameResourceModels.cpp | 8 ++++---- .../pages/modplatform/modrinth/ModrinthResourceModels.cpp | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index e98fec3d2..c700d7453 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,7 +76,7 @@ static QString enumToString(int hash_algorithm) } } -void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst) +void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { QVector unsortedVersions; for (auto versionIter : arr) { diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index a449569c4..f6b4b22be 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -13,7 +13,7 @@ namespace FlameMod { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); -void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst); -auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; -auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion; +void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr); +ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false); +ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst); } // namespace FlameMod \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 72550937c..16b300b02 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -112,7 +112,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraDataLoaded = true; } -void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst) +void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { QVector unsortedVersions; for (auto versionIter : arr) { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index aa568ba13..16f3d262c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -25,7 +25,7 @@ namespace Modrinth { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); -void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst); +void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr); auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index d737d6050..fea1fc27a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -32,7 +32,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr); } auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion @@ -65,7 +65,7 @@ void FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJso void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr); } bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const @@ -93,7 +93,7 @@ void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJson void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr); QVector filtered_versions(m.versions.size()); @@ -157,7 +157,7 @@ void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonO void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, &m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr); } bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 856018294..213d6e39e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -39,7 +39,7 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion @@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray @@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray @@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray From 8e33608ddbb92e586d452b212c09a0556516ad7f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Nov 2024 21:13:36 +0200 Subject: [PATCH 006/106] refactor FlameCheckUpdate to remove qEventLoop Signed-off-by: Trial97 --- launcher/modplatform/flame/FlameAPI.cpp | 51 --- launcher/modplatform/flame/FlameAPI.h | 12 +- .../modplatform/flame/FlameCheckUpdate.cpp | 313 +++++++++--------- launcher/modplatform/flame/FlameCheckUpdate.h | 11 +- 4 files changed, 173 insertions(+), 214 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 699eb792a..15eb7a696 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -102,57 +102,6 @@ QString FlameAPI::getModDescription(int modId) return description; } -QList FlameAPI::getLatestVersions(VersionSearchArgs&& args) -{ - auto versions_url_optional = getVersionsURL(args); - if (!versions_url_optional.has_value()) - return {}; - - auto versions_url = versions_url_optional.value(); - - QEventLoop loop; - - auto netJob = makeShared(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); - auto response = std::make_shared(); - QList ver; - - netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); - - QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - try { - auto obj = Json::requireObject(doc); - auto arr = Json::requireArray(obj, "data"); - - for (auto file : arr) { - auto file_obj = Json::requireObject(file); - ver.append(FlameMod::loadIndexedPackVersion(file_obj)); - } - - } catch (Json::JsonException& e) { - qCritical() << "Failed to parse response from a version request."; - qCritical() << e.what(); - qDebug() << doc; - } - }); - - QObject::connect(netJob.get(), &NetJob::finished, &loop, &QEventLoop::quit); - - netJob->start(); - - loop.exec(); - - return ver; -} - Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr response) const { auto netJob = makeShared(QString("Flame::GetProjects"), APPLICATION->network()); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 3ca0d5448..f85a08eb1 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -15,7 +15,6 @@ class FlameAPI : public NetworkResourceAPI { QString getModFileChangelog(int modId, int fileId); QString getModDescription(int modId); - QList getLatestVersions(VersionSearchArgs&& args); std::optional getLatestVersion(QList versions, QList instanceLoaders, ModPlatform::ModLoaderTypes fallback); @@ -108,12 +107,6 @@ class FlameAPI : public NetworkResourceAPI { return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); } - private: - [[nodiscard]] std::optional getInfoURL(QString const& id) const override - { - return QString("https://api.curseforge.com/v1/mods/%1").arg(id); - } - [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override { auto addonId = args.pack.addonId.toString(); @@ -129,6 +122,11 @@ class FlameAPI : public NetworkResourceAPI { return url; } + private: + [[nodiscard]] std::optional getInfoURL(QString const& id) const override + { + return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + } [[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override { auto addonId = args.dependency.addonId.toString(); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 2b469276d..047813675 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -3,115 +3,31 @@ #include "FlameAPI.h" #include "FlameModIndex.h" -#include +#include #include #include "Json.h" +#include "QObjectPtr.h" #include "ResourceDownloadTask.h" -#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" +#include "modplatform/ModIndex.h" #include "net/ApiDownload.h" +#include "net/NetJob.h" +#include "tasks/Task.h" static FlameAPI api; bool FlameCheckUpdate::abort() { - m_was_aborted = true; - if (m_net_job) - return m_net_job->abort(); - return true; -} - -ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info) -{ - ModPlatform::IndexedPack pack; - - QEventLoop loop; - - auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network()); - - auto response = std::make_shared(); - auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString()); - auto dl = Net::ApiDownload::makeByteArray(url, response); - get_project_job->addNetAction(dl); - - QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - try { - auto doc_obj = Json::requireObject(doc); - auto data_obj = Json::requireObject(doc_obj, "data"); - FlameMod::loadIndexedPack(pack, data_obj); - } catch (Json::JsonException& e) { - qWarning() << e.cause(); - qDebug() << doc; - } - }); - - connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed); - QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] { - get_project_job->deleteLater(); - loop.quit(); - }); - - get_project_job->start(); - loop.exec(); - - return pack; -} - -ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId) -{ - ModPlatform::IndexedVersion ver; - - QEventLoop loop; - - auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network()); - - auto response = std::make_shared(); - auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId)); - auto dl = Net::ApiDownload::makeByteArray(url, response); - get_file_info_job->addNetAction(dl); - - QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - try { - auto doc_obj = Json::requireObject(doc); - auto data_obj = Json::requireObject(doc_obj, "data"); - ver = FlameMod::loadIndexedPackVersion(data_obj); - } catch (Json::JsonException& e) { - qWarning() << e.cause(); - qDebug() << doc; - } - }); - connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed); - QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] { - get_file_info_job->deleteLater(); - loop.quit(); - }); - - get_file_info_job->start(); - loop.exec(); - - return ver; + bool result = false; + if (m_task && m_task->canAbort()) { + result = m_task->abort(); + } + Task::abort(); + return result; } /* Check for update: @@ -123,66 +39,163 @@ void FlameCheckUpdate::executeTask() { setStatus(tr("Preparing resources for CurseForge...")); - int i = 0; + auto netJob = new NetJob("Get latest versions", APPLICATION->network()); + connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods); + + connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress); + connect(netJob, &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress); + connect(netJob, &Task::details, this, &FlameCheckUpdate::setDetails); for (auto* resource : m_resources) { - setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name())); - setProgress(i++, m_resources.size()); - - auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions }); - - // Check if we were aborted while getting the latest version - if (m_was_aborted) { - aborted(); - return; - } - auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders); - - setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); - - if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { - QString reason; - if (dynamic_cast(resource) != nullptr) - reason = - tr("No valid version found for this resource. It's probably unavailable for the current game " - "version / mod loader."); - else - reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); - - emit checkFailed(resource, reason); + auto versions_url_optional = api.getVersionsURL({ { resource->metadata()->project_id.toString() }, m_game_versions }); + if (!versions_url_optional.has_value()) continue; - } - if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { - auto pack = getProjectInfo(latest_ver.value()); - auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString()); - emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url); + auto response = std::make_shared(); + auto task = Net::ApiDownload::makeByteArray(versions_url_optional.value(), response); - continue; - } + connect(task.get(), &Task::succeeded, this, [this, resource, response] { getLatestVersionCallback(resource, response); }); + netJob->addNetAction(task); + } + m_task.reset(netJob); + m_task->start(); +} - // Fake pack with the necessary info to pass to the download task :) - auto pack = std::make_shared(); - pack->name = resource->name(); - pack->slug = resource->metadata()->slug; - pack->addonId = resource->metadata()->project_id; - pack->provider = ModPlatform::ResourceProvider::FLAME; - if (!latest_ver->hash.isEmpty() && - (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); - } - - auto download_task = makeShared(pack, latest_ver.value(), m_resource_model); - m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, - api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); - } - m_deps.append(std::make_shared(pack, latest_ver.value())); +void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ptr response) +{ + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; } - emitSucceeded(); + // Fake pack with the necessary info to pass to the download task :) + auto pack = std::make_shared(); + pack->name = resource->name(); + pack->slug = resource->metadata()->slug; + pack->addonId = resource->metadata()->project_id; + pack->provider = ModPlatform::ResourceProvider::FLAME; + try { + auto obj = Json::requireObject(doc); + auto arr = Json::requireArray(obj, "data"); + + FlameMod::loadIndexedPackVersions(*pack.get(), arr); + } catch (Json::JsonException& e) { + qCritical() << "Failed to parse response from a version request."; + qCritical() << e.what(); + qDebug() << doc; + } + auto latest_ver = api.getLatestVersion(pack->versions, m_loaders_list, resource->metadata()->loaders); + + setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); + + if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { + QString reason; + if (dynamic_cast(resource) != nullptr) + reason = + tr("No valid version found for this resource. It's probably unavailable for the current game " + "version / mod loader."); + else + reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + + emit checkFailed(resource, reason); + return; + } + + if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { + m_blocked[resource] = latest_ver->fileId.toString(); + return; + } + + if (!latest_ver->hash.isEmpty() && + (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); + } + + auto download_task = makeShared(pack, latest_ver.value(), m_resource_model); + m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, + api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), + ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); + } + m_deps.append(std::make_shared(pack, latest_ver.value())); } + +void FlameCheckUpdate::collectBlockedMods() +{ + QStringList addonIds; + QHash quickSearch; + for (auto const& resource : m_blocked.keys()) { + auto addonId = resource->metadata()->project_id.toString(); + addonIds.append(addonId); + quickSearch[addonId] = resource; + } + + auto response = std::make_shared(); + Task::Ptr projTask; + + if (addonIds.isEmpty()) { + emitSucceeded(); + return; + } else if (addonIds.size() == 1) { + projTask = api.getProject(*addonIds.begin(), response); + } else { + projTask = api.getProjects(addonIds, response); + } + + connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] { + QJsonParseError parse_error{}; + auto doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame projects task at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + try { + QJsonArray entries; + if (addonIds.size() == 1) + entries = { Json::requireObject(Json::requireObject(doc), "data") }; + else + entries = Json::requireArray(Json::requireObject(doc), "data"); + + for (auto entry : entries) { + auto entry_obj = Json::requireObject(entry); + + auto id = QString::number(Json::requireInteger(entry_obj, "id")); + + auto resource = quickSearch.find(id).value(); + + ModPlatform::IndexedPack pack; + try { + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); + + FlameMod::loadIndexedPack(pack, entry_obj); + auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); + emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), + recover_url); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << entries; + } + } + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } + }); + + connect(projTask.get(), &Task::finished, this, &FlameCheckUpdate::emitSucceeded); // do not care much about error + connect(projTask.get(), &Task::progress, this, &FlameCheckUpdate::setProgress); + connect(projTask.get(), &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress); + connect(projTask.get(), &Task::details, this, &FlameCheckUpdate::setDetails); + m_task.reset(projTask); + m_task->start(); +} \ No newline at end of file diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 6543a0e04..eb80ce47c 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -1,7 +1,6 @@ #pragma once #include "modplatform/CheckUpdateTask.h" -#include "net/NetJob.h" class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT @@ -19,12 +18,12 @@ class FlameCheckUpdate : public CheckUpdateTask { protected slots: void executeTask() override; + private slots: + void getLatestVersionCallback(Resource* resource, std::shared_ptr response); + void collectBlockedMods(); private: - ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info); - ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId); + Task::Ptr m_task = nullptr; - NetJob* m_net_job = nullptr; - - bool m_was_aborted = false; + QHash m_blocked; }; From 4dd026d1ae0c97bc0fe48ff35ba1550cb0e98800 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Nov 2024 21:58:41 +0200 Subject: [PATCH 007/106] QList to QVector Signed-off-by: Trial97 --- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 15eb7a696..a06793de0 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -215,7 +215,7 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QList versions, +std::optional FlameAPI::getLatestVersion(QVector versions, QList instanceLoaders, ModPlatform::ModLoaderTypes modLoaders) { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index f85a08eb1..509e1abcd 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -15,7 +15,7 @@ class FlameAPI : public NetworkResourceAPI { QString getModFileChangelog(int modId, int fileId); QString getModDescription(int modId); - std::optional getLatestVersion(QList versions, + std::optional getLatestVersion(QVector versions, QList instanceLoaders, ModPlatform::ModLoaderTypes fallback); From d4e1851e6779d99e3ffde79c7e4e8edb568415ce Mon Sep 17 00:00:00 2001 From: Sticks Date: Tue, 4 Mar 2025 22:20:40 -0600 Subject: [PATCH 008/106] fix: remove qWarning for unknown state Co-authored-by: TheKodeToad Signed-off-by: Sticks --- launcher/ui/pages/instance/ManagedPackPage.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index ce68c24d3..0fccd1d33 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -343,14 +343,15 @@ void ModrinthManagedPackPage::suggestVersion() void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const { // Close the window if the update was successful - if (m_instance_window && did_succeed) { - m_instance_window->close(); + if (did_succeed) { + if (m_instance_window != nullptr) + m_instance_window->close(); + CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) ->show(); - } else if (!did_succeed) { + } else { CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) ->show(); - qWarning() << "onUpdateTaskCompleted: unknown state encountered: did_succeed=" << did_succeed << " m_instance_window=" << m_instance_window; } } From a65ced1598751d5838859a7c9a7b15b85ab310c2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:01:58 -0700 Subject: [PATCH 009/106] ci: add a workflow to detect and check dependencies for blocked pull requests Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 236 ++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 .github/workflows/blocked_prs.yml diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml new file mode 100644 index 000000000..8f060dbb6 --- /dev/null +++ b/.github/workflows/blocked_prs.yml @@ -0,0 +1,236 @@ +name: Blocked/Stacked Pull Requests Automation + +on: + pull_request_target: + types: + - opened + - edited + workflow_dispatch: + inputs: + pr_id: + description: Local Pull Request number to work on + required: true + type: number + +jobs: + block_status: + name: Check Blocked Status + runs-on: ubuntu-latest + steps: + - name: Setup From Pull Request Vent + if: ${{ github.event_name != 'workflow_dispatch' }} + id: pr_event_setup + env: + REPO_L: ${{ github.event.pull_request.base.repo.name }} + OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} + REPO_URL_L: $ {{ github.event.pull_request.base.repo.html_url }} + PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} + PR_NUMBER_L: ${{ github.event.pull_request.number }} + PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} + PR_BODY_L: ${{ github.event.pull_request.body }} + PR_LABLES_L: ${{ github.event.pull_request.labels }} + run: | + # setup env for the rest of the workflow + { + echo "REPO=$REPO_L" + echo "OWNER=$OWNER_L" + echo "REPO_URL=$REPO_URL_L" + echo "PR_NUMBER=$PR_NUMBER_L" + echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" + echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" + echo "PR_BODY=$PR_BODY_L" + echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + } >> "$GITHUB_ENV" + + - name: Setup From Dispatch Event + if: ${{ github.event_name == 'workflow_dispatch' }} + id: dispatch_event_setup + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OWNER_REPO_L: ${{ github.repository }} + OWNER_L: ${{ github.repository_owner }} + REPO_URL_L: $ {{ github.repositoryUrl }} + PR_NUMBER_L: ${{ inputs.pr_id }} + run: | + # setup env for the rest of the workflow + owner_prefix="$OWNER_L/" + REPO_L="${OWNER_REPO_L#"$owner_prefix"}" + PR_L=$( + gh api \ + -H "Accept: application/vnd.github.raw+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" + ) + PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") + PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") + PR_BODY_L=$(jq -r '.body' <<< "$PR_L") + PR_LABELS_L=$(jq '.labels' <<< "$PR_L") + { + echo "REPO=$REPO_L" + echo "OWNER=$OWNER_L" + echo "REPO_URL=$REPO_URL_L" + echo "PR_NUMBER=$PR_NUMBER_L" + echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" + echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" + echo "PR_BODY=$PR_BODY_L" + echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + } >> "$GITHUB_ENV" + + + - name: Find Blocked/Stacked PRs in body + id: pr_ids + run: | + PRS=$( + jq ' + . as $body + | ( + $body | scan("blocked (?by)|(?on):? #(?[0-9]+)") + | map({ + "type": "Blocked on", + "number": ( . | tonumber ) + }) + ) as $bprs + | ( + $body | scan("stacked on:? #(?[0-9]+)") + | map({ + "type": "Stacked on", + "number": ( . | tonumber ) + }) + ) as $sprs + | ($bprs + $sprs) as $prs + | { + "blocking": $prs, + "numBlocking": ( $prs | length), + } + ' <<< "$PR_BODY" + ) + echo "prs=$PRS" >> "$GITHUB_OUPUT" + + - name: Collect Blocked PR Data + id: blocked_data + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BLOCKED_PR_DATA=$( + while read -r PR ; do + gh api \ + -H "Accept: application/vnd.github.raw+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ + | jq --arg type "$(jq -r '.type' <<< "$PR")" \ + ' + . | { + "type": $type, + "number": .number, + "merged": .merged, + "labels": (reduce .labels[].name as $l ([]; . + [$l])), + "basePrUrl": .html_url, + "baseRepoName": .head.repo.name, + "baseRepoOwner": .head.repo.owner.login, + "baseRepoUrl": .head.repo.html_url, + "baseSha": .head.sha, + "baseRefName": .head.ref, + } + ' + done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s + ) + echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUPUT" + echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" + + - name: Apply Blocked Label if Missing + id: label_blocked + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" \ + -f "labels[]=blocked" + + - name: Remove 'blocked' Label if All Dependencies Are Merged + id: unlabel_blocked + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels/blocked" + + - name: Apply 'blocking' Label to Dependencies if Missing + id: label_blocking + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # label pr dependencies with 'blocking' if not already + while read -r PR_DATA ; do + if jq -e 'all(.labels[]; . != "blocking")' <<< "$PR_DATA" > /dev/null ; then + PR=$(jq -r '.number' <<< "$PR_DATA") + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/issues/$PR/labels" \ + -f "labels[]=blocking" + fi + done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + + - name: Apply Blocking PR Status Check + id: blocked_check + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # create commit Status, overwrites previous identical context + while read -r PR_DATA ; do + DESC=$( + jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$PR_DATA" + ) + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${OWNER}/${REPO}/statuses/${PR_HEAD_SHA}" \ + -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$PR_DATA")" \ + -f "target_url=$(jq -r '.basePrUrl' <<< "$PR_DATA" )" \ + -f "description=$DESC" \ + -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$PR_DATA")" + done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + + - name: Context Comment + id: blocked_comment + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + run: | + COMMENT_PATH="$(pwd)/temp_comment_file.txt" + touch "$COMMENT_PATH" + echo "" > "$COMMENT_PATH" + while read -r PR_DATA ; do + BASE_PR=$(jq '.number' <<< "$PR_DATA") + BASE_REF_NAME=$(jq '.baseRefName' <<< "$PR_DATA") + COMPARE_URL="$REPO_URL/compare/$BASE_REF_NAME...$PR_HEAD_LABEL" + STATUS=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$PR_DATA") + TYPE=$(jq -r '.type' <<< "$PR_DATA") + echo " - $TYPE #$BASE_PR $STATUS [(compare)]($COMPARE_URL)" >> "$COMMENT_PATH" + done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" + + - name: 💬 PR Comment + if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + continue-on-error: true + uses: spicyparrot/pr-comment-action@v1.0.0 + with: + comment: "### PR Dependencies :pushpin:" + comment_path: ${{ steps.blocked_comment.outputs.file_path }} + comment_id: "block_pr_dependencies" From be1ce8dd9d0612559560a03c4e70f667430de939 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 11 Mar 2025 20:19:03 -0700 Subject: [PATCH 010/106] ci: add workflow to trigger refresh of dependants when a blocking pr is merged Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 40 ++++++++----- .github/workflows/merge_blocking_pr.yml | 80 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/merge_blocking_pr.yml diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 8f060dbb6..ecafc65a8 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -13,12 +13,19 @@ on: type: number jobs: - block_status: + blocked_status: name: Check Blocked Status runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + statuses: write + checks: write + steps: - name: Setup From Pull Request Vent - if: ${{ github.event_name != 'workflow_dispatch' }} + if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: REPO_L: ${{ github.event.pull_request.base.repo.name }} @@ -43,7 +50,7 @@ jobs: } >> "$GITHUB_ENV" - name: Setup From Dispatch Event - if: ${{ github.event_name == 'workflow_dispatch' }} + if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -57,13 +64,13 @@ jobs: REPO_L="${OWNER_REPO_L#"$owner_prefix"}" PR_L=$( gh api \ - -H "Accept: application/vnd.github.raw+json" \ + -H "Accept: application/vnd.github.text+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" ) PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") - PR_BODY_L=$(jq -r '.body' <<< "$PR_L") + PR_BODY_L=$(jq -r '.body_text' <<< "$PR_L") PR_LABELS_L=$(jq '.labels' <<< "$PR_L") { echo "REPO=$REPO_L" @@ -84,7 +91,7 @@ jobs: jq ' . as $body | ( - $body | scan("blocked (?by)|(?on):? #(?[0-9]+)") + $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") | map({ "type": "Blocked on", "number": ( . | tonumber ) @@ -104,18 +111,18 @@ jobs: } ' <<< "$PR_BODY" ) - echo "prs=$PRS" >> "$GITHUB_OUPUT" + echo "prs=$PRS" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data id: blocked_data - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BLOCKED_PR_DATA=$( while read -r PR ; do gh api \ - -H "Accept: application/vnd.github.raw+json" \ + -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ | jq --arg type "$(jq -r '.type' <<< "$PR")" \ @@ -135,12 +142,12 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s ) - echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUPUT" + echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" - name: Apply Blocked Label if Missing id: label_blocked - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -154,7 +161,7 @@ jobs: - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -167,7 +174,7 @@ jobs: - name: Apply 'blocking' Label to Dependencies if Missing id: label_blocking - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -187,7 +194,7 @@ jobs: - name: Apply Blocking PR Status Check id: blocked_check - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -210,7 +217,7 @@ jobs: - name: Context Comment id: blocked_comment - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" @@ -227,10 +234,11 @@ jobs: echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - name: 💬 PR Comment - if: ${{ fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 }} + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true uses: spicyparrot/pr-comment-action@v1.0.0 with: comment: "### PR Dependencies :pushpin:" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies" + diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml new file mode 100644 index 000000000..fd52da295 --- /dev/null +++ b/.github/workflows/merge_blocking_pr.yml @@ -0,0 +1,80 @@ +name: Merged Blocking Pull Request Automation + +on: + pull_request: + types: + - closed + +jobs: + update_blocked_status: + name: Update Blocked Status + runs-on: ubuntu-latest + + if: github.event.pull_request.merged == true + + permissions: + issues: write + pull-requests: write + statuses: write + checks: write + + steps: + - name: Gather Dependent PRs + id: gather_deps + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + run: | + owner="${{ github.repository_owner }}" + owner_repo="${{ github.repository }}" + repo="${owner_repo#"${owner}/"}" + blocked_prs=$( + gh api graphql \ + -f repo="$repo" \ + -f owner="$owner" \ + -f query=' + query($repo: String!, $owner: String!, $endCursor: String) { + repository(name: $repo, owner: $owner) { + pullRequests(first: 100, after: $endCursor, states: [OPEN], labels: ["blocked"]) { + nodes { + number + bodyText + merged + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + ' \ + --paginate \ + --slurp \ + | jq --argjson pr "${{ github.event.pull_request.number }}" ' + [.[].data.repository.pullRequests.nodes[]] | .[] | select( + .bodyText | + scan("(?:blocked (?:by|on)|stacked on):? #(?[0-9]+)") | + map(tonumber) | + any(.[]; . == $pr) + ) + ' + ) + echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" + echo "numdeps='$(jq -r '. | length' <<< "$blocked_prs")" + + - name: Trigger Blocked PP Workflows for Dependants + if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + while read -r pr ; do + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${{ github.repository }}/actions/workflows/blocked_prs.yml/dispatches" \ + -f "ref=${{ github.ref_name }}" \ + -f "inputs[pr_id]=$pr" + done < <(jq -c '.[].number' <<< "${{steps.gather_deps.outputs.deps}}") + From cfc3c767791d5c2da170fd8ccd1199e1c8028d13 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:01:20 -0700 Subject: [PATCH 011/106] ci: fix labels detected as sequence Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index ecafc65a8..43915776d 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -24,7 +24,7 @@ jobs: checks: write steps: - - name: Setup From Pull Request Vent + - name: Setup From Pull Request Event if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: @@ -35,7 +35,7 @@ jobs: PR_NUMBER_L: ${{ github.event.pull_request.number }} PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} PR_BODY_L: ${{ github.event.pull_request.body }} - PR_LABLES_L: ${{ github.event.pull_request.labels }} + PR_LABLES_L: "${{ github.event.pull_request.labels }}" run: | # setup env for the rest of the workflow { From cbd1fc6f43eb4872cac250f1c074dc2c650f96a0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:26:02 -0700 Subject: [PATCH 012/106] ci: fix repo URL in blocked pr workflow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 43915776d..04ecd038e 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -30,7 +30,7 @@ jobs: env: REPO_L: ${{ github.event.pull_request.base.repo.name }} OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} - REPO_URL_L: $ {{ github.event.pull_request.base.repo.html_url }} + REPO_URL_L: ${{ github.event.pull_request.base.repo.html_url }} PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} PR_NUMBER_L: ${{ github.event.pull_request.number }} PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} @@ -56,7 +56,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER_REPO_L: ${{ github.repository }} OWNER_L: ${{ github.repository_owner }} - REPO_URL_L: $ {{ github.repositoryUrl }} + REPO_URL_L: https://github.com/${{ github.repository }} PR_NUMBER_L: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow From a2907dcaa356c22f6e58f82947bd1829e85abb7f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:05:46 -0700 Subject: [PATCH 013/106] ci: fix blocked PR workflow to preserve JSON in ENV Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 124 ++++++++++++++++-------------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 04ecd038e..ed07036af 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -28,25 +28,25 @@ jobs: if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: - REPO_L: ${{ github.event.pull_request.base.repo.name }} - OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} - REPO_URL_L: ${{ github.event.pull_request.base.repo.html_url }} - PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} - PR_NUMBER_L: ${{ github.event.pull_request.number }} - PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} - PR_BODY_L: ${{ github.event.pull_request.body }} - PR_LABLES_L: "${{ github.event.pull_request.labels }}" + PR_JSON: ${{ toJSON(github.event.pull_request) }} run: | # setup env for the rest of the workflow { - echo "REPO=$REPO_L" - echo "OWNER=$OWNER_L" - echo "REPO_URL=$REPO_URL_L" - echo "PR_NUMBER=$PR_NUMBER_L" - echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" - echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" - echo "PR_BODY=$PR_BODY_L" - echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" + echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" + echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" + echo "JOB_DATA=$(jq ' + { + "repo": .base.repo.name, + "owner": .base.repo.owner.login, + "repoUrl": .base.repo.html_url, + "prNumber": .number, + "prHeadSha": .head.sha, + "prHeadLabel": .head.label, + "prBody": .body, + "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + } + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - name: Setup From Dispatch Event @@ -54,33 +54,35 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OWNER_REPO_L: ${{ github.repository }} - OWNER_L: ${{ github.repository_owner }} - REPO_URL_L: https://github.com/${{ github.repository }} - PR_NUMBER_L: ${{ inputs.pr_id }} + OWNER_REPO: ${{ github.repository }} + OWNER: ${{ github.repository_owner }} + PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - owner_prefix="$OWNER_L/" - REPO_L="${OWNER_REPO_L#"$owner_prefix"}" - PR_L=$( + owner_prefix="$OWNER/" + REPO="${OWNER_REPO#"$owner_prefix"}" + PR_JSON=$( gh api \ - -H "Accept: application/vnd.github.text+json" \ + -H "Accept: application/vnd.github.raw+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" + "/repos/$OWNER/$REPO/pulls/$PR_NUMBER" ) - PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") - PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") - PR_BODY_L=$(jq -r '.body_text' <<< "$PR_L") - PR_LABELS_L=$(jq '.labels' <<< "$PR_L") { - echo "REPO=$REPO_L" - echo "OWNER=$OWNER_L" - echo "REPO_URL=$REPO_URL_L" - echo "PR_NUMBER=$PR_NUMBER_L" - echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" - echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" - echo "PR_BODY=$PR_BODY_L" - echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" + echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" + echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" + echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" + echo "JOB_DATA=$(jq ' + { + "repo": .base.repo.name, + "owner": .base.repo.owner.login, + "repoUrl": .base.repo.html_url, + "prNumber": .number, + "prHeadSha": .head.sha, + "prHeadLabel": .head.label, + "prBody": .body, + "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + } + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" @@ -89,7 +91,7 @@ jobs: run: | PRS=$( jq ' - . as $body + .prBody as $body | ( $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") | map({ @@ -109,7 +111,7 @@ jobs: "blocking": $prs, "numBlocking": ( $prs | length), } - ' <<< "$PR_BODY" + ' <<< "$JOB_DATA" ) echo "prs=$PRS" >> "$GITHUB_OUTPUT" @@ -120,12 +122,12 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BLOCKED_PR_DATA=$( - while read -r PR ; do + while read -r pr_data ; do gh api \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ - | jq --arg type "$(jq -r '.type' <<< "$PR")" \ + "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$pr_data")" \ + | jq --arg type "$(jq -r '.type' <<< "$pr_data")" \ ' . | { "type": $type, @@ -180,14 +182,14 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # label pr dependencies with 'blocking' if not already - while read -r PR_DATA ; do - if jq -e 'all(.labels[]; . != "blocking")' <<< "$PR_DATA" > /dev/null ; then - PR=$(jq -r '.number' <<< "$PR_DATA") + while read -r pr_data ; do + if jq -e 'all(.labels[]; . != "blocking")' <<< "$pr_data" > /dev/null ; then + pr=$(jq -r '.number' <<< "$pr_data") gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$PR/labels" \ + "/repos/$OWNER/$REPO/issues/$pr/labels" \ -f "labels[]=blocking" fi done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") @@ -199,20 +201,21 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") # create commit Status, overwrites previous identical context - while read -r PR_DATA ; do + while read -r pr_data ; do DESC=$( - jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$PR_DATA" + jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data" ) gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${OWNER}/${REPO}/statuses/${PR_HEAD_SHA}" \ - -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$PR_DATA")" \ - -f "target_url=$(jq -r '.basePrUrl' <<< "$PR_DATA" )" \ + "/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \ + -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \ + -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ - -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$PR_DATA")" + -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$pr_data")" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") - name: Context Comment @@ -223,13 +226,16 @@ jobs: COMMENT_PATH="$(pwd)/temp_comment_file.txt" touch "$COMMENT_PATH" echo "" > "$COMMENT_PATH" - while read -r PR_DATA ; do - BASE_PR=$(jq '.number' <<< "$PR_DATA") - BASE_REF_NAME=$(jq '.baseRefName' <<< "$PR_DATA") - COMPARE_URL="$REPO_URL/compare/$BASE_REF_NAME...$PR_HEAD_LABEL" - STATUS=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$PR_DATA") - TYPE=$(jq -r '.type' <<< "$PR_DATA") - echo " - $TYPE #$BASE_PR $STATUS [(compare)]($COMPARE_URL)" >> "$COMMENT_PATH" + pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA") + while read -r pr_data ; do + base_pr=$(jq -r '.number' <<< "$pr_data") + base_ref_name=$(jq -r '.baseRefName' <<< "$pr_data") + base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data") + base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data") + compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label" + status=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") + type=$(jq -r '.type' <<< "$pr_data") + echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" From a6a172a1365e94c791ef457e47c7e9e34d693263 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:21:08 -0700 Subject: [PATCH 014/106] ci: ensure block pr scan always returns valid json Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index ed07036af..d6239eedb 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -44,7 +44,7 @@ jobs: "prHeadSha": .head.sha, "prHeadLabel": .head.label, "prBody": .body, - "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" @@ -80,7 +80,7 @@ jobs: "prHeadSha": .head.sha, "prHeadLabel": .head.label, "prBody": .body, - "prLabels": reduce .labels[].name as $l ([]; . + [$l]) + "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" @@ -93,18 +93,24 @@ jobs: jq ' .prBody as $body | ( - $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") - | map({ - "type": "Blocked on", - "number": ( . | tonumber ) - }) + $body | + reduce ( + . | scan("blocked (?:by|on):? #([0-9]+)") + | map({ + "type": "Blocked on", + "number": ( . | tonumber ) + }) + ) as $i ([]; . + [$i]) ) as $bprs | ( - $body | scan("stacked on:? #(?[0-9]+)") - | map({ - "type": "Stacked on", - "number": ( . | tonumber ) - }) + $body | + reduce ( + . | scan("stacked on:? #([0-9]+)") + | map({ + "type": "Stacked on", + "number": ( . | tonumber ) + }) + ) as $i ([]; . + [$i]) ) as $sprs | ($bprs + $sprs) as $prs | { From 0bbf529afb09dfcb9b08facc1b39aa4fe46312d3 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:36:34 -0700 Subject: [PATCH 015/106] ci(blocked_prs): quote json in env file Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index d6239eedb..bd8be64f3 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -28,14 +28,14 @@ jobs: if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: - PR_JSON: ${{ toJSON(github.event.pull_request) }} + PR_JSON: "${{ toJSON(github.event.pull_request) }}" run: | # setup env for the rest of the workflow { echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=$(jq ' + echo "JOB_DATA=\"$(jq ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -46,8 +46,9 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")" + ' <<< "$PR_JSON")\"" } >> "$GITHUB_ENV" + cat $GITHUB_ENV - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' @@ -71,7 +72,7 @@ jobs: echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=$(jq ' + echo "JOB_DATA=\"$(jq ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -82,8 +83,9 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")" + ' <<< "$PR_JSON")\"" } >> "$GITHUB_ENV" + cat $GITHUB_ENV - name: Find Blocked/Stacked PRs in body From eb11cde0f4b2966e4f3b632038f39c91800d3493 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:50:43 -0700 Subject: [PATCH 016/106] ci(blocked_prs): use compact jq output when outputting to ENV or step ouput Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 16 ++++++++-------- .github/workflows/merge_blocking_pr.yml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index bd8be64f3..6afe6e485 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -35,7 +35,7 @@ jobs: echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=\"$(jq ' + echo "JOB_DATA=$(jq -c ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -46,7 +46,7 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")\"" + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" cat $GITHUB_ENV @@ -72,7 +72,7 @@ jobs: echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=\"$(jq ' + echo "JOB_DATA=$(jq -c ' { "repo": .base.repo.name, "owner": .base.repo.owner.login, @@ -83,7 +83,7 @@ jobs: "prBody": .body, "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } - ' <<< "$PR_JSON")\"" + ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" cat $GITHUB_ENV @@ -92,7 +92,7 @@ jobs: id: pr_ids run: | PRS=$( - jq ' + jq -c ' .prBody as $body | ( $body | @@ -135,7 +135,7 @@ jobs: -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$pr_data")" \ - | jq --arg type "$(jq -r '.type' <<< "$pr_data")" \ + | jq -c --arg type "$(jq -r '.type' <<< "$pr_data")" \ ' . | { "type": $type, @@ -150,7 +150,7 @@ jobs: "baseRefName": .head.ref, } ' - done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s + done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -c -s ) echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" @@ -241,7 +241,7 @@ jobs: base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data") base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data") compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label" - status=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") + status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index fd52da295..3123c83b3 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -51,7 +51,7 @@ jobs: ' \ --paginate \ --slurp \ - | jq --argjson pr "${{ github.event.pull_request.number }}" ' + | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' [.[].data.repository.pullRequests.nodes[]] | .[] | select( .bodyText | scan("(?:blocked (?:by|on)|stacked on):? #(?[0-9]+)") | From 14f042540c3b38c3463737a85676d9f6f69c6055 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:11:21 -0700 Subject: [PATCH 017/106] ci(blocking_pr): pass json expressions as env not direct. use internal compost comment action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 152 ++++++++++++++++++++++ .github/workflows/blocked_prs.yml | 31 +++-- .github/workflows/merge_blocking_pr.yml | 3 +- 3 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 .github/actions/create-comment/action.yml diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml new file mode 100644 index 000000000..5ba4bc5c9 --- /dev/null +++ b/.github/actions/create-comment/action.yml @@ -0,0 +1,152 @@ +name: Create Issue Comment +description: Create or updste an issue comment +inputs: + comment: + description: Comment Text + required: true + comment_path: + description: "Path to txt file to be parsed" + required: false + comment_id: + description: "Unique identifier for deduplicating comments" + default: "Create Issue Action" + required: false + issue_number: + description: Local Pull Request/Issue number to work on + required: true + gh_token: + description: gh api access token to use + required: true + repository: + description: the OWNER/REPOSITORY to operate on + required: true + +runs: + using: "composite" + steps: + - name: Generate Comment Text + shell: bash + env: + COMMENT_ID: ${{ inputs.comment_id }} + COMMENT_TEXT: ${{ inputs.comment }} + COMMENT_FILE: ${{ inputs.comment_path }} + run: | + comment_body="${COMMENT_TEXT}" + if [ -f "$COMMENT_FILE" ] ; then + echo "Reading comment file from ${COMMENT_FILE}" + comment_body="${comment_body}$(cat "$COMMENT_FILE")" + fi + echo "COMMENT_BODY=$comment_body" >> "$GITHUB_ENV" + + - name: Get Existing Comment Id + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + ISSUE_NUMBER: ${{ inputs.issue_number }} + REPOSITORY: ${{ inputs.repository }} + COMMENT_ID: ${{ inputs.comment_id }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + data=$( + gh api graphql \ + --paginate \ + -f owner="$owner" \ + -f repo="$repo" \ + -F issue="$ISSUE_NUMBER" \ + -f query=' + query($repo: String!, $owner: String!, $issue: Int!, $endCursor: String) { + repository(name: $repo, owner: $owner) { + issueOrPullRequest(number: $issue) { + ... on Issue { + id + number + comments(first: 100, after: $endCursor) { + nodes { + id + body + } + pageInfo { + hasNextPage + endCursor + } + } + } + ... on PullRequest { + id + number + comments(first: 100, after: $endCursor) { + nodes { + id + body + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + } + } + ' \ + --jq '' | jq -c --arg comment_id "" ' + .[0].data.repository.issueOrPullRequest.id as $id | + [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | + [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | + if ($id_comments | length) > 0 then + { "issueId": $id, "commentId": $id_comments[0].id } + else + { "issueId": $id, "commentId": "" } + end + ' + ) + echo "ISSUE_NODE_ID=$(jq -r '.issueId' <<< "$data")" >> "$GITHUB_ENV" + echo "COMMENT_NODE_ID=$(jq -r '.commentId' <<< "$data")" >> "$GITHUB_ENV" + + - name: Edit Existing Comment + if: env.COMMENT_NODE_ID != '' + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + run: | + gh api graphql \ + -f comment_id="$COMMENT_NODE_ID" \ + -f comment_body="$COMMENT_BODY" \ + -f query=' + mutation($comment_id: ID!, $comment_body: String!) { + updateIssueComment(input: { + id: $comment_id, + body: $comment_body, + }) { + issueComment { + lastEditedAt + } + } + } + ' + + - name: Create Comment + if: env.COMMENT_NODE_ID == '' + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + run: | + gh api graphql \ + -f issue_id="$ISSUE_NODE_ID" \ + -f comment_body="$COMMENT_BODY" \ + -f query=' + mutation ($issue_id: ID!, $comment_body: String!) { + addComment(input: { subjectId: $issue_id, body: $comment_body }) { + commentEdge { + node { + id + } + } + } + } + ' + + + + diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 6afe6e485..d12f139bc 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -55,13 +55,12 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OWNER_REPO: ${{ github.repository }} - OWNER: ${{ github.repository_owner }} + REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - owner_prefix="$OWNER/" - REPO="${OWNER_REPO#"$owner_prefix"}" + OWNER=$(echo "$REPOSITORY" | cut -d '/' -f 1) + REPO=$(echo "$REPOSITORY" | cut -d '/' -f 2) PR_JSON=$( gh api \ -H "Accept: application/vnd.github.raw+json" \ @@ -124,10 +123,11 @@ jobs: echo "prs=$PRS" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data - id: blocked_data + id: blocking_data if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }} run: | BLOCKED_PR_DATA=$( while read -r pr_data ; do @@ -150,14 +150,14 @@ jobs: "baseRefName": .head.ref, } ' - done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -c -s + done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" + echo "data=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" - name: Apply Blocked Label if Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -171,7 +171,7 @@ jobs: - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -188,6 +188,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} run: | # label pr dependencies with 'blocking' if not already while read -r pr_data ; do @@ -200,7 +201,7 @@ jobs: "/repos/$OWNER/$REPO/issues/$pr/labels" \ -f "labels[]=blocking" fi - done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Apply Blocking PR Status Check id: blocked_check @@ -208,6 +209,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} run: | pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") # create commit Status, overwrites previous identical context @@ -224,7 +226,7 @@ jobs: -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$pr_data")" - done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Context Comment id: blocked_comment @@ -250,9 +252,12 @@ jobs: - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: spicyparrot/pr-comment-action@v1.0.0 + uses: ./.github/actions/create-comment with: comment: "### PR Dependencies :pushpin:" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies" + issue_number: ${{ env.PR_NUMBER }} + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index 3123c83b3..8707dd95b 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -67,6 +67,7 @@ jobs: if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEPS: ${{ steps.gather_deps.outputs.deps }} run: | while read -r pr ; do gh api \ @@ -76,5 +77,5 @@ jobs: "/repos/${{ github.repository }}/actions/workflows/blocked_prs.yml/dispatches" \ -f "ref=${{ github.ref_name }}" \ -f "inputs[pr_id]=$pr" - done < <(jq -c '.[].number' <<< "${{steps.gather_deps.outputs.deps}}") + done < <(jq -c '.[].number' <<< "$DEPS") From 1c6ab1f0549e2b2d81ca19f4c83e631bb80b3677 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:28:44 -0700 Subject: [PATCH 018/106] ci(blocked_prs): unnest array of blocking prs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index d12f139bc..440fba16c 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -84,13 +84,11 @@ jobs: } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - cat $GITHUB_ENV - - name: Find Blocked/Stacked PRs in body id: pr_ids run: | - PRS=$( + prs=$( jq -c ' .prBody as $body | ( @@ -101,7 +99,7 @@ jobs: "type": "Blocked on", "number": ( . | tonumber ) }) - ) as $i ([]; . + [$i]) + ) as $i ([]; . + [$i[]]) ) as $bprs | ( $body | @@ -111,7 +109,7 @@ jobs: "type": "Stacked on", "number": ( . | tonumber ) }) - ) as $i ([]; . + [$i]) + ) as $i ([]; . + [$i[]]) ) as $sprs | ($bprs + $sprs) as $prs | { @@ -120,7 +118,7 @@ jobs: } ' <<< "$JOB_DATA" ) - echo "prs=$PRS" >> "$GITHUB_OUTPUT" + echo "prs=$prs" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data id: blocking_data From 6fd70ad095e15b25b47a27575c6cabebfcd67a04 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:35:41 -0700 Subject: [PATCH 019/106] ci(blocked_prs): correct condition to use JOB_DATA from env Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index 440fba16c..b16df71d7 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -155,7 +155,7 @@ jobs: - name: Apply Blocked Label if Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e00d93135f23d3f9dadbdc950099226832cec4bf Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:47:43 -0700 Subject: [PATCH 020/106] ci(blocked_prs): fix blocking_data refrences, add synchronize and reopend events Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index b16df71d7..b6169768c 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -4,7 +4,9 @@ on: pull_request_target: types: - opened + - reopened - edited + - synchronize workflow_dispatch: inputs: pr_id: @@ -151,7 +153,7 @@ jobs: done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) echo "data=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" >> "$GITHUB_OUTPUT" - name: Apply Blocked Label if Missing id: label_blocked @@ -186,7 +188,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | # label pr dependencies with 'blocking' if not already while read -r pr_data ; do @@ -207,7 +209,7 @@ jobs: continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BLOCKING_DATA: ${{ steps.blocking_data.outputs.state }} + BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") # create commit Status, overwrites previous identical context From 79283fd74446ba096004e66d900cf17913163318 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:57:24 -0700 Subject: [PATCH 021/106] ci(blocked_prs): checkout default branch for action access Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index b6169768c..e89cd9dde 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -26,6 +26,10 @@ jobs: checks: write steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} - name: Setup From Pull Request Event if: github.event_name != 'workflow_dispatch' id: pr_event_setup From 0474b03626e6aff15630e4965fe73a5136d21ea0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:01:29 -0700 Subject: [PATCH 022/106] ci(blocked_pr): fix comment action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml index 5ba4bc5c9..52b46d3b2 100644 --- a/.github/actions/create-comment/action.yml +++ b/.github/actions/create-comment/action.yml @@ -51,6 +51,7 @@ runs: data=$( gh api graphql \ --paginate \ + --slurp \ -f owner="$owner" \ -f repo="$repo" \ -F issue="$ISSUE_NUMBER" \ @@ -90,16 +91,16 @@ runs: } } ' \ - --jq '' | jq -c --arg comment_id "" ' - .[0].data.repository.issueOrPullRequest.id as $id | - [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | - [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | - if ($id_comments | length) > 0 then - { "issueId": $id, "commentId": $id_comments[0].id } - else - { "issueId": $id, "commentId": "" } - end - ' + | jq -c --arg comment_id "" ' + .[0].data.repository.issueOrPullRequest.id as $id | + [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | + [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | + if ($id_comments | length) > 0 then + { "issueId": $id, "commentId": $id_comments[0].id } + else + { "issueId": $id, "commentId": "" } + end + ' ) echo "ISSUE_NODE_ID=$(jq -r '.issueId' <<< "$data")" >> "$GITHUB_ENV" echo "COMMENT_NODE_ID=$(jq -r '.commentId' <<< "$data")" >> "$GITHUB_ENV" From 05356eff6939a9511d381f13e476713a188bbdac Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:12:02 -0700 Subject: [PATCH 023/106] ci(blocking_prs): fix generated comment Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked_prs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked_prs.yml index e89cd9dde..b83c01c0c 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked_prs.yml @@ -236,6 +236,8 @@ jobs: id: blocked_comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true + env: + BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" touch "$COMMENT_PATH" @@ -250,7 +252,7 @@ jobs: status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" - done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") + done < <(jq -c '.[]' <<< "$BLOCKING_DATA") echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - name: 💬 PR Comment @@ -258,7 +260,7 @@ jobs: continue-on-error: true uses: ./.github/actions/create-comment with: - comment: "### PR Dependencies :pushpin:" + comment: "

PR Dependencies :pushpin:

" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies" issue_number: ${{ env.PR_NUMBER }} From c2b7b4c82aebc88adf12fda75e2350a0e3fb6a05 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:18:17 -0700 Subject: [PATCH 024/106] ci(blocked_prs): fix multiline comment body in comment action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml index 52b46d3b2..5e5925504 100644 --- a/.github/actions/create-comment/action.yml +++ b/.github/actions/create-comment/action.yml @@ -36,7 +36,9 @@ runs: echo "Reading comment file from ${COMMENT_FILE}" comment_body="${comment_body}$(cat "$COMMENT_FILE")" fi - echo "COMMENT_BODY=$comment_body" >> "$GITHUB_ENV" + echo 'COMMENT_BODY<> "$GITHUB_ENV" + echo "$comment_body" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" - name: Get Existing Comment Id shell: bash From c75ae4170f3570c1577d088470a8eef8346e6638 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:40:33 -0700 Subject: [PATCH 025/106] ci(blocked_prs): fix merge workflow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/merge_blocking_pr.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index 8707dd95b..606ce7769 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -23,11 +23,9 @@ jobs: id: gather_deps env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - owner="${{ github.repository_owner }}" - owner_repo="${{ github.repository }}" - repo="${owner_repo#"${owner}/"}" + owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) + repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) blocked_prs=$( gh api graphql \ -f repo="$repo" \ @@ -52,16 +50,16 @@ jobs: --paginate \ --slurp \ | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' - [.[].data.repository.pullRequests.nodes[]] | .[] | select( + reduce ( .[].data.repository.pullRequests.nodes[] | select( .bodyText | - scan("(?:blocked (?:by|on)|stacked on):? #(?[0-9]+)") | + scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | map(tonumber) | any(.[]; . == $pr) - ) + )) as $i ([]; . + [$i]) ' ) echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps='$(jq -r '. | length' <<< "$blocked_prs")" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - name: Trigger Blocked PP Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 From 0818e7068aaca50b12c7e6a8ec8be30cd18de4ae Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Mar 2025 21:01:58 -0700 Subject: [PATCH 026/106] ci(blocked_pres): ensure merge workflow has actions permission Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/merge_blocking_pr.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge_blocking_pr.yml index 606ce7769..1127d2369 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge_blocking_pr.yml @@ -15,8 +15,7 @@ jobs: permissions: issues: write pull-requests: write - statuses: write - checks: write + actions: write steps: - name: Gather Dependent PRs From 7cbdb80f6ba17e1a85f61ecc408d61799b5c9273 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 10:26:19 -0700 Subject: [PATCH 027/106] ci(label-actions): composit actions to add and delete labels in bulk Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 238 +++++++++++++++++++++ .github/actions/delete-labels/action.yml | 99 +++++++++ .github/workflows/manual-add-label.yml | 44 ++++ .github/workflows/manual-delete-labels.yml | 29 +++ 4 files changed, 410 insertions(+) create mode 100644 .github/actions/add-labels/action.yml create mode 100644 .github/actions/delete-labels/action.yml create mode 100644 .github/workflows/manual-add-label.yml create mode 100644 .github/workflows/manual-delete-labels.yml diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml new file mode 100644 index 000000000..fdc8395b2 --- /dev/null +++ b/.github/actions/add-labels/action.yml @@ -0,0 +1,238 @@ +name: Add Label(s) +description: adds label(s) to labelable +inputs: + gh_token: + description: gh api access token to use + default: ${{ secrets.GITHUB_TOKEN }} + repository: + description: the OWNER/REPOSITORY to operate on + default: ${{ github.repository }} + issues: + description: a single or comma separated list of issue numbers + required: true + labels: + description: a single or comma separated list of labels to apply to all listed issues + required: true + colors: + description: | + A single or comma separated list of colors to create the labels with if needed. + the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` + default-color: + description: default color to create labels with + default: "#D4C5F9" + +runs: + using: "composite" + steps: + - name: Collect Repo Labels + id: collect_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPOSITORY: ${{ inputs.repository }} + LABELS: ${{ inputs.labels }} + COLORS: ${{ inputs.colors }} + DEFAULT_COLOR: ${{ inputs.default-color }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + query=$( + jq -nr \ + --arg labels "$LABELS" \ + ' + (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels + | "query ($owner: String!, $repo: String!) {\n" + + " repository(owner: $owner, name: $repo) {\n" + + " id\n" + + ( + reduce $labels[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": label(name: \"" + $i + "\") {\n" + + " id\n" + + " name\n"+ + " }\n" + ) + ]) + | .[1] + )+ + " }\n" + + "}" + ' + ) + data=$( + gh api graphql \ + -f owner="$owner" \ + -f repo="$repo" \ + -f query="$query" \ + | jq -c \ + --arg labels "$LABELS" \ + --arg colors "$COLORS" \ + --arg defaultColor "$DEFAULT_COLOR" \ + ' + . as $in + | ($labels | split(", *"; null)) as $labels + | ($colors | split(", *"; null)) as $colors + | ( + reduce ( [$labels, $colors] | transpose)[] as $i ( + {}; + .[$i[0]] = ( + if ($i[1] and $i[1] != "") then + $i[1] + else + $defaultColor + end + | if startswith("#") then .[1:] else . end + ) + ) + ) as $colorMap + | ( + reduce ( + $in.data.repository[] + | select( objects | .name as $name | any($labels[]; . == $name ) ) + ) as $i ({}; .[$i.name] = {"id": $i.id}) + ) as $found + | ( + reduce ( + $labels[] + | select( . as $name | $found | has($name) | not ) + ) as $i ({}; .[$i] = {"color": $colorMap[$i]}) + ) as $missing + | { + "repoId": $in.data.repository.id, + "found": $found, + "missing": $missing, + } + ' + ) + echo "found=$(jq -c '.found' <<< "$data")" >> "$GITHUB_OUTPUT" + echo "missing=$(jq -c '.missing' <<< "$data")" >> "$GITHUB_OUTPUT" + echo "repo_id=$(jq -r '.repoId' <<< "$data")" >> "$GITHUB_OUTPUT" + + - name: Collect Item Node IDs + id: collect_ids + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPOSITORY: ${{ inputs.repository }} + ISSUES: ${{ inputs.labels }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + query=$( + jq -nr \ + --arg issues "$ISSUES" \ + ' + (reduce ($issues | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $issues + | + "query ($owner: String!, $repo: String!) {\n" + + " repository(owner: $owner, name: $repo) {\n" + + ( + reduce $issues[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": issueOrPullRequest(number: " + ($i | tostring) +") {\n" + + " ... on Issue {\n" + + " id\n" + + " }\n" + + " ... on PullRequest {\n"+ + " id\n"+ + " }\n" + + " }\n" + ) + ]) + | .[1] + )+ + " }\n" + + "}" + ' + ) + data=$( + gh api graphql \ + -f owner="$owner" \ + -f repo="$repo" \ + -f query="$query" \ + | jq -c 'reduce .data.repository[].id as $i ([]; . + [$i])' + ) + echo "issue_ids=$data" >> "$GITHUB_OUTPUT" + + - name: Create Missing Labels + id: create_missing + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} + LABELS: ${{ steps.collect_labels.outputs.labels }} + MISSING: ${{ steps.collect_labels.outputs.missing }} + run: | + query=$( + jq -nr \ + --argjson labels "$MISSING" \ + --arg repo "$REPO_ID" \ + ' + "mutation {\n" + ( + reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": createLabel(input: {repositoryId: \"" + $repo + + "\", name: \"" + $i[0] + + "\", color: \"" + $i[1].color + + "\"}) {\n" + + " clientMutationId\n" + + " label {\n" + + " id\n" + + " name\n" + + " color\n" + + " }\n" + + " }\n" + ) + ]) + | .[1] + ) + + "}" + ' + ) + data=$( + gh api graphql -f query="$query" \ + | jq --argjson existing "$LABELS" \ + ' + reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) + | . + $existing + ' + ) + lable_ids=$(jq -c '[.[].id]' <<< "$data") + echo "label_ids=$lable_ids" >> "$GITHUB_OUTPUT" + + - name: Apply Labels + id: apply_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + ISSUES: ${{ steps.collect_ids.outputs.issue_ids }} + LABELS: ${{ steps.create_missing.outputs.label_ids }} + run: | + query=$( + jq -nr \ + --argjson labels "$LABELS" \ + --argjson issues "$ISSUES" \ + ' + "mutation {\n" + ( + reduce $issues[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": addLabelsToLabelable(input: {labelableId: \"" + + $i + "\", labelIds: " + ($labels | tojson) + "}) {\n" + + " clientMutationId\n" + + " }\n" + ) + ]) + | .[1] + ) + + "}" + ' + ) + gh api graphql -f query="$query" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml new file mode 100644 index 000000000..9ca921283 --- /dev/null +++ b/.github/actions/delete-labels/action.yml @@ -0,0 +1,99 @@ +name: Delete Label(s) +description: delete Label(s) +inputs: + gh_token: + description: gh api access token to use + default: ${{ secrets.GITHUB_TOKEN }} + repository: + description: the OWNER/REPOSITORY to operate on + default: ${{ github.repository }} + labels: + description: a single or comma separated list of labels to delete + required: true + +runs: + using: "composite" + steps: + - name: Collect Repo Labels + id: collect_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + REPOSITORY: ${{ inputs.repository }} + LABELS: ${{ inputs.labels }} + run: | + owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) + repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + query=$( + jq -nr \ + --arg labels "$LABELS" \ + ' + (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels + | "query ($owner: String!, $repo: String!) {\n" + + " repository(owner: $owner, name: $repo) {\n" + + ( + reduce $labels[] as $i ([0, ""]; [ + .[0] + 1, + .[1] + ( + " _" + (.[0] | tostring) + + ": label(name: \"" + $i + "\") {\n" + + " id\n" + + " name\n"+ + " }\n" + ) + ]) + | .[1] + )+ + " }\n" + + "}" + ' + ) + data=$( + gh api graphql \ + -f owner="$owner" \ + -f repo="$repo" \ + -f query="$query" \ + | jq -c \ + --arg labels "$LABELS" \ + --arg colors "$COLORS" \ + --arg defaultColor "$DEFAULT_COLOR" \ + ' + . as $in + | ($labels | split(", *"; null)) as $labels + | ( + reduce ( + $in.data.repository[] + | select( objects | .name as $name | any($labels[]; . == $name ) ) + ) as $i ({}; .[$i.name] = {"id": $i.id}) + ) as $found + | [.[].id] + ' + ) + echo "label_ids=$data" >>> "$GITHUB_OUTPUT" + + - name: Delete Labels + id: delete_labels + shell: bash + env: + GH_TOKEN: ${{ inputs.gh_token }} + LABELS: ${{ steps.collect_labels.outputs.label_ids }} + run: | + query=$(jq -r ' + . as $in + | ( + "mutation {\n" + ( + reduce $in[] as $id ([0, ""]; [ + .[0] + 1 , + .[1] + ( + " _" + (.[0] | tostring) + ": deleteLabel(input: {id: \"" + $id + "\"}) {\n" + + " clientMutationId\n" + + " }\n" + ) + ]) + | .[1] + ) + + "}" + ) + ' <<< "$LABELS" + ) + gh api graphql -f query="$query" diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml new file mode 100644 index 000000000..e6c768a7d --- /dev/null +++ b/.github/workflows/manual-add-label.yml @@ -0,0 +1,44 @@ +name: Manual workflow to apply labels in bulk + +on: + workflow_dispatch: + inputs: + issues: + description: a single or comma separated list of issue numbers + required: true + type: string + labels: + description: a single or comma separated list of labels to apply to all listed issues + required: true + type: string + colors: + description: | + A single or comma separated list of colors to create the labels with if needed. + the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default_color` + type: string + default-color: + description: default color to create labels with + default: "#D4C5F9" + type: string + +jobs: + apply-labels: + name: Apply Labels + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Run Label Action + uses: ./.github/actions/add-labels + with: + issues: ${{ inputs.issues }} + labels: ${{ inputs.labels }} + colors: ${{ inputs.colors }} + default-color: ${{ inputs.default-color }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml new file mode 100644 index 000000000..b575d59d8 --- /dev/null +++ b/.github/workflows/manual-delete-labels.yml @@ -0,0 +1,29 @@ + +name: Manual workflow to delete labels in bulk + +on: + workflow_dispatch: + inputs: + labels: + description: a single or comma separated list of labels to delete + required: true + type: string + +jobs: + delete-labels: + name: Delete Labels + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Run Label Action + uses: ./.github/actions/delete-labels + with: + labels: ${{ inputs.labels }} From da3f378d5d52b56ef5360d72e2fec7f466ec6190 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 10:35:35 -0700 Subject: [PATCH 028/106] ci(label-actions): actions can't directly access secrets Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 23 ++++++++++------------ .github/actions/delete-labels/action.yml | 6 +++--- .github/workflows/manual-add-label.yml | 3 ++- .github/workflows/manual-delete-labels.yml | 3 ++- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml index fdc8395b2..9adb87945 100644 --- a/.github/actions/add-labels/action.yml +++ b/.github/actions/add-labels/action.yml @@ -3,7 +3,7 @@ description: adds label(s) to labelable inputs: gh_token: description: gh api access token to use - default: ${{ secrets.GITHUB_TOKEN }} + required: true repository: description: the OWNER/REPOSITORY to operate on default: ${{ github.repository }} @@ -116,7 +116,7 @@ runs: env: GH_TOKEN: ${{ inputs.gh_token }} REPOSITORY: ${{ inputs.repository }} - ISSUES: ${{ inputs.labels }} + ISSUES: ${{ inputs.issues }} run: | owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) @@ -164,14 +164,13 @@ runs: env: GH_TOKEN: ${{ inputs.gh_token }} REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} - LABELS: ${{ steps.collect_labels.outputs.labels }} + EXISTING: ${{ steps.collect_labels.outputs.found }} MISSING: ${{ steps.collect_labels.outputs.missing }} run: | query=$( jq -nr \ --argjson labels "$MISSING" \ - --arg repo "$REPO_ID" \ - ' + --arg repo "$REPO_ID" ' "mutation {\n" + ( reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ .[0] + 1, @@ -196,15 +195,13 @@ runs: ' ) data=$( - gh api graphql -f query="$query" \ - | jq --argjson existing "$LABELS" \ - ' - reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) - | . + $existing - ' + gh api graphql -f query="$query" | jq --argjson existing "$EXISTING" ' + reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) + | . + $existing + ' ) - lable_ids=$(jq -c '[.[].id]' <<< "$data") - echo "label_ids=$lable_ids" >> "$GITHUB_OUTPUT" + label_ids=$(jq -c '[.[].id]' <<< "$data") + echo "label_ids=$label_ids" >> "$GITHUB_OUTPUT" - name: Apply Labels id: apply_labels diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml index 9ca921283..25a8d5fad 100644 --- a/.github/actions/delete-labels/action.yml +++ b/.github/actions/delete-labels/action.yml @@ -3,7 +3,7 @@ description: delete Label(s) inputs: gh_token: description: gh api access token to use - default: ${{ secrets.GITHUB_TOKEN }} + required: true repository: description: the OWNER/REPOSITORY to operate on default: ${{ github.repository }} @@ -66,10 +66,10 @@ runs: | select( objects | .name as $name | any($labels[]; . == $name ) ) ) as $i ({}; .[$i.name] = {"id": $i.id}) ) as $found - | [.[].id] + | [$found[].id] ' ) - echo "label_ids=$data" >>> "$GITHUB_OUTPUT" + echo "label_ids=$data" >> "$GITHUB_OUTPUT" - name: Delete Labels id: delete_labels diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml index e6c768a7d..8a06ae213 100644 --- a/.github/workflows/manual-add-label.yml +++ b/.github/workflows/manual-add-label.yml @@ -1,4 +1,4 @@ -name: Manual workflow to apply labels in bulk +name: Apply labels in bulk on: workflow_dispatch: @@ -38,6 +38,7 @@ jobs: - name: Run Label Action uses: ./.github/actions/add-labels with: + gh_token: ${{ secrets.GITHUB_TOKEN }} issues: ${{ inputs.issues }} labels: ${{ inputs.labels }} colors: ${{ inputs.colors }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml index b575d59d8..e8c4b985d 100644 --- a/.github/workflows/manual-delete-labels.yml +++ b/.github/workflows/manual-delete-labels.yml @@ -1,5 +1,5 @@ -name: Manual workflow to delete labels in bulk +name: Delete labels in bulk on: workflow_dispatch: @@ -26,4 +26,5 @@ jobs: - name: Run Label Action uses: ./.github/actions/delete-labels with: + gh_token: ${{ secrets.GITHUB_TOKEN }} labels: ${{ inputs.labels }} From b42b453f76f5381d39de3420f7e18066b8930640 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 12:58:42 -0700 Subject: [PATCH 029/106] ci(blocking-lables): cleanup lables with a pr closes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 2 +- .github/actions/create-comment/action.yml | 4 +- .github/actions/delete-labels/action.yml | 2 + .../{blocked_prs.yml => blocked-prs.yml} | 68 ++++++------------- .github/workflows/manual-add-label.yml | 2 +- .github/workflows/manual-delete-labels.yml | 2 +- ..._blocking_pr.yml => merge-blocking_pr.yml} | 66 ++++++++++++------ 7 files changed, 74 insertions(+), 72 deletions(-) rename .github/workflows/{blocked_prs.yml => blocked-prs.yml} (80%) rename .github/workflows/{merge_blocking_pr.yml => merge-blocking_pr.yml} (52%) diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml index 9adb87945..82b237497 100644 --- a/.github/actions/add-labels/action.yml +++ b/.github/actions/add-labels/action.yml @@ -19,7 +19,7 @@ inputs: the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` default-color: description: default color to create labels with - default: "#D4C5F9" + default: "#ffffff" runs: using: "composite" diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml index 5e5925504..484f1e5af 100644 --- a/.github/actions/create-comment/action.yml +++ b/.github/actions/create-comment/action.yml @@ -1,5 +1,5 @@ name: Create Issue Comment -description: Create or updste an issue comment +description: Create or update an issue comment inputs: comment: description: Comment Text @@ -19,7 +19,7 @@ inputs: required: true repository: description: the OWNER/REPOSITORY to operate on - required: true + default: ${{ github.repository }} runs: using: "composite" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml index 25a8d5fad..65d3ba3d4 100644 --- a/.github/actions/delete-labels/action.yml +++ b/.github/actions/delete-labels/action.yml @@ -70,10 +70,12 @@ runs: ' ) echo "label_ids=$data" >> "$GITHUB_OUTPUT" + echo "num_labels=$(jq -r 'length' <<< "$data")" >> "$GITHUB_OUTPUT" - name: Delete Labels id: delete_labels shell: bash + if: fromJSON( steps.collect_labels.outputs.num_labels ) > 0 env: GH_TOKEN: ${{ inputs.gh_token }} LABELS: ${{ steps.collect_labels.outputs.label_ids }} diff --git a/.github/workflows/blocked_prs.yml b/.github/workflows/blocked-prs.yml similarity index 80% rename from .github/workflows/blocked_prs.yml rename to .github/workflows/blocked-prs.yml index b83c01c0c..3201e5f7a 100644 --- a/.github/workflows/blocked_prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -54,7 +54,6 @@ jobs: } ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - cat $GITHUB_ENV - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' @@ -133,7 +132,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }} run: | - BLOCKED_PR_DATA=$( + blocked_pr_data=$( while read -r pr_data ; do gh api \ -H "Accept: application/vnd.github+json" \ @@ -156,56 +155,33 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - echo "data=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" >> "$GITHUB_OUTPUT" + blocked_by_labels=$(jq -c 'map( select( .merged | not ) | "blocked-by:" + (.number | tostring))' <<< "$blocked_pr_data" ) + echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" + echo "blocked_by_labels=$blocked_by_labels" >> "$GITHUB_OUTPUT" + echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" - - name: Apply Blocked Label if Missing + - name: Apply Blocked by Labels id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && steps.blocking_data.outputs.blocked_by_labels != '' continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" \ - -f "labels[]=blocked" + uses: ./.github/actions/add-labels + with: + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} + issues: ${{ env.PR_NUMBER }} + labels: ${{ join( fromJSON(steps.blocking_data.outputs.blocked_by_labels), ',' ) }} - - name: Remove 'blocked' Label if All Dependencies Are Merged - id: unlabel_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) - continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api \ - --method DELETE \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels/blocked" - - - name: Apply 'blocking' Label to Dependencies if Missing + - name: Apply 'blocking:' Label to Unmerged Dependencies id: label_blocking if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} - run: | - # label pr dependencies with 'blocking' if not already - while read -r pr_data ; do - if jq -e 'all(.labels[]; . != "blocking")' <<< "$pr_data" > /dev/null ; then - pr=$(jq -r '.number' <<< "$pr_data") - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/issues/$pr/labels" \ - -f "labels[]=blocking" - fi - done < <(jq -c '.[]' <<< "$BLOCKING_DATA") + uses: ./.github/actions/add-labels + with: + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} + issues: ${{ join( fromJSON(steps.blocking_data.outputs.current_blocking) , ',' ) }} + labels: ${{ format( 'blocking:{0}', env.PR_NUMBER ) }} - name: Apply Blocking PR Status Check id: blocked_check @@ -229,7 +205,7 @@ jobs: -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \ -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ - -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$pr_data")" + -f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Context Comment diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml index 8a06ae213..fdb69e389 100644 --- a/.github/workflows/manual-add-label.yml +++ b/.github/workflows/manual-add-label.yml @@ -1,4 +1,4 @@ -name: Apply labels in bulk +name: "Manual: Apply Labels in Bulk" on: workflow_dispatch: diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml index e8c4b985d..300c0b52a 100644 --- a/.github/workflows/manual-delete-labels.yml +++ b/.github/workflows/manual-delete-labels.yml @@ -1,5 +1,5 @@ -name: Delete labels in bulk +name: "Manual: Delete labels in bulk" on: workflow_dispatch: diff --git a/.github/workflows/merge_blocking_pr.yml b/.github/workflows/merge-blocking_pr.yml similarity index 52% rename from .github/workflows/merge_blocking_pr.yml rename to .github/workflows/merge-blocking_pr.yml index 1127d2369..a3f40a1a8 100644 --- a/.github/workflows/merge_blocking_pr.yml +++ b/.github/workflows/merge-blocking_pr.yml @@ -6,11 +6,13 @@ on: - closed jobs: - update_blocked_status: + update-blocked-status: name: Update Blocked Status runs-on: ubuntu-latest - if: github.event.pull_request.merged == true + # a pr that was a `blocking:` label was merged. + # find the open pr's it was blocked by and trigger a refresh of their state + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking:" ) permissions: issues: write @@ -22,30 +24,32 @@ jobs: id: gather_deps env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) + query=" + query(\$repo: String!, \$owner: String!, \$endCursor: String) { + repository(name: \$repo, owner: \$owner) { + pullRequests(first: 100, after: \$endCursor, states: [OPEN], labels: [\"blocked-by:${PR_NUMBER}\"]) { + nodes { + number + bodyText + merged + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + " blocked_prs=$( gh api graphql \ -f repo="$repo" \ -f owner="$owner" \ - -f query=' - query($repo: String!, $owner: String!, $endCursor: String) { - repository(name: $repo, owner: $owner) { - pullRequests(first: 100, after: $endCursor, states: [OPEN], labels: ["blocked"]) { - nodes { - number - bodyText - merged - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - ' \ + -f query="$query" \ --paginate \ --slurp \ | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' @@ -60,7 +64,7 @@ jobs: echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - - name: Trigger Blocked PP Workflows for Dependants + - name: Trigger Blocked PR Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -71,8 +75,28 @@ jobs: --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${{ github.repository }}/actions/workflows/blocked_prs.yml/dispatches" \ + "/repos/${{ github.repository }}/actions/workflows/blocked-prs.yml/dispatches" \ -f "ref=${{ github.ref_name }}" \ -f "inputs[pr_id]=$pr" done < <(jq -c '.[].number' <<< "$DEPS") + label-cleanup: + # this pr is closed, no need for these anymore + name: "Cleanup Related blocking: or blocked-by: labels" + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Delete Related Labels + uses: ./.github/actions/delete-labels + with: + repository: ${{ github.repository }} + gh_token: ${{ secrets.GITHUB_TOKEN }} + labels: ${{ format('blocking:{0},blocked-by:{0}', github.event.pull_request.number ) }} From e8e81a762717b02af463baee61bda04a7d80c0d2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:56:46 -0700 Subject: [PATCH 030/106] use `basename` and `dirname` to split `owner/repo` Co-authored-by: Seth Flynn Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml index 82b237497..0f36aff91 100644 --- a/.github/actions/add-labels/action.yml +++ b/.github/actions/add-labels/action.yml @@ -34,8 +34,8 @@ runs: COLORS: ${{ inputs.colors }} DEFAULT_COLOR: ${{ inputs.default-color }} run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + owner="$(dirname "$REPOSITORY")" + repo="$(basename "$REPOSITORY")" query=$( jq -nr \ --arg labels "$LABELS" \ @@ -118,8 +118,8 @@ runs: REPOSITORY: ${{ inputs.repository }} ISSUES: ${{ inputs.issues }} run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) + owner="$(dirname "$REPOSITORY")" + repo="$(basename "$REPOSITORY")" query=$( jq -nr \ --arg issues "$ISSUES" \ From 72aee5c9f63a7ba710dcd46111f7955b10470029 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:02:18 -0700 Subject: [PATCH 031/106] ci(blocked-pr): go back to single labels use gh cli actions directly insead of api where possible Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/add-labels/action.yml | 235 --------------------- .github/actions/delete-labels/action.yml | 101 --------- .github/workflows/blocked-prs.yml | 44 ++-- .github/workflows/manual-add-label.yml | 45 ---- .github/workflows/manual-delete-labels.yml | 30 --- .github/workflows/merge-blocking-pr.yml | 52 +++++ .github/workflows/merge-blocking_pr.yml | 102 --------- 7 files changed, 76 insertions(+), 533 deletions(-) delete mode 100644 .github/actions/add-labels/action.yml delete mode 100644 .github/actions/delete-labels/action.yml delete mode 100644 .github/workflows/manual-add-label.yml delete mode 100644 .github/workflows/manual-delete-labels.yml create mode 100644 .github/workflows/merge-blocking-pr.yml delete mode 100644 .github/workflows/merge-blocking_pr.yml diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml deleted file mode 100644 index 0f36aff91..000000000 --- a/.github/actions/add-labels/action.yml +++ /dev/null @@ -1,235 +0,0 @@ -name: Add Label(s) -description: adds label(s) to labelable -inputs: - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - issues: - description: a single or comma separated list of issue numbers - required: true - labels: - description: a single or comma separated list of labels to apply to all listed issues - required: true - colors: - description: | - A single or comma separated list of colors to create the labels with if needed. - the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` - default-color: - description: default color to create labels with - default: "#ffffff" - -runs: - using: "composite" - steps: - - name: Collect Repo Labels - id: collect_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - LABELS: ${{ inputs.labels }} - COLORS: ${{ inputs.colors }} - DEFAULT_COLOR: ${{ inputs.default-color }} - run: | - owner="$(dirname "$REPOSITORY")" - repo="$(basename "$REPOSITORY")" - query=$( - jq -nr \ - --arg labels "$LABELS" \ - ' - (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels - | "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - " id\n" + - ( - reduce $labels[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": label(name: \"" + $i + "\") {\n" + - " id\n" + - " name\n"+ - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c \ - --arg labels "$LABELS" \ - --arg colors "$COLORS" \ - --arg defaultColor "$DEFAULT_COLOR" \ - ' - . as $in - | ($labels | split(", *"; null)) as $labels - | ($colors | split(", *"; null)) as $colors - | ( - reduce ( [$labels, $colors] | transpose)[] as $i ( - {}; - .[$i[0]] = ( - if ($i[1] and $i[1] != "") then - $i[1] - else - $defaultColor - end - | if startswith("#") then .[1:] else . end - ) - ) - ) as $colorMap - | ( - reduce ( - $in.data.repository[] - | select( objects | .name as $name | any($labels[]; . == $name ) ) - ) as $i ({}; .[$i.name] = {"id": $i.id}) - ) as $found - | ( - reduce ( - $labels[] - | select( . as $name | $found | has($name) | not ) - ) as $i ({}; .[$i] = {"color": $colorMap[$i]}) - ) as $missing - | { - "repoId": $in.data.repository.id, - "found": $found, - "missing": $missing, - } - ' - ) - echo "found=$(jq -c '.found' <<< "$data")" >> "$GITHUB_OUTPUT" - echo "missing=$(jq -c '.missing' <<< "$data")" >> "$GITHUB_OUTPUT" - echo "repo_id=$(jq -r '.repoId' <<< "$data")" >> "$GITHUB_OUTPUT" - - - name: Collect Item Node IDs - id: collect_ids - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - ISSUES: ${{ inputs.issues }} - run: | - owner="$(dirname "$REPOSITORY")" - repo="$(basename "$REPOSITORY")" - query=$( - jq -nr \ - --arg issues "$ISSUES" \ - ' - (reduce ($issues | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $issues - | - "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - ( - reduce $issues[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": issueOrPullRequest(number: " + ($i | tostring) +") {\n" + - " ... on Issue {\n" + - " id\n" + - " }\n" + - " ... on PullRequest {\n"+ - " id\n"+ - " }\n" + - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c 'reduce .data.repository[].id as $i ([]; . + [$i])' - ) - echo "issue_ids=$data" >> "$GITHUB_OUTPUT" - - - name: Create Missing Labels - id: create_missing - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} - EXISTING: ${{ steps.collect_labels.outputs.found }} - MISSING: ${{ steps.collect_labels.outputs.missing }} - run: | - query=$( - jq -nr \ - --argjson labels "$MISSING" \ - --arg repo "$REPO_ID" ' - "mutation {\n" + ( - reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": createLabel(input: {repositoryId: \"" + $repo + - "\", name: \"" + $i[0] + - "\", color: \"" + $i[1].color + - "\"}) {\n" + - " clientMutationId\n" + - " label {\n" + - " id\n" + - " name\n" + - " color\n" + - " }\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ' - ) - data=$( - gh api graphql -f query="$query" | jq --argjson existing "$EXISTING" ' - reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) - | . + $existing - ' - ) - label_ids=$(jq -c '[.[].id]' <<< "$data") - echo "label_ids=$label_ids" >> "$GITHUB_OUTPUT" - - - name: Apply Labels - id: apply_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - ISSUES: ${{ steps.collect_ids.outputs.issue_ids }} - LABELS: ${{ steps.create_missing.outputs.label_ids }} - run: | - query=$( - jq -nr \ - --argjson labels "$LABELS" \ - --argjson issues "$ISSUES" \ - ' - "mutation {\n" + ( - reduce $issues[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": addLabelsToLabelable(input: {labelableId: \"" + - $i + "\", labelIds: " + ($labels | tojson) + "}) {\n" + - " clientMutationId\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ' - ) - gh api graphql -f query="$query" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml deleted file mode 100644 index 65d3ba3d4..000000000 --- a/.github/actions/delete-labels/action.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Delete Label(s) -description: delete Label(s) -inputs: - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - labels: - description: a single or comma separated list of labels to delete - required: true - -runs: - using: "composite" - steps: - - name: Collect Repo Labels - id: collect_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - LABELS: ${{ inputs.labels }} - run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) - query=$( - jq -nr \ - --arg labels "$LABELS" \ - ' - (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels - | "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - ( - reduce $labels[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": label(name: \"" + $i + "\") {\n" + - " id\n" + - " name\n"+ - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c \ - --arg labels "$LABELS" \ - --arg colors "$COLORS" \ - --arg defaultColor "$DEFAULT_COLOR" \ - ' - . as $in - | ($labels | split(", *"; null)) as $labels - | ( - reduce ( - $in.data.repository[] - | select( objects | .name as $name | any($labels[]; . == $name ) ) - ) as $i ({}; .[$i.name] = {"id": $i.id}) - ) as $found - | [$found[].id] - ' - ) - echo "label_ids=$data" >> "$GITHUB_OUTPUT" - echo "num_labels=$(jq -r 'length' <<< "$data")" >> "$GITHUB_OUTPUT" - - - name: Delete Labels - id: delete_labels - shell: bash - if: fromJSON( steps.collect_labels.outputs.num_labels ) > 0 - env: - GH_TOKEN: ${{ inputs.gh_token }} - LABELS: ${{ steps.collect_labels.outputs.label_ids }} - run: | - query=$(jq -r ' - . as $in - | ( - "mutation {\n" + ( - reduce $in[] as $id ([0, ""]; [ - .[0] + 1 , - .[1] + ( - " _" + (.[0] | tostring) + ": deleteLabel(input: {id: \"" + $id + "\"}) {\n" + - " clientMutationId\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ) - ' <<< "$LABELS" - ) - gh api graphql -f query="$query" diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 3201e5f7a..21e73660d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -60,12 +60,11 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - OWNER=$(echo "$REPOSITORY" | cut -d '/' -f 1) - REPO=$(echo "$REPOSITORY" | cut -d '/' -f 2) + OWNER=$(dirname "${{ github.repository }}") + REPO=$(basename "${{ github.repository }}") PR_JSON=$( gh api \ -H "Accept: application/vnd.github.raw+json" \ @@ -155,33 +154,38 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - blocked_by_labels=$(jq -c 'map( select( .merged | not ) | "blocked-by:" + (.number | tostring))' <<< "$blocked_pr_data" ) echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" - echo "blocked_by_labels=$blocked_by_labels" >> "$GITHUB_OUTPUT" echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" - - name: Apply Blocked by Labels + - name: Add 'blocked' Label is Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && steps.blocking_data.outputs.blocked_by_labels != '' + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true - uses: ./.github/actions/add-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ env.PR_NUMBER }} - labels: ${{ join( fromJSON(steps.blocking_data.outputs.blocked_by_labels), ',' ) }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER - - name: Apply 'blocking:' Label to Unmerged Dependencies + - name: Remove 'blocked' Label if All Dependencies Are Merged + id: unlabel_blocked + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER + + - name: Apply 'blocking' Label to Unmerged Dependencies id: label_blocking if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: ./.github/actions/add-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ join( fromJSON(steps.blocking_data.outputs.current_blocking) , ',' ) }} - labels: ${{ format( 'blocking:{0}', env.PR_NUMBER ) }} + env: + BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }} + run: | + while read -r pr ; do + gh -R ${{ github.repository }} issue edit --add-label 'blocking' "$pr" || true + done < <(jq -c '.[]' <<< "$BLOCKING_ISSUES") - name: Apply Blocking PR Status Check id: blocked_check diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml deleted file mode 100644 index fdb69e389..000000000 --- a/.github/workflows/manual-add-label.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: "Manual: Apply Labels in Bulk" - -on: - workflow_dispatch: - inputs: - issues: - description: a single or comma separated list of issue numbers - required: true - type: string - labels: - description: a single or comma separated list of labels to apply to all listed issues - required: true - type: string - colors: - description: | - A single or comma separated list of colors to create the labels with if needed. - the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default_color` - type: string - default-color: - description: default color to create labels with - default: "#D4C5F9" - type: string - -jobs: - apply-labels: - name: Apply Labels - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Run Label Action - uses: ./.github/actions/add-labels - with: - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ inputs.issues }} - labels: ${{ inputs.labels }} - colors: ${{ inputs.colors }} - default-color: ${{ inputs.default-color }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml deleted file mode 100644 index 300c0b52a..000000000 --- a/.github/workflows/manual-delete-labels.yml +++ /dev/null @@ -1,30 +0,0 @@ - -name: "Manual: Delete labels in bulk" - -on: - workflow_dispatch: - inputs: - labels: - description: a single or comma separated list of labels to delete - required: true - type: string - -jobs: - delete-labels: - name: Delete Labels - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Run Label Action - uses: ./.github/actions/delete-labels - with: - gh_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ inputs.labels }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml new file mode 100644 index 000000000..6db3483d4 --- /dev/null +++ b/.github/workflows/merge-blocking-pr.yml @@ -0,0 +1,52 @@ +name: Merged Blocking Pull Request Automation + +on: + pull_request: + types: + - closed + +jobs: + update-blocked-status: + name: Update Blocked Status + runs-on: ubuntu-latest + + # a pr that was a `blocking:` label was merged. + # find the open pr's it was blocked by and trigger a refresh of their state + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking" ) + + permissions: + issues: write + pull-requests: write + actions: write + + steps: + - name: Gather Dependent PRs + id: gather_deps + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + blocked_prs=$( + gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \ + | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' + reduce ( .[] | select( + .body | + scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | + map(tonumber) | + any(.[]; . == $pr) + )) as $i ([]; . + [$i]) + ' + ) + echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" + + - name: Trigger Blocked PR Workflows for Dependants + if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEPS: ${{ steps.gather_deps.outputs.deps }} + run: | + while read -r pr ; do + gh -R ${{ github.repository }} workflow run 'blocked-prs.yml' -r "${{ github.ref_name }}" -f pr_id="$pr" + done < <(jq -c '.[].number' <<< "$DEPS") + diff --git a/.github/workflows/merge-blocking_pr.yml b/.github/workflows/merge-blocking_pr.yml deleted file mode 100644 index a3f40a1a8..000000000 --- a/.github/workflows/merge-blocking_pr.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Merged Blocking Pull Request Automation - -on: - pull_request: - types: - - closed - -jobs: - update-blocked-status: - name: Update Blocked Status - runs-on: ubuntu-latest - - # a pr that was a `blocking:` label was merged. - # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking:" ) - - permissions: - issues: write - pull-requests: write - actions: write - - steps: - - name: Gather Dependent PRs - id: gather_deps - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) - repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) - query=" - query(\$repo: String!, \$owner: String!, \$endCursor: String) { - repository(name: \$repo, owner: \$owner) { - pullRequests(first: 100, after: \$endCursor, states: [OPEN], labels: [\"blocked-by:${PR_NUMBER}\"]) { - nodes { - number - bodyText - merged - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - " - blocked_prs=$( - gh api graphql \ - -f repo="$repo" \ - -f owner="$owner" \ - -f query="$query" \ - --paginate \ - --slurp \ - | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' - reduce ( .[].data.repository.pullRequests.nodes[] | select( - .bodyText | - scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | - map(tonumber) | - any(.[]; . == $pr) - )) as $i ([]; . + [$i]) - ' - ) - echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - - - name: Trigger Blocked PR Workflows for Dependants - if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEPS: ${{ steps.gather_deps.outputs.deps }} - run: | - while read -r pr ; do - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${{ github.repository }}/actions/workflows/blocked-prs.yml/dispatches" \ - -f "ref=${{ github.ref_name }}" \ - -f "inputs[pr_id]=$pr" - done < <(jq -c '.[].number' <<< "$DEPS") - - label-cleanup: - # this pr is closed, no need for these anymore - name: "Cleanup Related blocking: or blocked-by: labels" - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Delete Related Labels - uses: ./.github/actions/delete-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ format('blocking:{0},blocked-by:{0}', github.event.pull_request.number ) }} From e28dd30d87cd9779fc589c51b1519090c3e33ff5 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 14 Mar 2025 19:26:27 -0700 Subject: [PATCH 032/106] ci(blocked_pr): merge env setup for dispatch and pull_request events Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 60 +++++++++---------------- .github/workflows/merge-blocking-pr.yml | 2 +- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 21e73660d..96a8cfc03 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -30,13 +30,30 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - - name: Setup From Pull Request Event - if: github.event_name != 'workflow_dispatch' - id: pr_event_setup + + - name: Setup From Dispatch Event + if: github.event_name == 'workflow_dispatch' + id: dispatch_event_setup env: - PR_JSON: "${{ toJSON(github.event.pull_request) }}" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow + OWNER=$(dirname "${{ github.repository }}") + REPO=$(basename "${{ github.repository }}") + PR_JSON=$( + gh api \ + -H "Accept: application/vnd.github.raw+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/pulls/$PR_NUMBER" + ) + echo "PR_JSON=$PR_JSON" >> "$GITHUB_ENV" + + - name: Setup Environment + id: env_setup + run: | + # setup env for the rest of the workflow + PR_JSON=${PR_JSON:-'${{ toJSON(github.event.pull_request) }}'} { echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" @@ -55,39 +72,6 @@ jobs: ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - - name: Setup From Dispatch Event - if: github.event_name == 'workflow_dispatch' - id: dispatch_event_setup - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ inputs.pr_id }} - run: | - # setup env for the rest of the workflow - OWNER=$(dirname "${{ github.repository }}") - REPO=$(basename "${{ github.repository }}") - PR_JSON=$( - gh api \ - -H "Accept: application/vnd.github.raw+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/$OWNER/$REPO/pulls/$PR_NUMBER" - ) - { - echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" - echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" - echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")" - echo "JOB_DATA=$(jq -c ' - { - "repo": .base.repo.name, - "owner": .base.repo.owner.login, - "repoUrl": .base.repo.html_url, - "prNumber": .number, - "prHeadSha": .head.sha, - "prHeadLabel": .head.label, - "prBody": .body, - "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) - } - ' <<< "$PR_JSON")" - } >> "$GITHUB_ENV" - name: Find Blocked/Stacked PRs in body id: pr_ids @@ -160,7 +144,7 @@ jobs: - name: Add 'blocked' Label is Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) + if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 6db3483d4..4ac85fa5f 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -12,7 +12,7 @@ jobs: # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking" ) + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' ) permissions: issues: write From d5d83bf55c1bce91357e46714a265e79be012895 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 18 Mar 2025 16:48:18 +0200 Subject: [PATCH 033/106] fix no cape select Signed-off-by: Trial97 --- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 2127fc9cc..3bc0bc2d9 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -223,6 +223,8 @@ void SkinManageDialog::on_capeCombo_currentIndexChanged(int index) auto cape = m_capes.value(id.toString(), {}); if (!cape.isNull()) { m_ui->capeImage->setPixmap(previewCape(cape).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation)); + } else { + m_ui->capeImage->clear(); } m_skinPreview->updateCape(cape); if (auto skin = getSelectedSkin(); skin) { @@ -522,6 +524,8 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event) auto cape = m_capes.value(id.toString(), {}); if (!cape.isNull()) { m_ui->capeImage->setPixmap(previewCape(cape).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation)); + } else { + m_ui->capeImage->clear(); } } From 6bda537633d0e3b9b2a5d4e6971b792ddfc23826 Mon Sep 17 00:00:00 2001 From: hanlie <48323966+HanlieChina@users.noreply.github.com> Date: Wed, 19 Mar 2025 00:24:58 +0800 Subject: [PATCH 034/106] Improvements to modlist export Added .replace("{mod_id}", modID) Signed-off-by: hanlie <48323966+HanlieChina@users.noreply.github.com> --- launcher/minecraft/mod/Mod.cpp | 9 +++++++++ launcher/minecraft/mod/Mod.h | 1 + launcher/minecraft/mod/Resource.h | 4 ++++ launcher/modplatform/helpers/ExportToModList.cpp | 2 ++ 4 files changed, 16 insertions(+) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 50fb45d77..99fc39ce0 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -138,6 +138,15 @@ auto Mod::name() const -> QString return Resource::name(); } +auto Mod::mod_id() const -> QString +{ + auto d_mod_id = details().mod_id; + if (!d_mod_id.isEmpty()) + return d_mod_id; + + return Resource::name(); +} + auto Mod::version() const -> QString { return details().version; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 8a352c66c..deb1859de 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -61,6 +61,7 @@ class Mod : public Resource { auto details() const -> const ModDetails&; auto name() const -> QString override; + auto mod_id() const -> QString; auto version() const -> QString; auto homepage() const -> QString override; auto description() const -> QString; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 42463fe8f..16d8c2a89 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -152,6 +152,9 @@ class Resource : public QObject { [[nodiscard]] bool isMoreThanOneHardLink() const; + [[nodiscard]] auto mod_id() const -> QString { return m_mod_id; } + void setModId(const QString& modId) { m_mod_id = modId; } + protected: /* The file corresponding to this resource. */ QFileInfo m_file_info; @@ -162,6 +165,7 @@ class Resource : public QObject { QString m_internal_id; /* Name as reported via the file name. In the absence of a better name, this is shown to the user. */ QString m_name; + QString m_mod_id; /* The type of file we're dealing with. */ ResourceType m_type = ResourceType::UNKNOWN; diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index bddc7e320..678f89917 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -203,6 +203,7 @@ QString exportToModList(QList mods, QString lineTemplate) for (auto mod : mods) { auto meta = mod->metadata(); auto modName = mod->name(); + auto modID = mod->mod_id(); auto url = mod->homepage(); auto ver = mod->version(); if (ver.isEmpty() && meta != nullptr) @@ -211,6 +212,7 @@ QString exportToModList(QList mods, QString lineTemplate) auto filename = mod->fileinfo().fileName(); lines << QString(lineTemplate) .replace("{name}", modName) + .replace("{mod_id}", modID) .replace("{url}", url) .replace("{version}", ver) .replace("{authors}", authors) From 4c2182e086cc43c39e3e3dbba14a9c67f090d8cc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 18:59:33 +0000 Subject: [PATCH 035/106] chore(deps): update actions/cache action to v4.2.3 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b71d642a..c5e459914 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -170,7 +170,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v4.2.2 + uses: actions/cache@v4.2.3 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From 187728c1f238d55d613796ae6139ffd122c5b80f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:19:13 -0700 Subject: [PATCH 036/106] ci(blocked-pr): use app token Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 25 +++++++++++++------------ .github/workflows/merge-blocking-pr.yml | 16 +++++++++------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 96a8cfc03..4c1840b01 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -19,13 +19,14 @@ jobs: name: Check Blocked Status runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - statuses: write - checks: write - steps: + - name: Generate token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.PULL_REQUEST_APP_ID }} + private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} + - name: Checkout Default Branch uses: actions/checkout@v4 with: @@ -35,7 +36,7 @@ jobs: if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow @@ -112,7 +113,7 @@ jobs: id: blocking_data if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }} run: | blocked_pr_data=$( @@ -147,7 +148,7 @@ jobs: if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER @@ -156,7 +157,7 @@ jobs: if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER @@ -176,7 +177,7 @@ jobs: if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA") @@ -229,5 +230,5 @@ jobs: comment_id: "block_pr_dependencies" issue_number: ${{ env.PR_NUMBER }} repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} + gh_token: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 4ac85fa5f..755cdcde9 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -14,16 +14,18 @@ jobs: # find the open pr's it was blocked by and trigger a refresh of their state if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' ) - permissions: - issues: write - pull-requests: write - actions: write - steps: + - name: Generate token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.PULL_REQUEST_APP_ID }} + private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} + - name: Gather Dependent PRs id: gather_deps env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | blocked_prs=$( @@ -43,7 +45,7 @@ jobs: - name: Trigger Blocked PR Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} DEPS: ${{ steps.gather_deps.outputs.deps }} run: | while read -r pr ; do From 436896d36524c6c625637c74bc5c1174c04eed58 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:30:26 -0700 Subject: [PATCH 037/106] ci(blocked-pr): use `gh issue comment --create-if-none --edit-last` with app token, not composit action Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/actions/create-comment/action.yml | 155 ---------------------- .github/workflows/blocked-prs.yml | 31 ++--- 2 files changed, 14 insertions(+), 172 deletions(-) delete mode 100644 .github/actions/create-comment/action.yml diff --git a/.github/actions/create-comment/action.yml b/.github/actions/create-comment/action.yml deleted file mode 100644 index 484f1e5af..000000000 --- a/.github/actions/create-comment/action.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: Create Issue Comment -description: Create or update an issue comment -inputs: - comment: - description: Comment Text - required: true - comment_path: - description: "Path to txt file to be parsed" - required: false - comment_id: - description: "Unique identifier for deduplicating comments" - default: "Create Issue Action" - required: false - issue_number: - description: Local Pull Request/Issue number to work on - required: true - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - -runs: - using: "composite" - steps: - - name: Generate Comment Text - shell: bash - env: - COMMENT_ID: ${{ inputs.comment_id }} - COMMENT_TEXT: ${{ inputs.comment }} - COMMENT_FILE: ${{ inputs.comment_path }} - run: | - comment_body="${COMMENT_TEXT}" - if [ -f "$COMMENT_FILE" ] ; then - echo "Reading comment file from ${COMMENT_FILE}" - comment_body="${comment_body}$(cat "$COMMENT_FILE")" - fi - echo 'COMMENT_BODY<> "$GITHUB_ENV" - echo "$comment_body" >> "$GITHUB_ENV" - echo 'EOF' >> "$GITHUB_ENV" - - - name: Get Existing Comment Id - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - ISSUE_NUMBER: ${{ inputs.issue_number }} - REPOSITORY: ${{ inputs.repository }} - COMMENT_ID: ${{ inputs.comment_id }} - run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) - data=$( - gh api graphql \ - --paginate \ - --slurp \ - -f owner="$owner" \ - -f repo="$repo" \ - -F issue="$ISSUE_NUMBER" \ - -f query=' - query($repo: String!, $owner: String!, $issue: Int!, $endCursor: String) { - repository(name: $repo, owner: $owner) { - issueOrPullRequest(number: $issue) { - ... on Issue { - id - number - comments(first: 100, after: $endCursor) { - nodes { - id - body - } - pageInfo { - hasNextPage - endCursor - } - } - } - ... on PullRequest { - id - number - comments(first: 100, after: $endCursor) { - nodes { - id - body - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - } - } - ' \ - | jq -c --arg comment_id "" ' - .[0].data.repository.issueOrPullRequest.id as $id | - [ .[].data.repository.issueOrPullRequest.comments.nodes[] ] as $data | - [ $data.[] | select(.body | startswith($comment_id)) ] as $id_comments | - if ($id_comments | length) > 0 then - { "issueId": $id, "commentId": $id_comments[0].id } - else - { "issueId": $id, "commentId": "" } - end - ' - ) - echo "ISSUE_NODE_ID=$(jq -r '.issueId' <<< "$data")" >> "$GITHUB_ENV" - echo "COMMENT_NODE_ID=$(jq -r '.commentId' <<< "$data")" >> "$GITHUB_ENV" - - - name: Edit Existing Comment - if: env.COMMENT_NODE_ID != '' - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - run: | - gh api graphql \ - -f comment_id="$COMMENT_NODE_ID" \ - -f comment_body="$COMMENT_BODY" \ - -f query=' - mutation($comment_id: ID!, $comment_body: String!) { - updateIssueComment(input: { - id: $comment_id, - body: $comment_body, - }) { - issueComment { - lastEditedAt - } - } - } - ' - - - name: Create Comment - if: env.COMMENT_NODE_ID == '' - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - run: | - gh api graphql \ - -f issue_id="$ISSUE_NODE_ID" \ - -f comment_body="$COMMENT_BODY" \ - -f query=' - mutation ($issue_id: ID!, $comment_body: String!) { - addComment(input: { subjectId: $issue_id, body: $comment_body }) { - commentEdge { - node { - id - } - } - } - } - ' - - - - diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 4c1840b01..9a6caab7d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -27,11 +27,6 @@ jobs: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup @@ -198,15 +193,14 @@ jobs: done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - name: Context Comment - id: blocked_comment + id: generate-comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" - touch "$COMMENT_PATH" - echo "" > "$COMMENT_PATH" + echo '

PR Dependencies :pushpin:

' > "$COMMENT_PATH" pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA") while read -r pr_data ; do base_pr=$(jq -r '.number' <<< "$pr_data") @@ -218,17 +212,20 @@ jobs: type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" + + echo 'body<> "$GITHUB_OUTPUT" + cat "${COMMENT_PATH}" >> "$GITHUB_OUTPUT" + echo 'EOF' >> "$GITHUB_OUTPUT" - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: ./.github/actions/create-comment - with: - comment: "

PR Dependencies :pushpin:

" - comment_path: ${{ steps.blocked_comment.outputs.file_path }} - comment_id: "block_pr_dependencies" - issue_number: ${{ env.PR_NUMBER }} - repository: ${{ github.repository }} - gh_token: ${{ steps.generate-token.outputs.token }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + COMMENT_BODY: ${{ steps.generate-comment.outputs.body }} + run: | + gh -R ${{ github.repository }} issue comment "$PR_NUMBER" \ + --body "$COMMENT_BODY" \ + --create-if-none \ + --edit-last From 815306f1d0e1155f27a650ea7bfbee89a857c0de Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:16:44 -0700 Subject: [PATCH 038/106] Use group redirections for multiple outputs, prevent glob expansion of PR_NUMBER Co-authored-by: Seth Flynn Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 20 ++++++++++++-------- .github/workflows/merge-blocking-pr.yml | 6 ++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 9a6caab7d..d32f53bb1 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -134,9 +134,11 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" - echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" + { + echo "data=$blocked_pr_data"; + echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")"; + echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )"; + } >> "$GITHUB_OUTPUT" - name: Add 'blocked' Label is Missing id: label_blocked @@ -145,7 +147,7 @@ jobs: env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | - gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER + gh -R ${{ github.repository }} issue edit --add-label 'blocked' "$PR_NUMBER" - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked @@ -154,7 +156,7 @@ jobs: env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | - gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER + gh -R ${{ github.repository }} issue edit --remove-label 'blocked' "$PR_NUMBER" - name: Apply 'blocking' Label to Unmerged Dependencies id: label_blocking @@ -213,9 +215,11 @@ jobs: echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") - echo 'body<> "$GITHUB_OUTPUT" - cat "${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - echo 'EOF' >> "$GITHUB_OUTPUT" + { + echo 'body<> "$GITHUB_OUTPUT" - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 755cdcde9..6f85b9282 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -39,8 +39,10 @@ jobs: )) as $i ([]; . + [$i]) ' ) - echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" + { + echo "deps=$blocked_prs" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" + } >> "$GITHUB_OUTPUT" - name: Trigger Blocked PR Workflows for Dependants if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 From 9ee357dcf14bce6c218946e370576dcb97ba37b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 23 Mar 2025 00:27:03 +0000 Subject: [PATCH 039/106] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/6607cf789e541e7873d40d3a8f7815ea92204f32?narHash=sha256-cPfs8qMccim2RBgtKGF%2Bx9IBCduRvd/N5F4nYpU0TVE%3D' (2025-03-13) → 'github:NixOS/nixpkgs/a84ebe20c6bc2ecbcfb000a50776219f48d134cc?narHash=sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ%3D' (2025-03-19) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 0d1bd2df0..ef4c9f555 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1741851582, - "narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=", + "lastModified": 1742422364, + "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6607cf789e541e7873d40d3a8f7815ea92204f32", + "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc", "type": "github" }, "original": { From 9fa397a299252b4621095b462afb0502d62c7da4 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 22 Mar 2025 22:15:36 -0400 Subject: [PATCH 040/106] chore(gitignore): add compile_commands.json Signed-off-by: Seth Flynn --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b5523f685..c8f056eef 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ CMakeCache.txt /.vs cmake-build-*/ Debug +compile_commands.json # Build dirs build From 8a21b079159e12308928dab6d6d44d89c5bac0a3 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 22 Mar 2025 22:15:52 -0400 Subject: [PATCH 041/106] build: export compile commands for debug builds Signed-off-by: Seth Flynn --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc2e77d4a..138049018 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") # set CXXFLAGS for build targets set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") +# Export compile commands for debug builds if we can (useful in LSPs like clangd) +# https://cmake.org/cmake/help/v3.31/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html +if(CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja" AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +endif() + option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF) # If this is a Debug build turn on address sanitiser From d6103c2a068a8ebc96e8dd349f3878d84da7e64f Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 22 Mar 2025 22:19:16 -0400 Subject: [PATCH 042/106] build(nix): copy compile_commands.json when entering dev shell Signed-off-by: Seth Flynn --- flake.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/flake.nix b/flake.nix index 54add656d..d15a5bf03 100644 --- a/flake.nix +++ b/flake.nix @@ -79,6 +79,16 @@ ccache ninja ]; + + cmakeFlags = self.packages.${system}.prismlauncher-unwrapped.cmakeFlags ++ [ + "-GNinja" + "-Bbuild" + ]; + + shellHook = '' + cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug + ln -s {build/,}compile_commands.json + ''; }; } ); From e3f93295f0c8e4ea44762535e4e15bb4e0f0cf78 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 22 Mar 2025 22:19:49 -0400 Subject: [PATCH 043/106] build(nix): add clangd to dev shell Signed-off-by: Seth Flynn --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index d15a5bf03..150240c8b 100644 --- a/flake.nix +++ b/flake.nix @@ -78,6 +78,7 @@ buildInputs = with pkgs; [ ccache ninja + llvmPackages_19.clang-tools ]; cmakeFlags = self.packages.${system}.prismlauncher-unwrapped.cmakeFlags ++ [ From 269938dfd866df5918eb44779667142f7a157c22 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 23 Mar 2025 08:52:08 +0200 Subject: [PATCH 044/106] rename variable Signed-off-by: Trial97 --- launcher/net/FileSink.cpp | 8 ++++---- launcher/net/FileSink.h | 2 +- launcher/net/MetaCacheSink.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 95c1a8f44..3f30c5e54 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -54,7 +54,7 @@ Task::State FileSink::init(QNetworkRequest& request) return Task::State::Failed; } - wroteAnyData = false; + m_wroteAnyData = false; m_output_file.reset(new PSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; @@ -72,11 +72,11 @@ Task::State FileSink::write(QByteArray& data) qCCritical(taskNetLogC) << "Failed writing into " + m_filename; m_output_file->cancelWriting(); m_output_file.reset(); - wroteAnyData = false; + m_wroteAnyData = false; return Task::State::Failed; } - wroteAnyData = true; + m_wroteAnyData = true; return Task::State::Running; } @@ -100,7 +100,7 @@ Task::State FileSink::finalize(QNetworkReply& reply) // if we wrote any data to the save file, we try to commit the data to the real file. // if it actually got a proper file, we write it even if it was empty - if (gotFile || wroteAnyData) { + if (gotFile || m_wroteAnyData) { // ask validators for data consistency // we only do this for actual downloads, not 'your data is still the same' cache hits if (!finalizeAllValidators(reply)) diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 272f8ddc3..67c25361c 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -58,7 +58,7 @@ class FileSink : public Sink { protected: QString m_filename; - bool wroteAnyData = false; + bool m_wroteAnyData = false; std::unique_ptr m_output_file; }; } // namespace Net diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 889588a11..432c0c84b 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -78,7 +78,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply& reply) { QFileInfo output_file_info(m_filename); - if (wroteAnyData) { + if (m_wroteAnyData) { m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); } From 9ce5587f60d637d48bf20a8b9a40f020898710de Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 23 Mar 2025 08:52:30 +0200 Subject: [PATCH 045/106] fix crash on accessing reseted output Signed-off-by: Trial97 --- launcher/net/FileSink.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 3f30c5e54..3a58a4667 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -82,7 +82,9 @@ Task::State FileSink::write(QByteArray& data) Task::State FileSink::abort() { - m_output_file->cancelWriting(); + if (m_output_file) { + m_output_file->cancelWriting(); + } failAllValidators(); return Task::State::Failed; } From a4472406b96faabf2bdb78d152fc0e66bfe4ac63 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 23 Mar 2025 14:02:09 +0200 Subject: [PATCH 046/106] fix account help link Signed-off-by: Trial97 --- launcher/ui/pages/global/AccountListPage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 4f02b7df5..7bd5101c0 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -66,7 +66,7 @@ class AccountListPage : public QMainWindow, public BasePage { return icon; } QString id() const override { return "accounts"; } - QString helpPage() const override { return "/getting-started/adding-an-account"; } + QString helpPage() const override { return "getting-started/adding-an-account"; } void retranslate() override; public slots: From f08478c7ece0cd4e13b0bc188d0dcb9ea04c3199 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 23 Mar 2025 14:17:43 +0000 Subject: [PATCH 047/106] Use correct colours for all system themes Signed-off-by: TheKodeToad --- launcher/ui/themes/SystemTheme.cpp | 16 ++-------------- launcher/ui/themes/SystemTheme.h | 3 +-- launcher/ui/themes/ThemeManager.cpp | 6 ++---- launcher/ui/themes/ThemeManager.h | 1 - 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index a1674455a..31f6a96ab 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -40,23 +40,11 @@ #include "HintOverrideProxyStyle.h" #include "ThemeManager.h" -SystemTheme::SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme) +SystemTheme::SystemTheme(const QString& styleName, bool isDefaultTheme) { themeName = isDefaultTheme ? "system" : styleName; widgetTheme = styleName; - colorPalette = palette; -} - -void SystemTheme::apply(bool initial) -{ - // See https://github.com/MultiMC/Launcher/issues/1790 - // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial) { - QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); - return; - } - - ITheme::apply(initial); + colorPalette = QStyleFactory::create(styleName)->standardPalette(); } QString SystemTheme::id() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 7c260fdc4..195b8e368 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -38,9 +38,8 @@ class SystemTheme : public ITheme { public: - SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme); + SystemTheme(const QString& styleName, bool isDefaultTheme); virtual ~SystemTheme() {} - void apply(bool initial) override; QString id() override; QString name() override; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 30a1fe7be..6c50d7409 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -44,8 +44,6 @@ ThemeManager::ThemeManager() m_defaultStyle = style->objectName(); themeDebugLog() << "System theme seems to be:" << m_defaultStyle; - m_defaultPalette = QApplication::palette(); - initializeThemes(); initializeCatPacks(); } @@ -128,7 +126,7 @@ void ThemeManager::initializeIcons() void ThemeManager::initializeWidgets() { themeDebugLog() << "<> Initializing Widget Themes"; - themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique(m_defaultStyle, m_defaultPalette, true)); + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique(m_defaultStyle, true)); auto darkThemeId = addTheme(std::make_unique()); themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); @@ -141,7 +139,7 @@ void ThemeManager::initializeWidgets() continue; } #endif - themeDebugLog() << "Loading System Theme:" << addTheme(std::make_unique(st, m_defaultPalette, false)); + themeDebugLog() << "Loading System Theme:" << addTheme(std::make_unique(st, false)); } // TODO: need some way to differentiate same name themes in different subdirectories diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index a9036107c..9c9e818e5 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -69,7 +69,6 @@ class ThemeManager { QDir m_catPacksFolder{ "catpacks" }; std::map> m_catPacks; QString m_defaultStyle; - QPalette m_defaultPalette; LogColors m_logColors; void initializeThemes(); From 513959750f23719b55030cd34b9a8f5b248289c5 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 23 Mar 2025 14:29:58 +0000 Subject: [PATCH 048/106] Try best to avoid regression Signed-off-by: TheKodeToad --- launcher/ui/themes/SystemTheme.cpp | 12 ++++++++++++ launcher/ui/themes/SystemTheme.h | 1 + 2 files changed, 13 insertions(+) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 31f6a96ab..59644ddde 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -47,6 +47,18 @@ SystemTheme::SystemTheme(const QString& styleName, bool isDefaultTheme) colorPalette = QStyleFactory::create(styleName)->standardPalette(); } +void SystemTheme::apply(bool initial) +{ + // See https://github.com/MultiMC/Launcher/issues/1790 + // or https://github.com/PrismLauncher/PrismLauncher/issues/490 + if (initial && themeName == "system") { + QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); + return; + } + + ITheme::apply(initial); +} + QString SystemTheme::id() { return themeName; diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 195b8e368..09db1a322 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -40,6 +40,7 @@ class SystemTheme : public ITheme { public: SystemTheme(const QString& styleName, bool isDefaultTheme); virtual ~SystemTheme() {} + void apply(bool initial) override; QString id() override; QString name() override; From f7fd6f566f364d6327b11bc89d27ef6d1258d661 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 4 Oct 2023 13:51:25 +0300 Subject: [PATCH 049/106] chore:fixed some codeql warnings Signed-off-by: Trial97 --- launcher/DataMigrationTask.cpp | 2 +- launcher/FileSystem.h | 12 +-- launcher/InstanceCopyTask.cpp | 6 +- launcher/InstanceCopyTask.h | 2 +- launcher/JavaCommon.h | 2 +- launcher/VersionProxyModel.cpp | 44 ++++----- launcher/minecraft/PackProfile.cpp | 10 +-- launcher/minecraft/WorldList.cpp | 10 +-- launcher/minecraft/auth/AccountList.cpp | 90 ++++++++----------- .../minecraft/gameoptions/GameOptions.cpp | 14 ++- launcher/minecraft/mod/DataPack.cpp | 21 ++--- launcher/minecraft/mod/ModFolderModel.cpp | 9 +- .../minecraft/mod/ResourceFolderModel.cpp | 9 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 9 +- .../atlauncher/ATLPackInstallTask.h | 2 +- .../modplatform/legacy_ftb/PackFetchTask.h | 2 +- .../legacy_ftb/PackInstallTask.cpp | 2 +- .../modplatform/legacy_ftb/PackInstallTask.h | 2 +- launcher/pathmatcher/MultiMatcher.h | 2 + launcher/pathmatcher/RegexpMatcher.h | 2 + launcher/ui/dialogs/ProgressDialog.cpp | 2 +- launcher/ui/dialogs/ProgressDialog.h | 2 +- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 14 +-- launcher/ui/pages/instance/ServersPage.cpp | 59 ++++++------ launcher/ui/pages/instance/WorldListPage.cpp | 9 +- .../atlauncher/AtlOptionalModDialog.cpp | 10 ++- .../atlauncher/AtlOptionalModDialog.h | 8 +- .../AtlUserInteractionSupportImpl.cpp | 2 +- .../AtlUserInteractionSupportImpl.h | 3 +- .../modplatform/import_ftb/ListModel.cpp | 3 - .../modplatform/legacy_ftb/ListModel.cpp | 2 +- .../pages/modplatform/legacy_ftb/ListModel.h | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.h | 2 +- launcher/ui/widgets/FocusLineEdit.h | 2 + tests/FileSystem_test.cpp | 19 ++-- 36 files changed, 176 insertions(+), 217 deletions(-) diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp index c03302319..92e310a16 100644 --- a/launcher/DataMigrationTask.cpp +++ b/launcher/DataMigrationTask.cpp @@ -15,7 +15,7 @@ DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher) : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath) { - m_copy.matcher(m_pathMatcher.get()).whitelist(true); + m_copy.matcher(m_pathMatcher).whitelist(true); } void DataMigrationTask::executeTask() diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index c5beef7bd..bf91c603c 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -115,7 +115,7 @@ class copy : public QObject { m_followSymlinks = follow; return *this; } - copy& matcher(const IPathMatcher* filter) + copy& matcher(IPathMatcher::Ptr filter) { m_matcher = filter; return *this; @@ -147,7 +147,7 @@ class copy : public QObject { private: bool m_followSymlinks = true; - const IPathMatcher* m_matcher = nullptr; + IPathMatcher::Ptr m_matcher = nullptr; bool m_whitelist = false; bool m_overwrite = false; QDir m_src; @@ -209,7 +209,7 @@ class create_link : public QObject { m_useHardLinks = useHard; return *this; } - create_link& matcher(const IPathMatcher* filter) + create_link& matcher(IPathMatcher::Ptr filter) { m_matcher = filter; return *this; @@ -260,7 +260,7 @@ class create_link : public QObject { private: bool m_useHardLinks = false; - const IPathMatcher* m_matcher = nullptr; + IPathMatcher::Ptr m_matcher = nullptr; bool m_whitelist = false; bool m_recursive = true; @@ -488,7 +488,7 @@ class clone : public QObject { m_src.setPath(src); m_dst.setPath(dst); } - clone& matcher(const IPathMatcher* filter) + clone& matcher(IPathMatcher::Ptr filter) { m_matcher = filter; return *this; @@ -514,7 +514,7 @@ class clone : public QObject { bool operator()(const QString& offset, bool dryRun = false); private: - const IPathMatcher* m_matcher = nullptr; + IPathMatcher::Ptr m_matcher = nullptr; bool m_whitelist = false; QDir m_src; QDir m_dst; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index d335b11c4..fb5963532 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -43,7 +43,7 @@ void InstanceCopyTask::executeTask() m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { if (m_useClone) { FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); - folderClone.matcher(m_matcher.get()); + folderClone.matcher(m_matcher); folderClone(true); setProgress(0, folderClone.totalCloned()); @@ -72,7 +72,7 @@ void InstanceCopyTask::executeTask() } FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder - folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher); folderLink(true); setProgress(0, m_progressTotal + folderLink.totalToLink()); @@ -127,7 +127,7 @@ void InstanceCopyTask::executeTask() return !there_were_errors; } FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).matcher(m_matcher.get()); + folderCopy.followSymlinks(false).matcher(m_matcher); folderCopy(true); setProgress(0, folderCopy.totalCopied()); diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 0f7f1020d..3aba13e5c 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -28,7 +28,7 @@ class InstanceCopyTask : public InstanceTask { InstancePtr m_origInstance; QFuture m_copyFuture; QFutureWatcher m_copyFutureWatcher; - std::unique_ptr m_matcher; + IPathMatcher::Ptr m_matcher; bool m_keepPlaytime; bool m_useLinks = false; bool m_useHardLinks = false; diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index a21b5a494..0e4aa2b0a 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -24,7 +24,7 @@ class TestCheck : public QObject { TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen) : m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) {} - virtual ~TestCheck() {}; + virtual ~TestCheck() = default; void run(); diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 7538ce08c..3d9d95eb6 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -193,8 +193,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const if (value.toBool()) { return tr("Recommended"); } else if (hasLatest) { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if (value.toBool()) { + auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if (latest.toBool()) { return tr("Latest"); } } @@ -203,33 +203,27 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const } } case Qt::DecorationRole: { - switch (column) { - case Name: { - if (hasRecommended) { - auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if (recommenced.toBool()) { - return APPLICATION->getThemedIcon("star"); - } else if (hasLatest) { - auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if (latest.toBool()) { - return APPLICATION->getThemedIcon("bug"); - } - } - QPixmap pixmap; - QPixmapCache::find("placeholder", &pixmap); - if (!pixmap) { - QPixmap px(16, 16); - px.fill(Qt::transparent); - QPixmapCache::insert("placeholder", px); - return px; - } - return pixmap; + if (column == Name && hasRecommended) { + auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if (recommenced.toBool()) { + return APPLICATION->getThemedIcon("star"); + } else if (hasLatest) { + auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if (latest.toBool()) { + return APPLICATION->getThemedIcon("bug"); } } - default: { - return QVariant(); + QPixmap pixmap; + QPixmapCache::find("placeholder", &pixmap); + if (!pixmap) { + QPixmap px(16, 16); + px.fill(Qt::transparent); + QPixmapCache::insert("placeholder", px); + return px; } + return pixmap; } + return QVariant(); } default: { if (roles.contains((BaseVersionList::ModelRoles)role)) { diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index d6534b910..4deee9712 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -517,13 +517,9 @@ QVariant PackProfile::data(const QModelIndex& index, int role) const switch (role) { case Qt::CheckStateRole: { - switch (column) { - case NameColumn: { - return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; - } - default: - return QVariant(); - } + if (column == NameColumn) + return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; + return QVariant(); } case Qt::DisplayRole: { switch (column) { diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 812b13c71..cf27be676 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -208,13 +208,9 @@ QVariant WorldList::data(const QModelIndex& index, int role) const } case Qt::UserRole: - switch (column) { - case SizeColumn: - return QVariant::fromValue(world.bytes()); - - default: - return data(index, Qt::DisplayRole); - } + if (column == SizeColumn) + return QVariant::fromValue(world.bytes()); + return data(index, Qt::DisplayRole); case Qt::ToolTipRole: { if (column == InfoColumn) { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index d276d4c41..3cbbb2a74 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -260,6 +260,30 @@ int AccountList::count() const return m_accounts.count(); } +QString getAccountStatus(AccountState status) +{ + switch (status) { + case AccountState::Unchecked: + return QObject::tr("Unchecked", "Account status"); + case AccountState::Offline: + return QObject::tr("Offline", "Account status"); + case AccountState::Online: + return QObject::tr("Ready", "Account status"); + case AccountState::Working: + return QObject::tr("Working", "Account status"); + case AccountState::Errored: + return QObject::tr("Errored", "Account status"); + case AccountState::Expired: + return QObject::tr("Expired", "Account status"); + case AccountState::Disabled: + return QObject::tr("Disabled", "Account status"); + case AccountState::Gone: + return QObject::tr("Gone", "Account status"); + default: + return QObject::tr("Unknown", "Account status"); + } +} + QVariant AccountList::data(const QModelIndex& index, int role) const { if (!index.isValid()) @@ -273,13 +297,10 @@ QVariant AccountList::data(const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: switch (index.column()) { - case ProfileNameColumn: { + case ProfileNameColumn: return account->profileName(); - } - case NameColumn: return account->accountDisplayString(); - case TypeColumn: { switch (account->accountType()) { case AccountType::MSA: { @@ -291,39 +312,8 @@ QVariant AccountList::data(const QModelIndex& index, int role) const } return tr("Unknown", "Account type"); } - - case StatusColumn: { - switch (account->accountState()) { - case AccountState::Unchecked: { - return tr("Unchecked", "Account status"); - } - case AccountState::Offline: { - return tr("Offline", "Account status"); - } - case AccountState::Online: { - return tr("Ready", "Account status"); - } - case AccountState::Working: { - return tr("Working", "Account status"); - } - case AccountState::Errored: { - return tr("Errored", "Account status"); - } - case AccountState::Expired: { - return tr("Expired", "Account status"); - } - case AccountState::Disabled: { - return tr("Disabled", "Account status"); - } - case AccountState::Gone: { - return tr("Gone", "Account status"); - } - default: { - return tr("Unknown", "Account status"); - } - } - } - + case StatusColumn: + return getAccountStatus(account->accountState()); default: return QVariant(); } @@ -335,11 +325,9 @@ QVariant AccountList::data(const QModelIndex& index, int role) const return QVariant::fromValue(account); case Qt::CheckStateRole: - if (index.column() == ProfileNameColumn) { + if (index.column() == ProfileNameColumn) return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked; - } else { - return QVariant(); - } + return QVariant(); default: return QVariant(); @@ -461,18 +449,14 @@ bool AccountList::loadList() // Make sure the format version matches. auto listVersion = root.value("formatVersion").toVariant().toInt(); - switch (listVersion) { - case AccountListVersion::MojangMSA: { - return loadV3(root); - } break; - default: { - QString newName = "accounts-old.json"; - qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName; - // Attempt to rename the old version. - file.rename(newName); - return false; - } - } + if (listVersion == AccountListVersion::MojangMSA) + return loadV3(root); + + QString newName = "accounts-old.json"; + qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName; + // Attempt to rename the old version. + file.rename(newName); + return false; } bool AccountList::loadV3(QJsonObject& root) diff --git a/launcher/minecraft/gameoptions/GameOptions.cpp b/launcher/minecraft/gameoptions/GameOptions.cpp index 4f4fb99a7..25f7074ec 100644 --- a/launcher/minecraft/gameoptions/GameOptions.cpp +++ b/launcher/minecraft/gameoptions/GameOptions.cpp @@ -87,16 +87,12 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const if (row < 0 || row >= int(contents.size())) return QVariant(); - switch (role) { - case Qt::DisplayRole: - if (column == 0) { - return contents[row].key; - } else { - return contents[row].value; - } - default: - return QVariant(); + if (role == Qt::DisplayRole) { + if (column == 0) + return contents[row].key; + return contents[row].value; } + return QVariant(); } int GameOptions::rowCount(const QModelIndex&) const diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 4a9e77a70..580d5c714 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -105,19 +105,16 @@ std::pair DataPack::compatibleVersions() const int DataPack::compare(const Resource& other, SortType type) const { auto const& cast_other = static_cast(other); - switch (type) { - default: - return Resource::compare(other, type); - case SortType::PACK_FORMAT: { - auto this_ver = packFormat(); - auto other_ver = cast_other.packFormat(); + if (type == SortType::PACK_FORMAT) { + auto this_ver = packFormat(); + auto other_ver = cast_other.packFormat(); - if (this_ver > other_ver) - return 1; - if (this_ver < other_ver) - return -1; - break; - } + if (this_ver > other_ver) + return 1; + if (this_ver < other_ver) + return -1; + } else { + return Resource::compare(other, type); } return 0; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 027f3d4ca..43888ae27 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -145,12 +145,9 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const } return {}; case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return at(row).enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + return QVariant(); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 70555fa35..d4900616b 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -513,12 +513,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; - default: - return {}; - } + if (column == ActiveColumn) + return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; + return {}; default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 94774c81f..d9f27a043 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -128,12 +128,9 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } return {}; case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return at(row).enabled() ? Qt::Checked : Qt::Unchecked; - default: - return {}; - } + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + return {}; default: return {}; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index ffc358fbb..ee5960e30 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -62,7 +62,7 @@ class UserInteractionSupport { /** * Requests a user interaction to select which optional mods should be installed. */ - virtual std::optional> chooseOptionalMods(PackVersion version, QVector mods) = 0; + virtual std::optional> chooseOptionalMods(const PackVersion& version, QVector mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h index e37d949d5..4c7a8f6aa 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.h +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h @@ -40,7 +40,7 @@ class PackFetchTask : public QObject { void failed(QString reason); void aborted(); - void privateFileDownloadFinished(Modpack modpack); + void privateFileDownloadFinished(const Modpack& modpack); void privateFileDownloadFailed(QString reason, QString packCode); }; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index d6252663f..c04c0b2f3 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -52,7 +52,7 @@ namespace LegacyFTB { -PackInstallTask::PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version) +PackInstallTask::PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version) { m_pack = pack; m_version = version; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 30ff48597..42808a1a2 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -18,7 +18,7 @@ class PackInstallTask : public InstanceTask { Q_OBJECT public: - explicit PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version); + explicit PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version); virtual ~PackInstallTask() {} bool canAbort() const override { return true; } diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h index 3e2bdb95d..ccd5a9163 100644 --- a/launcher/pathmatcher/MultiMatcher.h +++ b/launcher/pathmatcher/MultiMatcher.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include "IPathMatcher.h" diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h index a6a3e616d..18c42f887 100644 --- a/launcher/pathmatcher/RegexpMatcher.h +++ b/launcher/pathmatcher/RegexpMatcher.h @@ -1,3 +1,5 @@ +#pragma once + #include #include "IPathMatcher.h" diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 9897687e3..aa2f67bdb 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -92,7 +92,7 @@ ProgressDialog::~ProgressDialog() { for (auto conn : this->m_taskConnections) { disconnect(conn); - } + } delete ui; } diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 4a696a49d..50e4418da 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -93,7 +93,7 @@ class ProgressDialog : public QDialog { Ui::ProgressDialog* ui; Task* m_task; - + QList m_taskConnections; bool m_is_multi_step = false; diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 7e29e1192..aa4bbd294 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -282,6 +282,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool bool skip_rest = false; ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; + // adds resource to list based on provider auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: @@ -293,6 +294,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool } }; + // ask the user on what provider to seach for the mod first for (auto candidate : m_candidates) { if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); @@ -335,6 +337,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool addToTmp(candidate, response.chosen); } + // prepare task for the modrinth mods if (!modrinth_tmp.empty()) { auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); @@ -350,6 +353,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool seq.addTask(modrinth_task); } + // prepare task for the flame mods if (!flame_tmp.empty()) { auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); @@ -367,6 +371,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool seq.addTask(m_second_try_metadata); + // execute all the tasks ProgressDialog checking_dialog(m_parent); checking_dialog.setSkipButton(true, tr("Abort")); checking_dialog.setWindowTitle(tr("Generating metadata...")); @@ -477,13 +482,8 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q auto changelog_area = new QTextBrowser(); QString text = info.changelog; - switch (info.provider) { - case ModPlatform::ResourceProvider::MODRINTH: { - text = markdownToHTML(info.changelog.toUtf8()); - break; - } - default: - break; + if (info.provider == ModPlatform::ResourceProvider::MODRINTH) { + text = markdownToHTML(info.changelog.toUtf8()); } changelog_area->setHtml(StringUtils::htmlListPatch(text)); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 4bc2e6998..136fb47c7 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -36,9 +36,9 @@ */ #include "ServersPage.h" +#include "ServerPingTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" -#include "ServerPingTask.h" #include #include @@ -49,10 +49,10 @@ #include #include +#include #include #include #include -#include static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things. @@ -113,8 +113,8 @@ struct Server { // Data - temporary bool m_checked = false; bool m_up = false; - QString m_motd; // https://mctools.org/motd-creator - std::optional m_currentPlayers; // nullopt if not calculated/calculating + QString m_motd; // https://mctools.org/motd-creator + std::optional m_currentPlayers; // nullopt if not calculated/calculating int m_maxPlayers = 0; }; @@ -317,10 +317,10 @@ class ServersModel : public QAbstractListModel { if (row < 0 || row >= m_servers.size()) return QVariant(); - switch (column) { - case 0: - switch (role) { - case Qt::DecorationRole: { + switch (role) { + case Qt::DecorationRole: { + switch (column) { + case 0: { auto& bytes = m_servers[row].m_icon; if (bytes.size()) { QPixmap px; @@ -329,31 +329,32 @@ class ServersModel : public QAbstractListModel { } return APPLICATION->getThemedIcon("unknown_server"); } - case Qt::DisplayRole: - return m_servers[row].m_name; - case ServerPtrRole: - return QVariant::fromValue((void*)&m_servers[row]); - default: - return QVariant(); - } - case 1: - switch (role) { - case Qt::DisplayRole: + case 1: return m_servers[row].m_address; default: return QVariant(); } - case 2: - switch (role) { - case Qt::DisplayRole: + case 2: + if (role == Qt::DisplayRole) { if (m_servers[row].m_currentPlayers) { return *m_servers[row].m_currentPlayers; } else { return "..."; } - default: + } else { return QVariant(); - } + } + } + case Qt::DisplayRole: + if (column == 0) + return m_servers[row].m_name; + else + return QVariant(); + case ServerPtrRole: + if (column == 0) + return QVariant::fromValue((void*)&m_servers[row]); + else + return QVariant(); default: return QVariant(); } @@ -447,22 +448,22 @@ class ServersModel : public QAbstractListModel { } m_currentQueryTask = ConcurrentTask::Ptr( - new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()) - ); + new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); int row = 0; - for (Server &server : m_servers) { + for (Server& server : m_servers) { // reset current players server.m_currentPlayers = {}; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); // Start task to query server status auto target = MinecraftTarget::parse(server.m_address, false); - auto *task = new ServerPingTask(target.address, target.port); + auto* task = new ServerPingTask(target.address, target.port); m_currentQueryTask->addTask(Task::Ptr(task)); // Update the model when the task is done connect(task, &Task::finished, this, [this, task, row]() { - if (m_servers.size() < row) return; + if (m_servers.size() < row) + return; m_servers[row].m_currentPlayers = task->m_outputOnlinePlayers; emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); }); @@ -717,7 +718,7 @@ void ServersPage::openedImpl() ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); - // ping servers + // ping servers m_model->queryServersStatus(); } diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 4ed5f1f73..dd7486a6c 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -166,12 +166,9 @@ void WorldListPage::retranslate() bool WorldListPage::worldListFilter(QKeyEvent* keyEvent) { - switch (keyEvent->key()) { - case Qt::Key_Delete: - on_actionRemove_triggered(); - return true; - default: - break; + if (keyEvent->key() == Qt::Key_Delete) { + on_actionRemove_triggered(); + return true; } return QWidget::eventFilter(ui->worldTreeView, keyEvent); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 6fb867733..d84737bf5 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -45,7 +45,9 @@ #include "net/ApiDownload.h" -AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods) +AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, + const ATLauncher::PackVersion& version, + QVector mods) : QAbstractListModel(parent), m_version(version), m_mods(mods) { // fill mod index @@ -233,7 +235,7 @@ void AtlOptionalModListModel::clearAll() emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); } -void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) +void AtlOptionalModListModel::toggleMod(const ATLauncher::VersionMod& mod, int index) { auto enable = !m_selection[mod.name]; @@ -251,7 +253,7 @@ void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) setMod(mod, index, enable); } -void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) +void AtlOptionalModListModel::setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit) { if (m_selection[mod.name] == enable) return; @@ -313,7 +315,7 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool } } -AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods) +AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { ui->setupUi(this); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 767d277d9..0636715cc 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -55,7 +55,7 @@ class AtlOptionalModListModel : public QAbstractListModel { DescriptionColumn, }; - AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods); + AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); QVector getResult(); @@ -78,8 +78,8 @@ class AtlOptionalModListModel : public QAbstractListModel { void clearAll(); private: - void toggleMod(ATLauncher::VersionMod mod, int index); - void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true); + void toggleMod(const ATLauncher::VersionMod& mod, int index); + void setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit = true); private: NetJob::Ptr m_jobPtr; @@ -97,7 +97,7 @@ class AtlOptionalModDialog : public QDialog { Q_OBJECT public: - AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods); + AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); ~AtlOptionalModDialog() override; QVector getResult() { return listModel->getResult(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index 0c7257859..7550ff758 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -41,7 +41,7 @@ AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {} -std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, +std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, QVector mods) { AtlOptionalModDialog optionalModDialog(m_parent, version, mods); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index 52ced2615..7ff021105 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -48,7 +48,8 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt private: QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; - std::optional> chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; + std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, + QVector mods) override; void displayMessage(QString message) override; private: diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index f3c737977..0fb83b6cb 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -106,9 +106,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const } auto pack = m_modpacks.at(pos); - if (role == Qt::ToolTipRole) { - } - switch (role) { case Qt::ToolTipRole: return tr("Minecraft %1").arg(pack.mcVersion); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 98922123c..bdb7c64d7 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -213,7 +213,7 @@ void ListModel::fill(ModpackList modpacks_) endResetModel(); } -void ListModel::addPack(Modpack modpack) +void ListModel::addPack(const Modpack& modpack) { beginResetModel(); this->modpacks.append(modpack); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h index f35012078..e4477c929 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h @@ -62,7 +62,7 @@ class ListModel : public QAbstractListModel { Qt::ItemFlags flags(const QModelIndex& index) const override; void fill(ModpackList modpacks); - void addPack(Modpack modpack); + void addPack(const Modpack& modpack); void clear(); void remove(int row); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 226a30ee3..5752b6c61 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -213,7 +213,7 @@ void Page::ftbPackDataDownloadAborted() CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); } -void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack) +void Page::ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack) { privateListModel->addPack(pack); } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index a2dee24e9..818000c05 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -85,7 +85,7 @@ class Page : public QWidget, public ModpackProviderBasePage { void ftbPackDataDownloadFailed(QString reason); void ftbPackDataDownloadAborted(); - void ftbPrivatePackDataDownloadSuccessfully(Modpack pack); + void ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack); void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); void onSortingSelectionChanged(QString data); diff --git a/launcher/ui/widgets/FocusLineEdit.h b/launcher/ui/widgets/FocusLineEdit.h index f5ea6602e..797969406 100644 --- a/launcher/ui/widgets/FocusLineEdit.h +++ b/launcher/ui/widgets/FocusLineEdit.h @@ -1,3 +1,5 @@ +#pragma once + #include class FocusLineEdit : public QLineEdit { diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index ca0313bb4..9f64f54ed 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -42,7 +43,7 @@ class LinkTask : public Task { ~LinkTask() { delete m_lnk; } - void matcher(const IPathMatcher* filter) { m_lnk->matcher(filter); } + void matcher(IPathMatcher::Ptr filter) { m_lnk->matcher(filter); } void linkRecursively(bool recursive) { @@ -203,8 +204,8 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - c.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + c.matcher(re); c(); for (auto entry : target_dir.entryList()) { @@ -236,8 +237,8 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - c.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + c.matcher(re); c.whitelist(true); c(); @@ -429,8 +430,8 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - lnk_tsk.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); @@ -476,8 +477,8 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - lnk_tsk.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); lnk_tsk.whitelist(true); QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { From 6ef59fe984777e2620b445a78493afb86074eb8b Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 24 Mar 2025 17:59:53 -0400 Subject: [PATCH 050/106] ci: use bundled linuxdeploy appimage plugin Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e459914..5a3e4f4ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -251,7 +251,6 @@ jobs: if: runner.os == 'Linux' && matrix.qt_ver != 5 run: | wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" From 5d5155bb22f9935e17e33008a644736404629928 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 24 Mar 2025 18:16:58 -0400 Subject: [PATCH 051/106] ci: pin appimage tooling Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a3e4f4ec..0e902a6ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,9 @@ jobs: qt_arch: "" qt_version: "6.5.3" qt_modules: "qt5compat qtimageformats qtnetworkauth" + linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" + linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" + appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" - os: windows-2022 name: "Windows-MinGW-w64" @@ -249,11 +252,19 @@ jobs: - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 + env: + APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }} + LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }} + LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }} run: | - wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" + wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage" + wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage" - wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" + wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage" + + sha256sum -c - <<< "$LINUXDEPLOY_HASH" + sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH" + sha256sum -c - <<< "$APPIMAGEUPDATE_HASH" sudo apt install libopengl0 libfuse2 From 671aad88f52e3fbba872e80f2805c0c5c64b7a8d Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 24 Mar 2025 18:17:26 -0400 Subject: [PATCH 052/106] ci: don't install libfuse2 Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e902a6ce..59489d0f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -266,7 +266,7 @@ jobs: sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH" sha256sum -c - <<< "$APPIMAGEUPDATE_HASH" - sudo apt install libopengl0 libfuse2 + sudo apt install libopengl0 - name: Add QT_HOST_PATH var (Windows MSVC arm64) if: runner.os == 'Windows' && matrix.architecture == 'arm64' From 0da645594fbb0e8cd0b791e0901e9311d20d9b16 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 01:20:07 +0800 Subject: [PATCH 053/106] Adjust instance view sorting mode to QComboBox and add renaming behavior Signed-off-by: Yihe Li --- launcher/Application.cpp | 2 + launcher/ui/pages/global/LauncherPage.cpp | 48 +++++++++++++++--- launcher/ui/pages/global/LauncherPage.ui | 62 ++++++++++++++++------- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c3477d331..816f7b8ab 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -709,7 +709,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ToolbarsLocked", false); + // Instance m_settings->registerSetting("InstSortMode", "Name"); + m_settings->registerSetting("InstRenamingMode", "AskEverytime"); m_settings->registerSetting("SelectedInstance", QString()); // Window state and geometry diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 04ee01b00..fab1bfe38 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -65,13 +65,19 @@ enum InstSortMode { Sort_LastLaunch }; +enum InstRenamingMode { + // Rename metadata only. + Rename_Metadata, + // Rename physical directory too. + Rename_Physical, + // Ask everytime. + Rename_Ask +}; + LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); - ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); - ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); - defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); m_languageModel = APPLICATION->translations(); @@ -234,7 +240,8 @@ void LauncherPage::applySettings() s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); - auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); + // Instance + auto sortMode = (InstSortMode) ui->viewSortingComboBox->currentIndex(); switch (sortMode) { case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); @@ -245,6 +252,20 @@ void LauncherPage::applySettings() break; } + auto renamingMode = (InstRenamingMode) ui->renamingBehaviorComboBox->currentIndex(); + switch (renamingMode) { + case Rename_Metadata: + s->set("InstRenamingMode", "MetadataOnly"); + break; + case Rename_Physical: + s->set("InstRenamingMode", "PhysicalDir"); + break; + case Rename_Ask: + default: + s->set("InstRenamingMode", "AskEverytime"); + break; + } + // Cat s->set("CatOpacity", ui->catOpacitySpinBox->value()); @@ -299,13 +320,26 @@ void LauncherPage::loadSettings() ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); ui->downloadsDirMoveCheckBox->setChecked(s->get("MoveModsFromDownloadsDir").toBool()); + // Instance QString sortMode = s->get("InstSortMode").toString(); - + InstSortMode sortModeEnum; if (sortMode == "LastLaunch") { - ui->sortLastLaunchedBtn->setChecked(true); + sortModeEnum = Sort_LastLaunch; } else { - ui->sortByNameBtn->setChecked(true); + sortModeEnum = Sort_Name; } + ui->viewSortingComboBox->setCurrentIndex(sortModeEnum); + + QString renamingMode = s->get("InstRenamingMode").toString(); + InstRenamingMode renamingModeEnum; + if (renamingMode == "MetadataOnly") { + renamingModeEnum = Rename_Metadata; + } else if (renamingMode == "PhysicalDir"){ + renamingModeEnum = Rename_Physical; + } else { + renamingModeEnum = Rename_Ask; + } + ui->renamingBehaviorComboBox->setCurrentIndex(renamingModeEnum); // Cat ui->catOpacitySpinBox->setValue(s->get("CatOpacity").toInt()); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 31c878f3e..01aad7af7 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -401,32 +401,59 @@ - + true - Instance view sorting mode + Instance - - - + + + - &By last launched + Instance view sorting mode - - sortingModeGroup - - - + + + + + By last launched + + + + + By name + + + + + + - By &name + Instance renaming behavior - - sortingModeGroup - + + + + + + + Rename metadata only + + + + + Rename physical directory + + + + + Ask everytime + + @@ -674,7 +701,7 @@ numberOfConcurrentDownloadsSpinBox numberOfManualRetriesSpinBox timeoutSecondsSpinBox - sortLastLaunchedBtn + sortLastLaunchedComboBox sortByNameBtn catOpacitySpinBox preferMenuBarCheckBox @@ -686,7 +713,4 @@ - - - \ No newline at end of file From 4b20e3bc3985c6de37120a462d32c8fa33e41fa2 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 03:10:25 +0800 Subject: [PATCH 054/106] Implement instance renaming Signed-off-by: Yihe Li --- launcher/InstanceList.cpp | 7 ++- launcher/InstanceList.h | 1 + launcher/ui/MainWindow.cpp | 63 +++++++++++++++++++++++ launcher/ui/MainWindow.h | 2 + launcher/ui/pages/global/LauncherPage.cpp | 6 +-- launcher/ui/pages/global/LauncherPage.ui | 2 - 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..a9e86cd7f 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -149,6 +149,11 @@ QStringList InstanceList::getLinkedInstancesById(const QString& id) const return linkedInstances; } +QString InstanceList::getInstanceRootById(const InstanceId& id) const +{ + return FS::PathCombine(m_instDir, id); +} + int InstanceList::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); @@ -627,7 +632,7 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) loadGroupList(); } - auto instanceRoot = FS::PathCombine(m_instDir, id); + auto instanceRoot = getInstanceRootById(id); auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index c85fe55c7..b04a289d2 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -152,6 +152,7 @@ class InstanceList : public QAbstractListModel { QMimeData* mimeData(const QModelIndexList& indexes) const override; QStringList getLinkedInstancesById(const QString& id) const; + QString getInstanceRootById(const InstanceId& id) const; signals: void dataIsInvalid(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a9473ac15..f413ac05d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -564,6 +565,67 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos) myMenu.exec(view->mapToGlobal(pos)); } +void MainWindow::updateInstanceRoot() +{ + QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); + if (renamingMode == "MetadataOnly") + return; + + auto oldRoot = m_selectedInstance->instanceRoot(); + auto newID = m_selectedInstance->name(); + auto newRoot = APPLICATION->instances()->getInstanceRootById(newID); + if (oldRoot == newRoot) + return; + + // Check for conflict + if (QDir(newRoot).exists()) { + QMessageBox::warning(this, tr("Cannot rename instance"), + tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); + return; + } + + // Ask if we should rename + if (renamingMode == "AskEverytime") { + QMessageBox messageBox(this); + messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); + messageBox.setInformativeText(tr("The following renaming operation will be performed:
" + " - Old instance root: %1
" + " - New instance root: %2") + .arg(oldRoot, newRoot)); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + messageBox.setIcon(QMessageBox::Question); + + auto checkBox = new QCheckBox(tr("&Remember my choice"), this); + checkBox->setChecked(true); + messageBox.setCheckBox(checkBox); + + auto res = messageBox.exec(); + if (checkBox->isChecked()) { + if (res == QMessageBox::Yes) + APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); + else + APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); + } + if (res == QMessageBox::No) + return; + } + + // Now we can confirm that a renaming is happening + auto ret = QFile::rename(oldRoot, newRoot); + if (!ret) { + QMessageBox::warning(this, tr("Cannot rename instance"), + tr("An error occurred when performing the following renaming operation:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") + .arg(oldRoot, newRoot)); + return; + } + refreshInstances(); + setSelectedInstanceById(newID); +} + void MainWindow::updateMainToolBar() { ui->menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); @@ -1703,6 +1765,7 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); + updateInstanceRoot(); } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0e692eda7..9ac1d39e0 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -176,6 +176,8 @@ class MainWindow : public QMainWindow { void showInstanceContextMenu(const QPoint&); + void updateInstanceRoot(); + void updateMainToolBar(); void updateLaunchButton(); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index fab1bfe38..db37d548a 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -241,7 +241,7 @@ void LauncherPage::applySettings() s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); // Instance - auto sortMode = (InstSortMode) ui->viewSortingComboBox->currentIndex(); + auto sortMode = (InstSortMode)ui->viewSortingComboBox->currentIndex(); switch (sortMode) { case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); @@ -252,7 +252,7 @@ void LauncherPage::applySettings() break; } - auto renamingMode = (InstRenamingMode) ui->renamingBehaviorComboBox->currentIndex(); + auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex(); switch (renamingMode) { case Rename_Metadata: s->set("InstRenamingMode", "MetadataOnly"); @@ -334,7 +334,7 @@ void LauncherPage::loadSettings() InstRenamingMode renamingModeEnum; if (renamingMode == "MetadataOnly") { renamingModeEnum = Rename_Metadata; - } else if (renamingMode == "PhysicalDir"){ + } else if (renamingMode == "PhysicalDir") { renamingModeEnum = Rename_Physical; } else { renamingModeEnum = Rename_Ask; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 01aad7af7..c1ba1d428 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -701,8 +701,6 @@ numberOfConcurrentDownloadsSpinBox numberOfManualRetriesSpinBox timeoutSecondsSpinBox - sortLastLaunchedComboBox - sortByNameBtn catOpacitySpinBox preferMenuBarCheckBox lineLimitSpinBox From abac3db125ee462b117a378a84130a8a20476921 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 04:11:29 +0800 Subject: [PATCH 055/106] Make remember checkbox off by default Signed-off-by: Yihe Li --- launcher/ui/MainWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f413ac05d..0e6deacee 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -597,7 +597,6 @@ void MainWindow::updateInstanceRoot() messageBox.setIcon(QMessageBox::Question); auto checkBox = new QCheckBox(tr("&Remember my choice"), this); - checkBox->setChecked(true); messageBox.setCheckBox(checkBox); auto res = messageBox.exec(); From 7ea5b6173c14a269eb544de00661f38d3c6a8c33 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 04:51:13 +0800 Subject: [PATCH 056/106] Refactor updateInstanceRoot() to BaseInstance Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 60 ++++++++++++++++++++++++++++++++++ launcher/BaseInstance.h | 3 ++ launcher/InstanceList.cpp | 7 +--- launcher/InstanceList.h | 1 - launcher/ui/MainWindow.cpp | 66 +++----------------------------------- launcher/ui/MainWindow.h | 2 -- 6 files changed, 69 insertions(+), 70 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index ccfd0b847..886075624 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -37,11 +37,13 @@ #include "BaseInstance.h" +#include #include #include #include #include #include +#include #include #include "settings/INISettingsObject.h" @@ -327,6 +329,64 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } +bool BaseInstance::updateInstanceRoot(QWidget* parent) +{ + QString renamingMode = globalSettings()->get("InstRenamingMode").toString(); + if (renamingMode == "MetadataOnly") + return false; + + auto oldRoot = instanceRoot(); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); + if (oldRoot == newRoot) + return false; + + // Check for conflict + if (QDir(newRoot).exists()) { + QMessageBox::warning(parent, tr("Cannot rename instance"), + tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); + return false; + } + + // Ask if we should rename + if (renamingMode == "AskEverytime") { + QMessageBox messageBox(parent); + messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); + messageBox.setInformativeText(tr("The following renaming operation will be performed:
" + " - Old instance root: %1
" + " - New instance root: %2") + .arg(oldRoot, newRoot)); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + messageBox.setIcon(QMessageBox::Question); + + auto checkBox = new QCheckBox(tr("&Remember my choice"), parent); + messageBox.setCheckBox(checkBox); + + auto res = messageBox.exec(); + if (checkBox->isChecked()) { + if (res == QMessageBox::Yes) + globalSettings()->set("InstRenamingMode", "PhysicalDir"); + else + globalSettings()->set("InstRenamingMode", "MetadataOnly"); + } + if (res == QMessageBox::No) + return false; + } + + // Now we can confirm that a renaming is happening + auto ret = QFile::rename(oldRoot, newRoot); + if (!ret) { + QMessageBox::warning(parent, tr("Cannot rename instance"), + tr("An error occurred when performing the following renaming operation:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") + .arg(oldRoot, newRoot)); + return false; + } + return true; +} + SettingsObjectPtr BaseInstance::settings() { loadSpecificSettings(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9827a08b4..8efb9e9d8 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -117,6 +117,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index b04a289d2..c85fe55c7 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -152,7 +152,6 @@ class InstanceList : public QAbstractListModel { QMimeData* mimeData(const QModelIndexList& indexes) const override; QStringList getLinkedInstancesById(const QString& id) const; - QString getInstanceRootById(const InstanceId& id) const; signals: void dataIsInvalid(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 0e6deacee..17e5a0bc9 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -565,66 +565,6 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos) myMenu.exec(view->mapToGlobal(pos)); } -void MainWindow::updateInstanceRoot() -{ - QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); - if (renamingMode == "MetadataOnly") - return; - - auto oldRoot = m_selectedInstance->instanceRoot(); - auto newID = m_selectedInstance->name(); - auto newRoot = APPLICATION->instances()->getInstanceRootById(newID); - if (oldRoot == newRoot) - return; - - // Check for conflict - if (QDir(newRoot).exists()) { - QMessageBox::warning(this, tr("Cannot rename instance"), - tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); - return; - } - - // Ask if we should rename - if (renamingMode == "AskEverytime") { - QMessageBox messageBox(this); - messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); - messageBox.setInformativeText(tr("The following renaming operation will be performed:
" - " - Old instance root: %1
" - " - New instance root: %2") - .arg(oldRoot, newRoot)); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - messageBox.setIcon(QMessageBox::Question); - - auto checkBox = new QCheckBox(tr("&Remember my choice"), this); - messageBox.setCheckBox(checkBox); - - auto res = messageBox.exec(); - if (checkBox->isChecked()) { - if (res == QMessageBox::Yes) - APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); - else - APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); - } - if (res == QMessageBox::No) - return; - } - - // Now we can confirm that a renaming is happening - auto ret = QFile::rename(oldRoot, newRoot); - if (!ret) { - QMessageBox::warning(this, tr("Cannot rename instance"), - tr("An error occurred when performing the following renaming operation:
" - " - Old instance root: %1
" - " - New instance root: %2
" - "Only the metadata is renamed.") - .arg(oldRoot, newRoot)); - return; - } - refreshInstances(); - setSelectedInstanceById(newID); -} - void MainWindow::updateMainToolBar() { ui->menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); @@ -1764,7 +1704,11 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - updateInstanceRoot(); + if (m_selectedInstance && m_selectedInstance->updateInstanceRoot(this)) { + auto newID = m_selectedInstance->name(); + refreshInstances(); + setSelectedInstanceById(newID); + } } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 9ac1d39e0..0e692eda7 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -176,8 +176,6 @@ class MainWindow : public QMainWindow { void showInstanceContextMenu(const QPoint&); - void updateInstanceRoot(); - void updateMainToolBar(); void updateLaunchButton(); From b550a6c5c488ca441199f1be16e492302267805e Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 05:20:58 +0800 Subject: [PATCH 057/106] Revert the radio button changes Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 8 +- launcher/ui/pages/global/LauncherPage.cpp | 27 +- launcher/ui/pages/global/LauncherPage.ui | 287 +++++++++++----------- 3 files changed, 166 insertions(+), 156 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 886075624..92353da10 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -336,6 +336,7 @@ bool BaseInstance::updateInstanceRoot(QWidget* parent) return false; auto oldRoot = instanceRoot(); + auto oldName = QFileInfo(oldRoot).baseName(); auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); if (oldRoot == newRoot) return false; @@ -350,11 +351,8 @@ bool BaseInstance::updateInstanceRoot(QWidget* parent) // Ask if we should rename if (renamingMode == "AskEverytime") { QMessageBox messageBox(parent); - messageBox.setText(tr("Do you want to also rename the instance\'s physical directory?")); - messageBox.setInformativeText(tr("The following renaming operation will be performed:
" - " - Old instance root: %1
" - " - New instance root: %2") - .arg(oldRoot, newRoot)); + messageBox.setText(tr("Would you also like to rename the instance folder?")); + messageBox.setInformativeText(tr("Renaming \'%1\' -> \'%2\'").arg(oldName, name())); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); messageBox.setDefaultButton(QMessageBox::Yes); messageBox.setIcon(QMessageBox::Question); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index db37d548a..d89a65a85 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -67,17 +67,20 @@ enum InstSortMode { enum InstRenamingMode { // Rename metadata only. - Rename_Metadata, - // Rename physical directory too. - Rename_Physical, + Rename_Always, // Ask everytime. - Rename_Ask + Rename_Ask, + // Rename physical directory too. + Rename_Never }; LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); + ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); + ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); + defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); m_languageModel = APPLICATION->translations(); @@ -241,7 +244,7 @@ void LauncherPage::applySettings() s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); // Instance - auto sortMode = (InstSortMode)ui->viewSortingComboBox->currentIndex(); + auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); switch (sortMode) { case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); @@ -254,10 +257,10 @@ void LauncherPage::applySettings() auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex(); switch (renamingMode) { - case Rename_Metadata: + case Rename_Always: s->set("InstRenamingMode", "MetadataOnly"); break; - case Rename_Physical: + case Rename_Never: s->set("InstRenamingMode", "PhysicalDir"); break; case Rename_Ask: @@ -322,20 +325,18 @@ void LauncherPage::loadSettings() // Instance QString sortMode = s->get("InstSortMode").toString(); - InstSortMode sortModeEnum; if (sortMode == "LastLaunch") { - sortModeEnum = Sort_LastLaunch; + ui->sortLastLaunchedBtn->setChecked(true); } else { - sortModeEnum = Sort_Name; + ui->sortByNameBtn->setChecked(true); } - ui->viewSortingComboBox->setCurrentIndex(sortModeEnum); QString renamingMode = s->get("InstRenamingMode").toString(); InstRenamingMode renamingModeEnum; if (renamingMode == "MetadataOnly") { - renamingModeEnum = Rename_Metadata; + renamingModeEnum = Rename_Always; } else if (renamingMode == "PhysicalDir") { - renamingModeEnum = Rename_Physical; + renamingModeEnum = Rename_Never; } else { renamingModeEnum = Rename_Ask; } diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index c1ba1d428..7942f4f4f 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -112,40 +112,76 @@ Folders - - + + - &Downloads: + I&nstances: - downloadsDirTextBox + instDirTextBox - - + + + + + Browse - - - - - - - - + + + - &Skins: - - - skinsDirTextBox + Rename instance folders + + + + + Never + + + + + Ask + + + + + Always + + + + + + + + &Mods: + + + modsDirTextBox + + + + + + + + + + Browse + + + + + &Icons: @@ -155,7 +191,81 @@ - + + + + + + + Browse + + + + + + + + &Java: + + + javaDirTextBox + + + + + + + + + + Browse + + + + + + + + &Skins: + + + skinsDirTextBox + + + + + + + + + + Browse + + + + + + + + &Downloads: + + + downloadsDirTextBox + + + + + + + + + + Browse + + + + + @@ -179,83 +289,6 @@ - - - - - - - &Java: - - - javaDirTextBox - - - - - - - &Mods: - - - modsDirTextBox - - - - - - - - - - - - - - - - Browse - - - - - - - Browse - - - - - - - Browse - - - - - - - I&nstances: - - - instDirTextBox - - - - - - - Browse - - - - - - - Browse - - -
@@ -401,59 +434,32 @@ - + true - Instance + Instance view sorting mode - - - + + + - Instance view sorting mode + &By last launched + + sortingModeGroup + - - - - - By last launched - - - - - By name - - - - - - + + - Instance renaming behavior + By &name - - - - - - - Rename metadata only - - - - - Rename physical directory - - - - - Ask everytime - - + + sortingModeGroup + @@ -701,6 +707,8 @@ numberOfConcurrentDownloadsSpinBox numberOfManualRetriesSpinBox timeoutSecondsSpinBox + sortLastLaunchedBtn + sortByNameBtn catOpacitySpinBox preferMenuBarCheckBox lineLimitSpinBox @@ -711,4 +719,7 @@ + + + \ No newline at end of file From 7b511f4c676392c077c9ae4b90de36464f0221a9 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 05:47:29 +0800 Subject: [PATCH 058/106] Refactor into InstanceDirUpdate.h Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 56 ------------------- launcher/BaseInstance.h | 3 -- launcher/CMakeLists.txt | 2 + launcher/InstanceDirUpdate.cpp | 99 ++++++++++++++++++++++++++++++++++ launcher/InstanceDirUpdate.h | 43 +++++++++++++++ launcher/ui/MainWindow.cpp | 3 +- 6 files changed, 146 insertions(+), 60 deletions(-) create mode 100644 launcher/InstanceDirUpdate.cpp create mode 100644 launcher/InstanceDirUpdate.h diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 92353da10..0087b44f0 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -329,62 +329,6 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } -bool BaseInstance::updateInstanceRoot(QWidget* parent) -{ - QString renamingMode = globalSettings()->get("InstRenamingMode").toString(); - if (renamingMode == "MetadataOnly") - return false; - - auto oldRoot = instanceRoot(); - auto oldName = QFileInfo(oldRoot).baseName(); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); - if (oldRoot == newRoot) - return false; - - // Check for conflict - if (QDir(newRoot).exists()) { - QMessageBox::warning(parent, tr("Cannot rename instance"), - tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); - return false; - } - - // Ask if we should rename - if (renamingMode == "AskEverytime") { - QMessageBox messageBox(parent); - messageBox.setText(tr("Would you also like to rename the instance folder?")); - messageBox.setInformativeText(tr("Renaming \'%1\' -> \'%2\'").arg(oldName, name())); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - messageBox.setIcon(QMessageBox::Question); - - auto checkBox = new QCheckBox(tr("&Remember my choice"), parent); - messageBox.setCheckBox(checkBox); - - auto res = messageBox.exec(); - if (checkBox->isChecked()) { - if (res == QMessageBox::Yes) - globalSettings()->set("InstRenamingMode", "PhysicalDir"); - else - globalSettings()->set("InstRenamingMode", "MetadataOnly"); - } - if (res == QMessageBox::No) - return false; - } - - // Now we can confirm that a renaming is happening - auto ret = QFile::rename(oldRoot, newRoot); - if (!ret) { - QMessageBox::warning(parent, tr("Cannot rename instance"), - tr("An error occurred when performing the following renaming operation:
" - " - Old instance root: %1
" - " - New instance root: %2
" - "Only the metadata is renamed.") - .arg(oldRoot, newRoot)); - return false; - } - return true; -} - SettingsObjectPtr BaseInstance::settings() { loadSpecificSettings(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 8efb9e9d8..9827a08b4 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -117,9 +117,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "InstanceDirUpdate.h" + +#include +#include + +#include "FileSystem.h" + +bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent) +{ + QString renamingMode = globalSettings->get("InstRenamingMode").toString(); + if (renamingMode == "MetadataOnly") + return false; + + auto oldRoot = instance->instanceRoot(); + auto oldName = QFileInfo(oldRoot).baseName(); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), instance->name()); + if (oldRoot == newRoot) + return false; + + // Check for conflict + if (QDir(newRoot).exists()) { + QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), + QObject::tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); + return false; + } + + // Ask if we should rename + if (renamingMode == "AskEverytime") { + QMessageBox messageBox(parent); + messageBox.setText(QObject::tr("Would you also like to rename the instance folder?")); + messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, instance->name())); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + messageBox.setIcon(QMessageBox::Question); + + auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent); + messageBox.setCheckBox(checkBox); + + auto res = messageBox.exec(); + if (checkBox->isChecked()) { + if (res == QMessageBox::Yes) + globalSettings->set("InstRenamingMode", "PhysicalDir"); + else + globalSettings->set("InstRenamingMode", "MetadataOnly"); + } + if (res == QMessageBox::No) + return false; + } + + // Now we can confirm that a renaming is happening + auto ret = QFile::rename(oldRoot, newRoot); + if (!ret) { + QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), + QObject::tr("An error occurred when performing the following renaming operation:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") + .arg(oldRoot, newRoot)); + return false; + } + return true; +} diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h new file mode 100644 index 000000000..fc0931779 --- /dev/null +++ b/launcher/InstanceDirUpdate.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance.h" + +/// Update instanceRoot to make it sync with name/id; return true if a refresh is needed +bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 17e5a0bc9..23cd6f8c8 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -124,6 +124,7 @@ #include "KonamiCode.h" #include "InstanceCopyTask.h" +#include "InstanceDirUpdate.h" #include "Json.h" @@ -1704,7 +1705,7 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - if (m_selectedInstance && m_selectedInstance->updateInstanceRoot(this)) { + if (m_selectedInstance && askToUpdateInstanceDirName(APPLICATION->settings(), m_selectedInstance, this)) { auto newID = m_selectedInstance->name(); refreshInstances(); setSelectedInstanceById(newID); From ea44c2465c30ad048df7047007059d668edd6cfb Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 05:47:47 +0800 Subject: [PATCH 059/106] Add link instance detection Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 41 ++++++++++++++++++++++++++++------ launcher/InstanceDirUpdate.h | 5 ++++- launcher/ui/MainWindow.cpp | 18 +++------------ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index f24adb642..0b61ddb88 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -40,11 +40,15 @@ #include #include +#include "Application.h" #include "FileSystem.h" -bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent) +#include "InstanceList.h" +#include "ui/dialogs/CustomMessageBox.h" + +bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) { - QString renamingMode = globalSettings->get("InstRenamingMode").toString(); + QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); if (renamingMode == "MetadataOnly") return false; @@ -76,24 +80,47 @@ bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr in auto res = messageBox.exec(); if (checkBox->isChecked()) { if (res == QMessageBox::Yes) - globalSettings->set("InstRenamingMode", "PhysicalDir"); + APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); else - globalSettings->set("InstRenamingMode", "MetadataOnly"); + APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); } if (res == QMessageBox::No) return false; } + // Check for linked instances + if (!checkLinkedInstances(instance->id(), parent)) + return false; + // Now we can confirm that a renaming is happening auto ret = QFile::rename(oldRoot, newRoot); if (!ret) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("An error occurred when performing the following renaming operation:
" - " - Old instance root: %1
" - " - New instance root: %2
" - "Only the metadata is renamed.") + " - Old instance root: %1
" + " - New instance root: %2
" + "Only the metadata is renamed.") .arg(oldRoot, newRoot)); return false; } return true; } + +bool checkLinkedInstances(const QString& id, QWidget* parent) +{ + auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); + if (!linkedInstances.empty()) { + auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"), + QObject::tr("The following instance(s) might reference files in this instance:\n\n" + "%1\n\n" + "Deleting it could break the other instance(s), \n\n" + "Do you wish to proceed?", + nullptr, linkedInstances.count()) + .arg(linkedInstances.join("\n")), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + if (response != QMessageBox::Yes) + return false; + } + return true; +} diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index fc0931779..0059d8b7b 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -40,4 +40,7 @@ #include "BaseInstance.h" /// Update instanceRoot to make it sync with name/id; return true if a refresh is needed -bool askToUpdateInstanceDirName(SettingsObjectPtr globalSettings, InstancePtr instance, QWidget* parent); +bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); + +/// Check if there are linked instances, and display a warning; return true if the operation should proceed +bool checkLinkedInstances(const QString& id, QWidget* parent); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 23cd6f8c8..d5a2b477d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1379,20 +1379,8 @@ void MainWindow::on_actionDeleteInstance_triggered() if (response != QMessageBox::Yes) return; - auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); - if (!linkedInstances.empty()) { - response = CustomMessageBox::selectable(this, tr("There are linked instances"), - tr("The following instance(s) might reference files in this instance:\n\n" - "%1\n\n" - "Deleting it could break the other instance(s), \n\n" - "Do you wish to proceed?", - nullptr, linkedInstances.count()) - .arg(linkedInstances.join("\n")), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); - if (response != QMessageBox::Yes) - return; - } + if (!checkLinkedInstances(id, this)) + return; if (APPLICATION->instances()->trashInstance(id)) { ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); @@ -1705,7 +1693,7 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - if (m_selectedInstance && askToUpdateInstanceDirName(APPLICATION->settings(), m_selectedInstance, this)) { + if (m_selectedInstance && askToUpdateInstanceDirName(m_selectedInstance, this)) { auto newID = m_selectedInstance->name(); refreshInstances(); setSelectedInstanceById(newID); From 8fea37b8b73f09d35d73e54f059d62dd7b6883ae Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 06:53:57 +0800 Subject: [PATCH 060/106] Only call on interactive uses Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 11 +++++++++++ launcher/BaseInstance.h | 3 +++ launcher/InstanceDirUpdate.cpp | 3 +-- launcher/ui/MainWindow.cpp | 12 +++++++----- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0087b44f0..5ffbb809b 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -388,6 +388,17 @@ void BaseInstance::setName(QString val) emit propertiesChanged(this); } +bool BaseInstance::syncInstanceDirName() const +{ + auto oldRoot = instanceRoot(); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); + if (oldRoot == newRoot) + return true; + if (!QFile::rename(oldRoot, newRoot)) + return false; + return true; +} + QString BaseInstance::name() const { return m_settings->get("name").toString(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9827a08b4..b19f1fd27 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -126,6 +126,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_thissyncInstanceDirName()) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("An error occurred when performing the following renaming operation:
" " - Old instance root: %1
" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d5a2b477d..f357b0765 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1428,6 +1428,13 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered() void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) { + connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { + if (askToUpdateInstanceDirName(m_selectedInstance, this)) { + auto newID = m_selectedInstance->name(); + refreshInstances(); + setSelectedInstanceById(newID); + } + }, Qt::SingleShotConnection); view->edit(view->currentIndex()); } } @@ -1693,11 +1700,6 @@ void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelInd QItemSelection test(topLeft, bottomRight); if (test.contains(current)) { instanceChanged(current, current); - if (m_selectedInstance && askToUpdateInstanceDirName(m_selectedInstance, this)) { - auto newID = m_selectedInstance->name(); - refreshInstances(); - setSelectedInstanceById(newID); - } } } From 294448a01e07f44e0df99f67541f2a18e24069ac Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 06:54:10 +0800 Subject: [PATCH 061/106] Filter out invalid chars Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 3 +-- launcher/BaseInstance.h | 2 +- launcher/InstanceDirUpdate.cpp | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 5ffbb809b..92d41521b 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -388,10 +388,9 @@ void BaseInstance::setName(QString val) emit propertiesChanged(this); } -bool BaseInstance::syncInstanceDirName() const +bool BaseInstance::syncInstanceDirName(const QString& newRoot) const { auto oldRoot = instanceRoot(); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), name()); if (oldRoot == newRoot) return true; if (!QFile::rename(oldRoot, newRoot)) diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index b19f1fd27..2a2b4dc4a 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -127,7 +127,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_thisinstanceRoot(); auto oldName = QFileInfo(oldRoot).baseName(); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), instance->name()); + auto newName = FS::DirNameFromString(instance->name(), QFileInfo(oldRoot).dir().absolutePath()); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName); if (oldRoot == newRoot) return false; @@ -69,7 +70,7 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) if (renamingMode == "AskEverytime") { QMessageBox messageBox(parent); messageBox.setText(QObject::tr("Would you also like to rename the instance folder?")); - messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, instance->name())); + messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, newName)); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); messageBox.setDefaultButton(QMessageBox::Yes); messageBox.setIcon(QMessageBox::Question); @@ -93,7 +94,7 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) return false; // Now we can confirm that a renaming is happening - if (!instance->syncInstanceDirName()) { + if (!instance->syncInstanceDirName(newRoot)) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("An error occurred when performing the following renaming operation:
" " - Old instance root: %1
" From a7af120cf0635f048e5df18e6ca6c1e853071825 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 07:22:05 +0800 Subject: [PATCH 062/106] Fix interaction with invalid chars Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 19 +++++++++++-------- launcher/InstanceDirUpdate.h | 4 ++-- launcher/InstanceList.cpp | 17 +++++++++++++++++ launcher/InstanceList.h | 2 ++ launcher/ui/MainWindow.cpp | 25 ++++++++++++++++++------- launcher/ui/MainWindow.h | 1 + 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index a6ca6ca23..8064082fd 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -46,24 +46,27 @@ #include "InstanceList.h" #include "ui/dialogs/CustomMessageBox.h" -bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) +QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) { QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); if (renamingMode == "MetadataOnly") - return false; + return QString(); auto oldRoot = instance->instanceRoot(); auto oldName = QFileInfo(oldRoot).baseName(); + if (oldName == FS::RemoveInvalidFilenameChars(instance->name(), '-')) + return QString(); + auto newName = FS::DirNameFromString(instance->name(), QFileInfo(oldRoot).dir().absolutePath()); auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName); if (oldRoot == newRoot) - return false; + return QString(); // Check for conflict if (QDir(newRoot).exists()) { QMessageBox::warning(parent, QObject::tr("Cannot rename instance"), QObject::tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot)); - return false; + return QString(); } // Ask if we should rename @@ -86,12 +89,12 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly"); } if (res == QMessageBox::No) - return false; + return QString(); } // Check for linked instances if (!checkLinkedInstances(instance->id(), parent)) - return false; + return QString(); // Now we can confirm that a renaming is happening if (!instance->syncInstanceDirName(newRoot)) { @@ -101,9 +104,9 @@ bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) " - New instance root: %2
" "Only the metadata is renamed.") .arg(oldRoot, newRoot)); - return false; + return QString(); } - return true; + return newRoot; } bool checkLinkedInstances(const QString& id, QWidget* parent) diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 0059d8b7b..23738a19a 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -39,8 +39,8 @@ #include "BaseInstance.h" -/// Update instanceRoot to make it sync with name/id; return true if a refresh is needed -bool askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); +/// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened +QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed bool checkLinkedInstances(const QString& id, QWidget* parent); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..a4f694e2e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -583,6 +583,18 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } +InstancePtr InstanceList::getInstanceByRoot(QString instanceRoot) const +{ + if (instanceRoot.isEmpty()) + return InstancePtr(); + for (auto& inst : m_instances) { + if (inst->instanceRoot() == instanceRoot) { + return inst; + } + } + return InstancePtr(); +} + InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) @@ -601,6 +613,11 @@ QModelIndex InstanceList::getInstanceIndexById(const QString& id) const return index(getInstIndex(getInstanceById(id).get())); } +QModelIndex InstanceList::getInstanceIndexByRoot(const QString& instanceRoot) const +{ + return index(getInstIndex(getInstanceByRoot(instanceRoot).get())); +} + int InstanceList::getInstIndex(BaseInstance* inst) const { int count = m_instances.count(); diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index c85fe55c7..dace9e5cf 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -99,9 +99,11 @@ class InstanceList : public QAbstractListModel { /* O(n) */ InstancePtr getInstanceById(QString id) const; + InstancePtr getInstanceByRoot(QString instanceRoot) const; /* O(n) */ InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString& id) const; + QModelIndex getInstanceIndexByRoot(const QString& instanceRoot) const; QStringList getGroups(); bool isGroupCollapsed(const QString& groupName); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f357b0765..43fe5c067 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -294,6 +294,12 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi view->setFrameShape(QFrame::NoFrame); // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); + connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { + if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, this); !newRoot.isEmpty()) { + refreshInstances(); + setSelectedInstanceByRoot(newRoot); + } + }); view->installEventFilter(this); view->setContextMenuPolicy(Qt::CustomContextMenu); @@ -1133,6 +1139,18 @@ void MainWindow::setSelectedInstanceById(const QString& id) } } +void MainWindow::setSelectedInstanceByRoot(const QString& instanceRoot) +{ + if (instanceRoot.isNull()) + return; + const QModelIndex index = APPLICATION->instances()->getInstanceIndexByRoot(instanceRoot); + if (index.isValid()) { + QModelIndex selectionIndex = proxymodel->mapFromSource(index); + view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); + updateStatusCenter(); + } +} + void MainWindow::on_actionChangeInstGroup_triggered() { if (!m_selectedInstance) @@ -1428,13 +1446,6 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered() void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) { - connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { - if (askToUpdateInstanceDirName(m_selectedInstance, this)) { - auto newID = m_selectedInstance->name(); - refreshInstances(); - setSelectedInstanceById(newID); - } - }, Qt::SingleShotConnection); view->edit(view->currentIndex()); } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0e692eda7..02e9bb31e 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -224,6 +224,7 @@ class MainWindow : public QMainWindow { void setCatBackground(bool enabled); void updateInstanceToolIcon(QString new_icon); void setSelectedInstanceById(const QString& id); + void setSelectedInstanceByRoot(const QString& instanceRoot); void updateStatusCenter(); void setInstanceActionsEnabled(bool enabled); From 45368fbf2f93aba8d224058d4ee53284e28e6f23 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 07:31:24 +0800 Subject: [PATCH 063/106] Get rid of unused includes Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 2 -- launcher/ui/MainWindow.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 92d41521b..114275b3c 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -37,13 +37,11 @@ #include "BaseInstance.h" -#include #include #include #include #include #include -#include #include #include "settings/INISettingsObject.h" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 43fe5c067..05afea0b4 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include From 11a0dbf81059a39521d12c43fde7c5f07175ada2 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 15:59:10 +0800 Subject: [PATCH 064/106] Make deleting a verb Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 9 +++++---- launcher/InstanceDirUpdate.h | 2 +- launcher/ui/MainWindow.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 8064082fd..49010e6c9 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -93,7 +93,7 @@ QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) } // Check for linked instances - if (!checkLinkedInstances(instance->id(), parent)) + if (!checkLinkedInstances(instance->id(), parent, QObject::tr("Renaming"))) return QString(); // Now we can confirm that a renaming is happening @@ -109,17 +109,18 @@ QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) return newRoot; } -bool checkLinkedInstances(const QString& id, QWidget* parent) +bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb) { auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); if (!linkedInstances.empty()) { auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"), QObject::tr("The following instance(s) might reference files in this instance:\n\n" "%1\n\n" - "Deleting it could break the other instance(s), \n\n" + "%2 it could break the other instance(s), \n\n" "Do you wish to proceed?", nullptr, linkedInstances.count()) - .arg(linkedInstances.join("\n")), + .arg(linkedInstances.join("\n")) + .arg(verb), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); if (response != QMessageBox::Yes) diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 23738a19a..354eba465 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -43,4 +43,4 @@ QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed -bool checkLinkedInstances(const QString& id, QWidget* parent); +bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 05afea0b4..7e9c72da7 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1396,7 +1396,7 @@ void MainWindow::on_actionDeleteInstance_triggered() if (response != QMessageBox::Yes) return; - if (!checkLinkedInstances(id, this)) + if (!checkLinkedInstances(id, this, tr("Deleting"))) return; if (APPLICATION->instances()->trashInstance(id)) { From a2e44c0ef76a73befc9c31e243b675d8df6eb751 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 17:13:38 +0800 Subject: [PATCH 065/106] Use custom signals to record previous name Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 15 ++++++------- launcher/InstanceDirUpdate.h | 2 +- launcher/InstanceList.cpp | 17 --------------- launcher/InstanceList.h | 2 -- launcher/ui/MainWindow.cpp | 21 +++++-------------- launcher/ui/MainWindow.h | 1 - launcher/ui/instanceview/InstanceDelegate.cpp | 1 + launcher/ui/instanceview/InstanceDelegate.h | 3 +++ 8 files changed, 18 insertions(+), 44 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 49010e6c9..ac6dddc37 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -46,21 +46,22 @@ #include "InstanceList.h" #include "ui/dialogs/CustomMessageBox.h" -QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent) +QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent) { + if (oldName == newName) + return QString(); + QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString(); if (renamingMode == "MetadataOnly") return QString(); auto oldRoot = instance->instanceRoot(); - auto oldName = QFileInfo(oldRoot).baseName(); - if (oldName == FS::RemoveInvalidFilenameChars(instance->name(), '-')) - return QString(); - - auto newName = FS::DirNameFromString(instance->name(), QFileInfo(oldRoot).dir().absolutePath()); - auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName); + auto newDirName = FS::DirNameFromString(newName, QFileInfo(oldRoot).dir().absolutePath()); + auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newDirName); if (oldRoot == newRoot) return QString(); + if (oldRoot == FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName)) + return QString(); // Check for conflict if (QDir(newRoot).exists()) { diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 354eba465..94dd3b933 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -40,7 +40,7 @@ #include "BaseInstance.h" /// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened -QString askToUpdateInstanceDirName(InstancePtr instance, QWidget* parent); +QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index a4f694e2e..918fa1073 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -583,18 +583,6 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } -InstancePtr InstanceList::getInstanceByRoot(QString instanceRoot) const -{ - if (instanceRoot.isEmpty()) - return InstancePtr(); - for (auto& inst : m_instances) { - if (inst->instanceRoot() == instanceRoot) { - return inst; - } - } - return InstancePtr(); -} - InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) @@ -613,11 +601,6 @@ QModelIndex InstanceList::getInstanceIndexById(const QString& id) const return index(getInstIndex(getInstanceById(id).get())); } -QModelIndex InstanceList::getInstanceIndexByRoot(const QString& instanceRoot) const -{ - return index(getInstIndex(getInstanceByRoot(instanceRoot).get())); -} - int InstanceList::getInstIndex(BaseInstance* inst) const { int count = m_instances.count(); diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index dace9e5cf..c85fe55c7 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -99,11 +99,9 @@ class InstanceList : public QAbstractListModel { /* O(n) */ InstancePtr getInstanceById(QString id) const; - InstancePtr getInstanceByRoot(QString instanceRoot) const; /* O(n) */ InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString& id) const; - QModelIndex getInstanceIndexByRoot(const QString& instanceRoot) const; QStringList getGroups(); bool isGroupCollapsed(const QString& groupName); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7e9c72da7..6c5f86d0a 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -289,14 +289,15 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi view->setSelectionMode(QAbstractItemView::SingleSelection); // FIXME: leaks ListViewDelegate - view->setItemDelegate(new ListViewDelegate(this)); + auto delegate = new ListViewDelegate(this); + view->setItemDelegate(delegate); view->setFrameShape(QFrame::NoFrame); // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); - connect(view->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, [this] { - if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, this); !newRoot.isEmpty()) { + connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) { + if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) { refreshInstances(); - setSelectedInstanceByRoot(newRoot); + setSelectedInstanceById(QFileInfo(newRoot).fileName()); } }); @@ -1138,18 +1139,6 @@ void MainWindow::setSelectedInstanceById(const QString& id) } } -void MainWindow::setSelectedInstanceByRoot(const QString& instanceRoot) -{ - if (instanceRoot.isNull()) - return; - const QModelIndex index = APPLICATION->instances()->getInstanceIndexByRoot(instanceRoot); - if (index.isValid()) { - QModelIndex selectionIndex = proxymodel->mapFromSource(index); - view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); - updateStatusCenter(); - } -} - void MainWindow::on_actionChangeInstGroup_triggered() { if (!m_selectedInstance) diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 02e9bb31e..0e692eda7 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -224,7 +224,6 @@ class MainWindow : public QMainWindow { void setCatBackground(bool enabled); void updateInstanceToolIcon(QString new_icon); void setSelectedInstanceById(const QString& id); - void setSelectedInstanceByRoot(const QString& instanceRoot); void updateStatusCenter(); void setInstanceActionsEnabled(bool enabled); diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index d947163bc..c7115801e 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -397,6 +397,7 @@ void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, // Prevent instance names longer than 128 chars text.truncate(128); if (text.size() != 0) { + emit textChanged(model->data(index).toString(), text); model->setData(index, text); } } diff --git a/launcher/ui/instanceview/InstanceDelegate.h b/launcher/ui/instanceview/InstanceDelegate.h index 69dd32ba7..98ff9a2fc 100644 --- a/launcher/ui/instanceview/InstanceDelegate.h +++ b/launcher/ui/instanceview/InstanceDelegate.h @@ -33,6 +33,9 @@ class ListViewDelegate : public QStyledItemDelegate { void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + signals: + void textChanged(QString before, QString after) const; + private slots: void editingDone(); }; From 694959ef7f56e5f8d0f7918b61bd059483707cab Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 27 Mar 2025 20:25:30 +0800 Subject: [PATCH 066/106] Remove names from header comments Signed-off-by: Yihe Li --- launcher/InstanceDirUpdate.cpp | 3 --- launcher/InstanceDirUpdate.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index ac6dddc37..65a62129b 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -1,9 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2023 TheKodeToad * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 94dd3b933..b92a59c4c 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -1,9 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2023 TheKodeToad * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 02ca6bea8c4261776500862159d61817abf246cc Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sat, 29 Mar 2025 14:11:09 +0800 Subject: [PATCH 067/106] Sync group after updating instance Signed-off-by: Yihe Li --- launcher/ui/MainWindow.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 6c5f86d0a..ddf726373 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -296,8 +296,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi view->setAttribute(Qt::WA_MacShowFocusRect, false); connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) { if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) { + auto oldID = m_selectedInstance->id(); + auto newID = QFileInfo(newRoot).fileName(); + QString origGroup(APPLICATION->instances()->getInstanceGroup(oldID)); + bool syncGroup = origGroup != GroupId() && oldID != newID; + if (syncGroup) + APPLICATION->instances()->setInstanceGroup(oldID, GroupId()); + refreshInstances(); - setSelectedInstanceById(QFileInfo(newRoot).fileName()); + setSelectedInstanceById(newID); + + if (syncGroup) + APPLICATION->instances()->setInstanceGroup(newID, origGroup); } }); From 66c6399adedcb52c2b7391cdbf38602f8b2814a6 Mon Sep 17 00:00:00 2001 From: LAHarbottle <87842870+LAHarbottle@users.noreply.github.com> Date: Sat, 29 Mar 2025 17:17:57 +0000 Subject: [PATCH 068/106] Fix typo in NetRequest.cpp Signed-off-by: LAHarbottle <87842870+LAHarbottle@users.noreply.github.com> --- launcher/net/NetRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index cf2e72858..310653508 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -80,7 +80,7 @@ void NetRequest::executeTask() emit finished(); return; case State::Running: - qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString(); + qCDebug(logCat) << getUid().toString() << "Running " << m_url.toString(); break; case State::Inactive: case State::Failed: From 5b12d3cfff9092c0364e455c211fc189d87039cd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 30 Mar 2025 00:46:59 +0200 Subject: [PATCH 069/106] fix themes leak Signed-off-by: Trial97 --- launcher/ui/themes/SystemTheme.cpp | 36 ++++++++++++++++-------------- launcher/ui/themes/SystemTheme.h | 6 ++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 59644ddde..c755b379b 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -42,16 +42,18 @@ SystemTheme::SystemTheme(const QString& styleName, bool isDefaultTheme) { - themeName = isDefaultTheme ? "system" : styleName; - widgetTheme = styleName; - colorPalette = QStyleFactory::create(styleName)->standardPalette(); + m_themeName = isDefaultTheme ? "system" : styleName; + m_widgetTheme = styleName; + auto style = QStyleFactory::create(styleName); + m_colorPalette = style->standardPalette(); + delete style; } void SystemTheme::apply(bool initial) { // See https://github.com/MultiMC/Launcher/issues/1790 // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial && themeName == "system") { + if (initial && m_themeName == "system") { QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); return; } @@ -61,35 +63,35 @@ void SystemTheme::apply(bool initial) QString SystemTheme::id() { - return themeName; + return m_themeName; } QString SystemTheme::name() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Windows Vista"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("Windows 11"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("System"); } else { - return themeName; + return m_themeName; } } QString SystemTheme::tooltip() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Widget style trying to look like your win32 theme"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x inspired widget style"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("WinUI 3 inspired Qt widget style"); - } else if (themeName.toLower() == "fusion") { + } else if (m_themeName.toLower() == "fusion") { return QObject::tr("The default Qt widget style"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("Your current system theme"); } else { return ""; @@ -98,12 +100,12 @@ QString SystemTheme::tooltip() QString SystemTheme::qtTheme() { - return widgetTheme; + return m_widgetTheme; } QPalette SystemTheme::colorScheme() { - return colorPalette; + return m_colorPalette; } QString SystemTheme::appStyleSheet() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 09db1a322..54404d052 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -53,7 +53,7 @@ class SystemTheme : public ITheme { QColor fadeColor() override; private: - QPalette colorPalette; - QString widgetTheme; - QString themeName; + QPalette m_colorPalette; + QString m_widgetTheme; + QString m_themeName; }; From 0487ab375415e535daefa0c3f78c1042b3b5060c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 30 Mar 2025 00:27:40 +0000 Subject: [PATCH 070/106] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/a84ebe20c6bc2ecbcfb000a50776219f48d134cc?narHash=sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ%3D' (2025-03-19) → 'github:NixOS/nixpkgs/5e5402ecbcb27af32284d4a62553c019a3a49ea6?narHash=sha256-gWd4urRoLRe8GLVC/3rYRae1h%2BxfQzt09xOfb0PaHSk%3D' (2025-03-27) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index ef4c9f555..fa30cfd33 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1742422364, - "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=", + "lastModified": 1743095683, + "narHash": "sha256-gWd4urRoLRe8GLVC/3rYRae1h+xfQzt09xOfb0PaHSk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc", + "rev": "5e5402ecbcb27af32284d4a62553c019a3a49ea6", "type": "github" }, "original": { From da007d62123b3022f1afd09a7a3577405e5e4310 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 31 Mar 2025 07:11:05 +0800 Subject: [PATCH 071/106] Use CustomMessageBox::selectable Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 6 +----- launcher/InstanceDirUpdate.cpp | 18 ++++++++---------- launcher/ui/dialogs/CustomMessageBox.cpp | 5 ++++- launcher/ui/dialogs/CustomMessageBox.h | 3 ++- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 114275b3c..eab91a5eb 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -389,11 +389,7 @@ void BaseInstance::setName(QString val) bool BaseInstance::syncInstanceDirName(const QString& newRoot) const { auto oldRoot = instanceRoot(); - if (oldRoot == newRoot) - return true; - if (!QFile::rename(oldRoot, newRoot)) - return false; - return true; + return oldRoot == newRoot || QFile::rename(oldRoot, newRoot); } QString BaseInstance::name() const diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 65a62129b..8be0dccac 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -35,7 +35,6 @@ #include "InstanceDirUpdate.h" #include -#include #include "Application.h" #include "FileSystem.h" @@ -69,17 +68,16 @@ QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, // Ask if we should rename if (renamingMode == "AskEverytime") { - QMessageBox messageBox(parent); - messageBox.setText(QObject::tr("Would you also like to rename the instance folder?")); - messageBox.setInformativeText(QObject::tr("Renaming \'%1\' -> \'%2\'").arg(oldName, newName)); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - messageBox.setIcon(QMessageBox::Question); - auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent); - messageBox.setCheckBox(checkBox); + auto dialog = + CustomMessageBox::selectable(parent, QObject::tr("Rename instance folder"), + QObject::tr("Would you also like to rename the instance folder?\n\n" + "Old name: %1\n" + "New name: %2") + .arg(oldName, newName), + QMessageBox::Question, QMessageBox::No | QMessageBox::Yes, QMessageBox::NoButton, checkBox); - auto res = messageBox.exec(); + auto res = dialog->exec(); if (checkBox->isChecked()) { if (res == QMessageBox::Yes) APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir"); diff --git a/launcher/ui/dialogs/CustomMessageBox.cpp b/launcher/ui/dialogs/CustomMessageBox.cpp index 1af47a449..ca0fe99e0 100644 --- a/launcher/ui/dialogs/CustomMessageBox.cpp +++ b/launcher/ui/dialogs/CustomMessageBox.cpp @@ -21,7 +21,8 @@ QMessageBox* selectable(QWidget* parent, const QString& text, QMessageBox::Icon icon, QMessageBox::StandardButtons buttons, - QMessageBox::StandardButton defaultButton) + QMessageBox::StandardButton defaultButton, + QCheckBox* checkBox) { QMessageBox* messageBox = new QMessageBox(parent); messageBox->setWindowTitle(title); @@ -31,6 +32,8 @@ QMessageBox* selectable(QWidget* parent, messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); messageBox->setIcon(icon); messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + if (checkBox) + messageBox->setCheckBox(checkBox); return messageBox; } diff --git a/launcher/ui/dialogs/CustomMessageBox.h b/launcher/ui/dialogs/CustomMessageBox.h index a9bc6a24a..1ee29903e 100644 --- a/launcher/ui/dialogs/CustomMessageBox.h +++ b/launcher/ui/dialogs/CustomMessageBox.h @@ -23,5 +23,6 @@ QMessageBox* selectable(QWidget* parent, const QString& text, QMessageBox::Icon icon = QMessageBox::NoIcon, QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QCheckBox* checkBox = nullptr); } From 99852c972c6e9fc6a8caca727946a71a85c1672f Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 16:41:19 -0400 Subject: [PATCH 072/106] Reapply "refactor(nix): nix-filter -> lib.fileset" After extensive (5 minutes) of testing, it seems we don't actually come across any Nix bugs with lib.fileset! (aside from those inherit to it...but :shrug:) This reverts commit a49a58bc4571f87f42f45bb9eeb1a957d5d12331. Signed-off-by: Seth Flynn --- flake.lock | 16 ---------------- flake.nix | 4 ---- nix/unwrapped.nix | 22 +++++++++++----------- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/flake.lock b/flake.lock index fa30cfd33..24f04474e 100644 --- a/flake.lock +++ b/flake.lock @@ -32,21 +32,6 @@ "type": "github" } }, - "nix-filter": { - "locked": { - "lastModified": 1731533336, - "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", - "owner": "numtide", - "repo": "nix-filter", - "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "nix-filter", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1743095683, @@ -67,7 +52,6 @@ "inputs": { "flake-compat": "flake-compat", "libnbtplusplus": "libnbtplusplus", - "nix-filter": "nix-filter", "nixpkgs": "nixpkgs" } } diff --git a/flake.nix b/flake.nix index 150240c8b..517a4b2ff 100644 --- a/flake.nix +++ b/flake.nix @@ -16,8 +16,6 @@ flake = false; }; - nix-filter.url = "github:numtide/nix-filter"; - /* Inputs below this are optional and can be removed @@ -44,7 +42,6 @@ self, nixpkgs, libnbtplusplus, - nix-filter, ... }: let @@ -100,7 +97,6 @@ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { inherit libnbtplusplus - nix-filter self ; }; diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 7ba20b68b..0a4d2e253 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -11,7 +11,6 @@ kdePackages, libnbtplusplus, ninja, - nix-filter, self, stripJavaArchivesHook, tomlplusplus, @@ -29,17 +28,18 @@ stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; version = self.shortRev or self.dirtyShortRev or "unknown"; - src = nix-filter.lib { - root = self; - include = [ - "buildconfig" - "cmake" - "launcher" - "libraries" - "program_info" - "tests" - ../COPYING.md + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ ../CMakeLists.txt + ../COPYING.md + + ../buildconfig + ../cmake + ../launcher + ../libraries + ../program_info + ../tests ]; }; From c367f48ec99f048f8d4bcaf0634370be33b29c62 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 16:43:49 -0400 Subject: [PATCH 073/106] refactor(nix): pin flake-compat in default.nix Avoids polluting downstream flake.locks Signed-off-by: Seth Flynn --- default.nix | 13 ++++--------- flake.lock | 17 ----------------- flake.nix | 20 -------------------- nix/README.md | 6 ------ 4 files changed, 4 insertions(+), 52 deletions(-) diff --git a/default.nix b/default.nix index 6466507b7..5ecef5590 100644 --- a/default.nix +++ b/default.nix @@ -1,9 +1,4 @@ -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } -) { src = ./.; }).defaultNix +(import (fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz"; + sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU="; +}) { src = ./.; }).defaultNix diff --git a/flake.lock b/flake.lock index 24f04474e..d8c7c5248 100644 --- a/flake.lock +++ b/flake.lock @@ -1,21 +1,5 @@ { "nodes": { - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, "libnbtplusplus": { "flake": false, "locked": { @@ -50,7 +34,6 @@ }, "root": { "inputs": { - "flake-compat": "flake-compat", "libnbtplusplus": "libnbtplusplus", "nixpkgs": "nixpkgs" } diff --git a/flake.nix b/flake.nix index 517a4b2ff..b26773ed1 100644 --- a/flake.nix +++ b/flake.nix @@ -15,26 +15,6 @@ url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; - - /* - Inputs below this are optional and can be removed - - ``` - { - inputs.prismlauncher = { - url = "github:PrismLauncher/PrismLauncher"; - inputs = { - flake-compat.follows = ""; - }; - }; - } - ``` - */ - - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; }; outputs = diff --git a/nix/README.md b/nix/README.md index 7c43658f9..21714d64a 100644 --- a/nix/README.md +++ b/nix/README.md @@ -44,9 +44,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; @@ -92,9 +89,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; From 32b49ecb84da5683bce42e2e888319f4517f1d53 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 17:11:51 -0400 Subject: [PATCH 074/106] refactor(nix): cleanup flake Signed-off-by: Seth Flynn --- flake.nix | 71 +++++++++++++++++++++++++++++++++++++++++--------- nix/checks.nix | 42 ----------------------------- 2 files changed, 58 insertions(+), 55 deletions(-) delete mode 100644 nix/checks.nix diff --git a/flake.nix b/flake.nix index b26773ed1..ea14f9048 100644 --- a/flake.nix +++ b/flake.nix @@ -22,8 +22,8 @@ self, nixpkgs, libnbtplusplus, - ... }: + let inherit (nixpkgs) lib; @@ -35,30 +35,71 @@ forAllSystems = lib.genAttrs systems; nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system}); in + { checks = forAllSystems ( system: + let - checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; }; + pkgs = nixpkgsFor.${system}; + llvm = pkgs.llvmPackages_19; in - lib.filterAttrs (_: lib.isDerivation) checks' + + { + formatting = + pkgs.runCommand "check-formatting" + { + nativeBuildInputs = with pkgs; [ + deadnix + llvm.clang-tools + markdownlint-cli + nixfmt-rfc-style + statix + ]; + } + '' + cd ${self} + + echo "Running clang-format...." + clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp} + + echo "Running deadnix..." + deadnix --fail + + echo "Running markdownlint..." + markdownlint --dot . + + echo "Running nixfmt..." + find -type f -name '*.nix' -exec nixfmt --check {} + + + echo "Running statix" + statix check . + + touch $out + ''; + } ); devShells = forAllSystems ( system: + let pkgs = nixpkgsFor.${system}; + llvm = pkgs.llvmPackages_19; + + packages' = self.packages.${system}; in + { default = pkgs.mkShell { - inputsFrom = [ self.packages.${system}.prismlauncher-unwrapped ]; - buildInputs = with pkgs; [ + inputsFrom = [ packages'.prismlauncher-unwrapped ]; + + nativeBuildInputs = with pkgs; [ ccache - ninja - llvmPackages_19.clang-tools + llvm.clang-tools ]; - cmakeFlags = self.packages.${system}.prismlauncher-unwrapped.cmakeFlags ++ [ + cmakeFlags = packages'.prismlauncher-unwrapped.cmakeFlags ++ [ "-GNinja" "-Bbuild" ]; @@ -86,6 +127,7 @@ packages = forAllSystems ( system: + let pkgs = nixpkgsFor.${system}; @@ -98,6 +140,7 @@ default = prismPackages.prismlauncher; }; in + # Only output them if they're available on the current system lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages ); @@ -105,16 +148,18 @@ # We put these under legacyPackages as they are meant for CI, not end user consumption legacyPackages = forAllSystems ( system: + let - prismPackages = self.packages.${system}; - legacyPackages = self.legacyPackages.${system}; + packages' = self.packages.${system}; + legacyPackages' = self.legacyPackages.${system}; in + { - prismlauncher-debug = prismPackages.prismlauncher.override { - prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug; + prismlauncher-debug = packages'.prismlauncher.override { + prismlauncher-unwrapped = legacyPackages'.prismlauncher-unwrapped-debug; }; - prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs { + prismlauncher-unwrapped-debug = packages'.prismlauncher-unwrapped.overrideAttrs { cmakeBuildType = "Debug"; dontStrip = true; }; diff --git a/nix/checks.nix b/nix/checks.nix deleted file mode 100644 index ec219d6f8..000000000 --- a/nix/checks.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - runCommand, - deadnix, - llvmPackages_18, - markdownlint-cli, - nixfmt-rfc-style, - statix, - self, -}: -{ - formatting = - runCommand "check-formatting" - { - nativeBuildInputs = [ - deadnix - llvmPackages_18.clang-tools - markdownlint-cli - nixfmt-rfc-style - statix - ]; - } - '' - cd ${self} - - echo "Running clang-format...." - clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp} - - echo "Running deadnix..." - deadnix --fail - - echo "Running markdownlint..." - markdownlint --dot . - - echo "Running nixfmt..." - nixfmt --check . - - echo "Running statix" - statix check . - - touch $out - ''; -} From 58579539d071c569800ba0370a6d5918de025e33 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 17:19:20 -0400 Subject: [PATCH 075/106] fix(nix): only create compile_commands.json if it doesn't exist Signed-off-by: Seth Flynn --- flake.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index ea14f9048..ceb4bad4b 100644 --- a/flake.nix +++ b/flake.nix @@ -105,8 +105,10 @@ ]; shellHook = '' - cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug - ln -s {build/,}compile_commands.json + if [ ! -f compile_commands.json ]; then + cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug + ln -s {build/,}compile_commands.json + fi ''; }; } From 2d4bc09cb9bec621997e00c23acac0b7cf3e99d7 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 18:41:06 -0400 Subject: [PATCH 076/106] build(nix): properly wrap development shell Allows actually running the executables built in the development shell Signed-off-by: Seth Flynn --- flake.nix | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index ceb4bad4b..55d8b0464 100644 --- a/flake.nix +++ b/flake.nix @@ -88,13 +88,35 @@ llvm = pkgs.llvmPackages_19; packages' = self.packages.${system}; + + # Re-use our package wrapper to wrap our development environment + qt-wrapper-env = packages'.prismlauncher.overrideAttrs (old: { + name = "qt-wrapper-env"; + + # Required to use script-based makeWrapper below + strictDeps = true; + + # We don't need/want the unwrapped Prism package + paths = [ ]; + + nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [ + # Ensure the wrapper is script based so it can be sourced + pkgs.makeWrapper + ]; + + # Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10 + buildCommand = '' + makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out" + sed -i '/^exec/d' "$out" + ''; + }); in { default = pkgs.mkShell { inputsFrom = [ packages'.prismlauncher-unwrapped ]; - nativeBuildInputs = with pkgs; [ + packages = with pkgs; [ ccache llvm.clang-tools ]; @@ -105,6 +127,9 @@ ]; shellHook = '' + echo "Sourcing ${qt-wrapper-env}" + source ${qt-wrapper-env} + if [ ! -f compile_commands.json ]; then cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug ln -s {build/,}compile_commands.json From de923a07d8a4b7c9f8cd688b9682587c78116e14 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:01:47 -0400 Subject: [PATCH 077/106] refactor(nix): rely more on setup hooks in dev shell Signed-off-by: Seth Flynn --- flake.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 55d8b0464..339a4026c 100644 --- a/flake.nix +++ b/flake.nix @@ -121,18 +121,18 @@ llvm.clang-tools ]; - cmakeFlags = packages'.prismlauncher-unwrapped.cmakeFlags ++ [ - "-GNinja" - "-Bbuild" - ]; + cmakeBuildType = "Debug"; + cmakeFlags = [ "-GNinja" ] ++ packages'.prismlauncher.cmakeFlags; + dontFixCmake = true; shellHook = '' echo "Sourcing ${qt-wrapper-env}" source ${qt-wrapper-env} if [ ! -f compile_commands.json ]; then - cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug - ln -s {build/,}compile_commands.json + cmakeConfigurePhase + cd .. + ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json fi ''; }; From 38ec7def324891c5062d9b58c1180d44494f91d1 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:01:47 -0400 Subject: [PATCH 078/106] chore(nix): add nice welcome message to dev shell Signed-off-by: Seth Flynn --- flake.nix | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/flake.nix b/flake.nix index 339a4026c..cf5f656d5 100644 --- a/flake.nix +++ b/flake.nix @@ -89,6 +89,24 @@ packages' = self.packages.${system}; + welcomeMessage = '' + Welcome to the Prism Launcher repository! 🌈 + + We just set some things up for you. To get building, you can run: + + ``` + $ cd "$cmakeBuildDir" + $ ninjaBuildPhase + $ ninjaInstallPhase + ``` + + Feel free to ask any questions in our Discord server or Matrix space: + - https://prismlauncher.org/discord + - https://matrix.to/#/#prismlauncher:matrix.org + + And thanks for helping out :) + ''; + # Re-use our package wrapper to wrap our development environment qt-wrapper-env = packages'.prismlauncher.overrideAttrs (old: { name = "qt-wrapper-env"; @@ -134,6 +152,8 @@ cd .. ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json fi + + echo ${lib.escapeShellArg welcomeMessage} ''; }; } From 9b38226f8cb0e2f82bcb017c686fd03520990dd1 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:01:47 -0400 Subject: [PATCH 079/106] chore(nix): clone git submodules automatically Signed-off-by: Seth Flynn --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index cf5f656d5..fd3003bc4 100644 --- a/flake.nix +++ b/flake.nix @@ -147,6 +147,8 @@ echo "Sourcing ${qt-wrapper-env}" source ${qt-wrapper-env} + git submodule update --init --force + if [ ! -f compile_commands.json ]; then cmakeConfigurePhase cd .. From e9cac2e0e37c4531f662545bdc45b72499654642 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:19:30 -0400 Subject: [PATCH 080/106] refactor(nix): use date for version Helps avoid needless rebuilds where only the revision changed. Also better conforms to Nixpkgs' version standards Signed-off-by: Seth Flynn --- nix/unwrapped.nix | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 0a4d2e253..1db414a53 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -24,9 +24,28 @@ assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; +let + date = + let + # YYYYMMDD + date' = lib.substring 0 8 self.lastModifiedDate; + year = lib.substring 0 4 date'; + month = lib.substring 4 2 date'; + date = lib.substring 6 2 date'; + in + if (self ? "lastModifiedDate") then + lib.concatStringsSep "-" [ + year + month + date + ] + else + "unknown"; +in + stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; - version = self.shortRev or self.dirtyShortRev or "unknown"; + version = "10.0-unstable-${date}"; src = lib.fileset.toSource { root = ../.; From de08d7c3644e96a3694f232b53c81f53c40c077a Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 30 Mar 2025 19:48:17 -0400 Subject: [PATCH 081/106] chore(gitignore): add more nix-related files Signed-off-by: Seth Flynn --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c8f056eef..b563afbc7 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,12 @@ run/ # Nix/NixOS .direnv/ -.pre-commit-config.yaml +## Used when manually invoking stdenv phases +outputs/ +## Regular artifacts result +result-* +repl-result-* # Flatpak .flatpak-builder From b1c4e85806dd71136d8f5c655061a7140735c3a0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:53:22 +0000 Subject: [PATCH 082/106] chore(deps): update cachix/install-nix-action digest to d1ca217 --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 48a8418f5..cdd360589 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31 + - uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31 - uses: DeterminateSystems/update-flake-lock@v24 with: From f82b050bca8d0198e181765fafdd99dab931c1e9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 1 Apr 2025 23:33:17 -0700 Subject: [PATCH 083/106] Fix blocked pr comment body (#3570) * Add newline to seperate header from comment body Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Seth Flynn Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --------- Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> Co-authored-by: Seth Flynn --- .github/workflows/blocked-prs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index d32f53bb1..94ec81317 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -163,6 +163,7 @@ jobs: if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }} run: | while read -r pr ; do @@ -202,7 +203,8 @@ jobs: BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }} run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" - echo '

PR Dependencies :pushpin:

' > "$COMMENT_PATH" + echo '

PR Dependencies :pushpin:

' > "$COMMENT_PATH" + echo >> "$COMMENT_PATH" pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA") while read -r pr_data ; do base_pr=$(jq -r '.number' <<< "$pr_data") From 8fd86e065ead6b44b967624cbf44940993d0d4ef Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 1 Apr 2025 23:46:49 -0700 Subject: [PATCH 084/106] push json expansion into env Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 94ec81317..9a5e84bea 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -47,9 +47,11 @@ jobs: - name: Setup Environment id: env_setup + env: + EVENT_PR_JSON: ${{ toJSON(github.event.pull_request) }} run: | # setup env for the rest of the workflow - PR_JSON=${PR_JSON:-'${{ toJSON(github.event.pull_request) }}'} + PR_JSON=${PR_JSON:-"$EVENT_PR_JSON"} { echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")" echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")" From 0518c5095819589573d2679e51baa0b31c1b3a71 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Wed, 2 Apr 2025 16:01:26 +0800 Subject: [PATCH 085/106] Add checkbox for LiteLoader in mod filter Signed-off-by: Yihe Li --- .../ui/pages/modplatform/flame/FlamePage.cpp | 2 +- .../modplatform/flame/FlameResourcePages.cpp | 2 +- .../modplatform/modrinth/ModrinthPage.cpp | 2 +- .../modrinth/ModrinthResourcePages.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 21 ++++++++++++------- launcher/ui/widgets/ModFilterWidget.h | 7 +++++-- launcher/ui/widgets/ModFilterWidget.ui | 7 +++++++ 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index de6b3d633..0d61032d1 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,7 +341,7 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, this); + auto widget = ModFilterWidget::create(nullptr, false, ModPlatform::ResourceProvider::FLAME, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 4e01f3a65..b10a5b062 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -209,7 +209,7 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false, ModPlatform::ResourceProvider::FLAME, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 7d70abec4..b91d50d3d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,7 +391,7 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, true, this); + auto widget = ModFilterWidget::create(nullptr, true, ModPlatform::ResourceProvider::MODRINTH, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 4ee620677..be6343504 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -144,7 +144,7 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true, ModPlatform::ResourceProvider::MODRINTH, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 37211693f..4973514a4 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,9 +49,12 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, + bool extended, + ModPlatform::ResourceProvider provider, + QWidget* parent) { - return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); + return unique_qobject_ptr(new ModFilterWidget(instance, extended, provider, parent)); } class VersionBasicModel : public QIdentityProxyModel { @@ -107,7 +110,7 @@ class AllVersionProxyModel : public QSortFilterProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, ModPlatform::ResourceProvider provider, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); @@ -148,11 +151,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - - connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + if (provider == ModPlatform::ResourceProvider::FLAME) + ui->liteLoader->setEnabled(false); + else + connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); if (extended) { connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); @@ -224,6 +226,7 @@ void ModFilterWidget::prepareBasicFilter() ui->forge->setChecked(loaders & ModPlatform::Forge); ui->fabric->setChecked(loaders & ModPlatform::Fabric); ui->quilt->setChecked(loaders & ModPlatform::Quilt); + ui->liteLoader->setChecked(loaders & ModPlatform::LiteLoader); m_filter->loaders = loaders; auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); m_filter->versions.emplace_front(def); @@ -269,6 +272,8 @@ void ModFilterWidget::onLoadersFilterChanged() loaders |= ModPlatform::Fabric; if (ui->quilt->isChecked()) loaders |= ModPlatform::Quilt; + if (ui->liteLoader->isChecked()) + loaders |= ModPlatform::LiteLoader; m_filter_changed = loaders != m_filter->loaders; m_filter->loaders = loaders; if (m_filter_changed) diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 41a2f1bbd..bedde51a9 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,7 +83,10 @@ class ModFilterWidget : public QTabWidget { } }; - static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, + bool extended, + ModPlatform::ResourceProvider provider, + QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -96,7 +99,7 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, ModPlatform::ResourceProvider provider, QWidget* parent = nullptr); void loadVersionList(); void prepareBasicFilter(); diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 807a0019a..788202714 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -121,6 +121,13 @@
+ + + + LiteLoader + + +
From b0c85fd539a3fa1301738a659430dc0d99068fb6 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Wed, 2 Apr 2025 16:22:52 +0800 Subject: [PATCH 086/106] Hide checkbox for CurseForge Signed-off-by: Yihe Li --- launcher/ui/widgets/ModFilterWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 4973514a4..4cedec472 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -152,7 +152,7 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, Mod connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); if (provider == ModPlatform::ResourceProvider::FLAME) - ui->liteLoader->setEnabled(false); + ui->liteLoader->setVisible(false); else connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); From 3ae68114f6d5a4a5a2137c792e793bb5d30d99a8 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Wed, 2 Apr 2025 16:28:49 +0800 Subject: [PATCH 087/106] Remove extra argument from ModFilterWidget::create Signed-off-by: Yihe Li --- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 2 +- .../modplatform/flame/FlameResourcePages.cpp | 2 +- .../pages/modplatform/modrinth/ModrinthPage.cpp | 2 +- .../modrinth/ModrinthResourcePages.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 15 ++++++--------- launcher/ui/widgets/ModFilterWidget.h | 7 ++----- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 0d61032d1..de6b3d633 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,7 +341,7 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, ModPlatform::ResourceProvider::FLAME, this); + auto widget = ModFilterWidget::create(nullptr, false, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index b10a5b062..4e01f3a65 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -209,7 +209,7 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), false, ModPlatform::ResourceProvider::FLAME, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index b91d50d3d..7d70abec4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,7 +391,7 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, true, ModPlatform::ResourceProvider::MODRINTH, this); + auto widget = ModFilterWidget::create(nullptr, true, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index be6343504..4ee620677 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -144,7 +144,7 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), true, ModPlatform::ResourceProvider::MODRINTH, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 4cedec472..0fda7933e 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,12 +49,9 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, - bool extended, - ModPlatform::ResourceProvider provider, - QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) { - return unique_qobject_ptr(new ModFilterWidget(instance, extended, provider, parent)); + return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); } class VersionBasicModel : public QIdentityProxyModel { @@ -110,7 +107,7 @@ class AllVersionProxyModel : public QSortFilterProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, ModPlatform::ResourceProvider provider, QWidget* parent) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); @@ -151,10 +148,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, Mod connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - if (provider == ModPlatform::ResourceProvider::FLAME) - ui->liteLoader->setVisible(false); - else + if (extended) connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + else + ui->liteLoader->setVisible(false); if (extended) { connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index bedde51a9..41a2f1bbd 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,10 +83,7 @@ class ModFilterWidget : public QTabWidget { } }; - static unique_qobject_ptr create(MinecraftInstance* instance, - bool extended, - ModPlatform::ResourceProvider provider, - QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -99,7 +96,7 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, ModPlatform::ResourceProvider provider, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); void loadVersionList(); void prepareBasicFilter(); From dc4cb8b9d51efab8de8993108b0cd133717d00ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:22:42 +0000 Subject: [PATCH 088/106] chore(deps): update actions/create-github-app-token action to v2 --- .github/workflows/blocked-prs.yml | 2 +- .github/workflows/merge-blocking-pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 9a5e84bea..bd49b7230 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Generate token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 6f85b9282..d6410f997 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Generate token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} From 12c266f8bde96e4e613856b1f8482cde02fee3f7 Mon Sep 17 00:00:00 2001 From: hanlie <48323966+HanlieChina@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:22:00 +0800 Subject: [PATCH 089/106] Update ExportToModListDialog.ui Signed-off-by: hanlie <48323966+HanlieChina@users.noreply.github.com> --- launcher/ui/dialogs/ExportToModListDialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui index 3afda2fa8..ec049d7e7 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.ui +++ b/launcher/ui/dialogs/ExportToModListDialog.ui @@ -79,6 +79,9 @@ 0 + + This text supports the following placeholders: {name} - Mod name {mod_id} - Mod ID {url} - Mod URL {version} - Mod version {authors} - Mod authors +
From aec410cb28bb844f3e3bc0e1a2040a90baf09b71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Apr 2025 00:27:19 +0000 Subject: [PATCH 090/106] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/5e5402ecbcb27af32284d4a62553c019a3a49ea6?narHash=sha256-gWd4urRoLRe8GLVC/3rYRae1h%2BxfQzt09xOfb0PaHSk%3D' (2025-03-27) → 'github:NixOS/nixpkgs/2c8d3f48d33929642c1c12cd243df4cc7d2ce434?narHash=sha256-F7n4%2BKOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE%3D' (2025-04-02) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d8c7c5248..996d79e22 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1743095683, - "narHash": "sha256-gWd4urRoLRe8GLVC/3rYRae1h+xfQzt09xOfb0PaHSk=", + "lastModified": 1743583204, + "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5e5402ecbcb27af32284d4a62553c019a3a49ea6", + "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", "type": "github" }, "original": { From e5861129ad58a8b5cdf55ed69bb9a7ce92811b87 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 6 Apr 2025 06:40:43 -0400 Subject: [PATCH 091/106] fix(SystemTheme): use default palette on all system themes Signed-off-by: Seth Flynn --- launcher/ui/themes/SystemTheme.cpp | 23 ++++++++++++++++------- launcher/ui/themes/SystemTheme.h | 2 +- launcher/ui/themes/ThemeManager.cpp | 6 ++++-- launcher/ui/themes/ThemeManager.h | 1 + 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index c755b379b..7fba08026 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -40,20 +40,29 @@ #include "HintOverrideProxyStyle.h" #include "ThemeManager.h" -SystemTheme::SystemTheme(const QString& styleName, bool isDefaultTheme) +// See https://github.com/MultiMC/Launcher/issues/1790 +// or https://github.com/PrismLauncher/PrismLauncher/issues/490 +static const QStringList S_NATIVE_STYLES{ "windows11", "windowsvista", "macos", "system", "windows" }; + +SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme) { m_themeName = isDefaultTheme ? "system" : styleName; m_widgetTheme = styleName; - auto style = QStyleFactory::create(styleName); - m_colorPalette = style->standardPalette(); - delete style; + // NOTE: SystemTheme is reconstructed on page refresh. We can't accurately determine the system palette here + // See also S_NATIVE_STYLES comment + if (S_NATIVE_STYLES.contains(m_themeName)) { + m_colorPalette = defaultPalette; + } else { + auto style = QStyleFactory::create(styleName); + m_colorPalette = style->standardPalette(); + delete style; + } } void SystemTheme::apply(bool initial) { - // See https://github.com/MultiMC/Launcher/issues/1790 - // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial && m_themeName == "system") { + // See S_NATIVE_STYLES comment + if (initial && S_NATIVE_STYLES.contains(m_themeName)) { QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); return; } diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 54404d052..7ae24c3db 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -38,7 +38,7 @@ class SystemTheme : public ITheme { public: - SystemTheme(const QString& styleName, bool isDefaultTheme); + SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme); virtual ~SystemTheme() {} void apply(bool initial) override; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 6c50d7409..30a1fe7be 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -44,6 +44,8 @@ ThemeManager::ThemeManager() m_defaultStyle = style->objectName(); themeDebugLog() << "System theme seems to be:" << m_defaultStyle; + m_defaultPalette = QApplication::palette(); + initializeThemes(); initializeCatPacks(); } @@ -126,7 +128,7 @@ void ThemeManager::initializeIcons() void ThemeManager::initializeWidgets() { themeDebugLog() << "<> Initializing Widget Themes"; - themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique(m_defaultStyle, true)); + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique(m_defaultStyle, m_defaultPalette, true)); auto darkThemeId = addTheme(std::make_unique()); themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); @@ -139,7 +141,7 @@ void ThemeManager::initializeWidgets() continue; } #endif - themeDebugLog() << "Loading System Theme:" << addTheme(std::make_unique(st, false)); + themeDebugLog() << "Loading System Theme:" << addTheme(std::make_unique(st, m_defaultPalette, false)); } // TODO: need some way to differentiate same name themes in different subdirectories diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index 9c9e818e5..8de7562d1 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -68,6 +68,7 @@ class ThemeManager { QDir m_applicationThemeFolder{ "themes" }; QDir m_catPacksFolder{ "catpacks" }; std::map> m_catPacks; + QPalette m_defaultPalette; QString m_defaultStyle; LogColors m_logColors; From da3a4984909b4b1d801018bf80eb93feadc97100 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:11:59 -0400 Subject: [PATCH 092/106] ci(nix): run on tags Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 6c2b13de7..315d63667 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -2,6 +2,8 @@ name: Nix on: push: + tags: + - "*" paths-ignore: - "**.md" - "**/LICENSE" From 1a5a162727c7d9d91aefd3852c127d60179844ae Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:11:59 -0400 Subject: [PATCH 093/106] ci(nix): ignore more paths Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 315d63667..1317a1a05 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -5,18 +5,27 @@ on: tags: - "*" paths-ignore: + - ".github/**" + - "!.github/workflows/nix.yml" + - "flatpak/" + - "scripts/" + + - ".git*" + - ".envrc" - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "flatpak/**" + - "!COPYING.md" + - "renovate.json" pull_request_target: paths-ignore: + - ".github/**" + - "flatpak/" + - "scripts/" + + - ".git*" + - ".envrc" - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "flatpak/**" + - "!COPYING.md" + - "renovate.json" workflow_dispatch: permissions: From 76bec385d6803e94899028695226d872185b3d39 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:19:58 -0400 Subject: [PATCH 094/106] ci(nix): correctly parse action env vars as bools Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 1317a1a05..99bb4cb7d 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -71,7 +71,7 @@ jobs: # For PRs - name: Setup Nix Magic Cache - if: ${{ env.USE_DETERMINATE }} + if: ${{ env.USE_DETERMINATE == 'true' }} uses: DeterminateSystems/flakehub-cache-action@v1 # For in-tree builds @@ -87,14 +87,14 @@ jobs: nix flake check --print-build-logs --show-trace - name: Build debug package - if: ${{ env.DEBUG }} + if: ${{ env.DEBUG == 'true' }} run: | nix build \ --no-link --print-build-logs --print-out-paths \ .#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY" - name: Build release package - if: ${{ !env.DEBUG }} + if: ${{ env.DEBUG == 'false' }} run: | nix build \ --no-link --print-build-logs --print-out-paths \ From 180292098d9c4ad87ed0d9babed92e152feb5475 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:19:58 -0400 Subject: [PATCH 095/106] ci(nix): pin release packages in cachix Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 99bb4cb7d..fea0df6ce 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -95,7 +95,10 @@ jobs: - name: Build release package if: ${{ env.DEBUG == 'false' }} + env: + TAG: ${{ github.ref_name }} + SYSTEM: ${{ matrix.system }} run: | - nix build \ - --no-link --print-build-logs --print-out-paths \ - .#prismlauncher >> "$GITHUB_STEP_SUMMARY" + nix build --no-link --print-out-paths .#prismlauncher \ + | tee -a "$GITHUB_STEP_SUMMARY" \ + | xargs cachix pin prismlauncher "$TAG"-"$SYSTEM" From 6386d044e3fc5ec056479e508ade4da993a81cd4 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 7 Apr 2025 08:32:30 -0400 Subject: [PATCH 096/106] revert: "feat: publish on flakehub" Flakehub requires semver. We don't use that (yet) Refs: 86cc6d3 Signed-off-by: Seth Flynn --- .github/workflows/publish.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 034a8548b..8a7da812e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,28 +8,6 @@ permissions: contents: read jobs: - flakehub: - name: FlakeHub - - runs-on: ubuntu-latest - - permissions: - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - - name: Install Nix - uses: cachix/install-nix-action@v31 - - - name: Publish on FlakeHub - uses: determinatesystems/flakehub-push@v5 - with: - visibility: "public" - winget: name: Winget From d92f7b3c9710679550111037fb7e41fe64443927 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 6 Apr 2025 17:08:25 -0400 Subject: [PATCH 097/106] ci: use ninja with msvc on x64 Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59489d0f7..6e4d1fbf3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -171,6 +171,12 @@ jobs: with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} + - name: Use ccache on Debug builds only + if: inputs.build_type == 'Debug' + shell: bash + run: | + echo "CCACHE_VAR=ccache" >> $GITHUB_ENV + - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' uses: actions/cache@v4.2.3 @@ -190,11 +196,17 @@ jobs: ccache -p # Show config ccache -z # Zero stats - - name: Use ccache on Debug builds only - if: inputs.build_type == 'Debug' - shell: bash + - name: Configure ccache (Windows MSVC) + if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }} run: | - echo "CCACHE_VAR=ccache" >> $GITHUB_ENV + # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) + Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe + echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV + echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV + echo "TrackFileAccess=false" >> $env:GITHUB_ENV + # Needed for ccache, but also speeds up compile + echo "UseMultiToolTask=true" >> $env:GITHUB_ENV + - name: Set short version shell: bash @@ -300,19 +312,14 @@ jobs: cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja - name: Configure CMake (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' + if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64' + run: | + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja + + - name: Configure CMake (Windows MSVC arm64) + if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64' run: | cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} - # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) - if ("${{ env.CCACHE_VAR }}") - { - Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe - echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV - echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV - echo "TrackFileAccess=false" >> $env:GITHUB_ENV - } - # Needed for ccache, but also speeds up compile - echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - name: Configure CMake (Linux) if: runner.os == 'Linux' From b579cae5c28be2ef8342246be56655452ce141bb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 8 Apr 2025 04:17:10 -0700 Subject: [PATCH 098/106] feat(server): start using semver for launcher Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- CMakeLists.txt | 13 +++---- buildconfig/BuildConfig.cpp.in | 36 +++++++------------ buildconfig/BuildConfig.h | 2 ++ .../updater/prismupdater/PrismUpdater.cpp | 12 ++++++- launcher/updater/prismupdater/PrismUpdater.h | 1 + program_info/win_install.nsi.in | 1 + 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 138049018..f80c675bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}" # Export compile commands for debug builds if we can (useful in LSPs like clangd) # https://cmake.org/cmake/help/v3.31/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html if(CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja" AND CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF) @@ -195,10 +195,11 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 10) set(Launcher_VERSION_MINOR 0) +set(Launcher_VERSION_PATCH 0) -set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") -set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0") -set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0") +set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") +set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") +set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_PATCH},0") # Build platform. set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") @@ -242,7 +243,7 @@ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON) # differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this # feature if they know it will work with their distribution. if(UNIX AND NOT APPLE) - set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF) + set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF) endif() # Java downloader @@ -383,7 +384,7 @@ set(Launcher_ENABLE_UPDATER NO) set(Launcher_BUILD_UPDATER NO) if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL "")) - set(Launcher_BUILD_UPDATER YES) + set(Launcher_BUILD_UPDATER YES) endif() if(NOT (UNIX AND APPLE)) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 2124d02ae..6bebcb80e 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -34,8 +34,8 @@ */ #include -#include "BuildConfig.h" #include +#include "BuildConfig.h" const Config BuildConfig; @@ -58,6 +58,7 @@ Config::Config() // Version information VERSION_MAJOR = @Launcher_VERSION_MAJOR@; VERSION_MINOR = @Launcher_VERSION_MINOR@; + VERSION_PATCH = @Launcher_VERSION_PATCH@; BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@"; BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@"; @@ -74,14 +75,13 @@ Config::Config() MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@"; - if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) - { + if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) { UPDATER_ENABLED = true; - } else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) { + } else if (!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) { UPDATER_ENABLED = true; } - #cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER +#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER; GIT_COMMIT = "@Launcher_GIT_COMMIT@"; @@ -89,27 +89,19 @@ Config::Config() GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; // Assume that builds outside of Git repos are "stable" - if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") - || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND") - || GIT_REFSPEC == QStringLiteral("") - || GIT_TAG == QStringLiteral("GIT-NOTFOUND")) - { + if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND") || + GIT_REFSPEC == QStringLiteral("") || GIT_TAG == QStringLiteral("GIT-NOTFOUND")) { GIT_REFSPEC = "refs/heads/stable"; GIT_TAG = versionString(); GIT_COMMIT = ""; } - if (GIT_REFSPEC.startsWith("refs/heads/")) - { + if (GIT_REFSPEC.startsWith("refs/heads/")) { VERSION_CHANNEL = GIT_REFSPEC; - VERSION_CHANNEL.remove("refs/heads/"); - } - else if (!GIT_COMMIT.isEmpty()) - { + VERSION_CHANNEL.remove("refs/heads/"); + } else if (!GIT_COMMIT.isEmpty()) { VERSION_CHANNEL = GIT_COMMIT.mid(0, 8); - } - else - { + } else { VERSION_CHANNEL = "unknown"; } @@ -136,7 +128,7 @@ Config::Config() QString Config::versionString() const { - return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR); + return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_PATCH); } QString Config::printableVersionString() const @@ -144,8 +136,7 @@ QString Config::printableVersionString() const QString vstr = versionString(); // If the build is not a main release, append the channel - if(VERSION_CHANNEL != "stable" && GIT_TAG != vstr) - { + if (VERSION_CHANNEL != "stable" && GIT_TAG != vstr) { vstr += "-" + VERSION_CHANNEL; } return vstr; @@ -162,4 +153,3 @@ QString Config::systemID() const { return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR); } - diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 099d9b5ca..b59adcb57 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -59,6 +59,8 @@ class Config { int VERSION_MAJOR; /// The minor version number. int VERSION_MINOR; + /// The patch version number. + int VERSION_PATCH; /** * The version channel diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 8bf8cb473..96172b0bc 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -298,6 +298,10 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar auto version_parts = version.split('.'); m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; } m_allowPreRelease = parser.isSet("pre-release"); @@ -556,6 +560,7 @@ void PrismUpdaterApp::run() m_prismVersion = BuildConfig.printableVersionString(); m_prismVersionMajor = BuildConfig.VERSION_MAJOR; m_prismVersionMinor = BuildConfig.VERSION_MINOR; + m_prismVersionPatch = BuildConfig.VERSION_PATCH; m_prsimVersionChannel = BuildConfig.VERSION_CHANNEL; m_prismGitCommit = BuildConfig.GIT_COMMIT; } @@ -564,6 +569,7 @@ void PrismUpdaterApp::run() qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVersion; qDebug() << "Version major:" << m_prismVersionMajor; qDebug() << "Version minor:" << m_prismVersionMinor; + qDebug() << "Version minor:" << m_prismVersionPatch; qDebug() << "Version channel:" << m_prsimVersionChannel; qDebug() << "Git Commit:" << m_prismGitCommit; @@ -1277,6 +1283,10 @@ bool PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) return false; m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; m_prismGitCommit = lines.takeFirst().simplified(); return true; } @@ -1400,7 +1410,7 @@ GitHubRelease PrismUpdaterApp::getLatestRelease() bool PrismUpdaterApp::needUpdate(const GitHubRelease& release) { - auto current_ver = Version(QString("%1.%2").arg(QString::number(m_prismVersionMajor)).arg(QString::number(m_prismVersionMinor))); + auto current_ver = Version(QString("%1.%2.%3").arg(m_prismVersionMajor).arg(m_prismVersionMinor).arg(m_prismVersionPatch)); return current_ver < release.version; } diff --git a/launcher/updater/prismupdater/PrismUpdater.h b/launcher/updater/prismupdater/PrismUpdater.h index f3dd6e062..a904cbb6f 100644 --- a/launcher/updater/prismupdater/PrismUpdater.h +++ b/launcher/updater/prismupdater/PrismUpdater.h @@ -121,6 +121,7 @@ class PrismUpdaterApp : public QApplication { QString m_prismVersion; int m_prismVersionMajor = -1; int m_prismVersionMinor = -1; + int m_prismVersionPatch = -1; QString m_prsimVersionChannel; QString m_prismGitCommit; diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 24f6ee4e8..dfdce2e1c 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -398,6 +398,7 @@ Section "@Launcher_DisplayName@" WriteRegStr HKCU Software\Classes\prismlauncher\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"' ; Write the uninstall keys for Windows + ; https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} From c89a8a676e9540797dcdbb61cba07d438811ce8d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 20:43:41 +0300 Subject: [PATCH 099/106] increment linux qt version Signed-off-by: Trial97 --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4d1fbf3..cb92ca270 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: qt_ver: 6 qt_host: linux qt_arch: "" - qt_version: "6.5.3" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" @@ -207,7 +207,6 @@ jobs: # Needed for ccache, but also speeds up compile echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - - name: Set short version shell: bash run: | From 5ba25a147627646a957282e9ebc60179d6686328 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 20:33:02 +0300 Subject: [PATCH 100/106] remove mac legacy build Signed-off-by: Trial97 --- .github/workflows/build.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e459914..e21dad106 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,14 +106,6 @@ jobs: qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" - - os: macos-14 - name: macOS-Legacy - macosx_deployment_target: 10.13 - qt_ver: 5 - qt_host: mac - qt_version: "5.15.2" - qt_modules: "qtnetworkauth" - runs-on: ${{ matrix.os }} env: @@ -278,11 +270,6 @@ jobs: run: | cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja - - name: Configure CMake (macOS-Legacy) - if: runner.os == 'macOS' && matrix.qt_ver == 5 - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja - - name: Configure CMake (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} From 1a3cc00438c9551992992f369ff506a28f717752 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 21:37:34 +0300 Subject: [PATCH 101/106] deprecate ubuntu 20.04 runner Signed-off-by: Trial97 --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4d1fbf3..f75875dce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 qt_ver: 5 qt_host: linux qt_arch: "" @@ -207,7 +207,6 @@ jobs: # Needed for ccache, but also speeds up compile echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - - name: Set short version shell: bash run: | From 4361aaa094820e07e9d35d96865b3851f184d49a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 7 Apr 2025 13:17:48 +0300 Subject: [PATCH 102/106] remove ghc_filesystem Signed-off-by: Trial97 --- .gitmodules | 3 --- CMakeLists.txt | 9 -------- COPYING.md | 22 ------------------- launcher/CMakeLists.txt | 3 --- launcher/FileSystem.cpp | 16 -------------- launcher/filelink/FileLink.cpp | 16 -------------- .../updater/prismupdater/PrismUpdater.cpp | 16 -------------- libraries/README.md | 8 ------- libraries/filesystem | 1 - nix/unwrapped.nix | 2 -- tests/FileSystem_test.cpp | 16 -------------- 11 files changed, 112 deletions(-) delete mode 160000 libraries/filesystem diff --git a/.gitmodules b/.gitmodules index 0f437d277..7ad40becb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "libraries/tomlplusplus"] path = libraries/tomlplusplus url = https://github.com/marzer/tomlplusplus.git -[submodule "libraries/filesystem"] - path = libraries/filesystem - url = https://github.com/gulrak/filesystem [submodule "libraries/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PrismLauncher/libnbtplusplus.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 138049018..deb14fdf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,9 +363,6 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) - # Find ghc_filesystem - find_package(ghc_filesystem QUIET) - # Find cmark find_package(cmark QUIET) endif() @@ -560,12 +557,6 @@ else() endif() add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API -if (NOT ghc_filesystem_FOUND) - message(STATUS "Using bundled ghc_filesystem") - add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS -else() - message(STATUS "Using system ghc_filesystem") -endif() add_subdirectory(libraries/qdcss) # css parser ############################### Built Artifacts ############################### diff --git a/COPYING.md b/COPYING.md index 0ea3437d3..f1f0b3a70 100644 --- a/COPYING.md +++ b/COPYING.md @@ -362,28 +362,6 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -## gulrak/filesystem - - Copyright (c) 2018, Steffen Schümann - - 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. - ## Breeze icons Copyright (C) 2014 Uri Herrera and others diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0871675d0..30d657f9e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1289,7 +1289,6 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets - ghcFilesystem::ghc_filesystem ) if (UNIX AND NOT CYGWIN AND NOT APPLE) @@ -1376,7 +1375,6 @@ if(Launcher_BUILD_UPDATER) ${ZLIB_LIBRARIES} systeminfo BuildConfig - ghcFilesystem::ghc_filesystem Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network @@ -1415,7 +1413,6 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) target_link_libraries(filelink_logic systeminfo BuildConfig - ghcFilesystem::ghc_filesystem Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 954e7936e..5d3008aae 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -77,24 +77,8 @@ #include #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif // clone #if defined(Q_OS_LINUX) diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index b641b41d5..a082b4b5b 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -40,24 +40,8 @@ #include "WindowsConsole.h" #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 8bf8cb473..1a0d08ebf 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -53,24 +53,8 @@ #include #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif #include "DesktopServices.h" diff --git a/libraries/README.md b/libraries/README.md index 67d78dade..3c5f5a4ab 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -2,14 +2,6 @@ This folder has third-party or otherwise external libraries needed for other parts to work. -## filesystem - -Gulrak's implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD. - -See [github repo](https://github.com/gulrak/filesystem). - -MIT licensed. - ## gamemode A performance optimization daemon. diff --git a/libraries/filesystem b/libraries/filesystem deleted file mode 160000 index 076592ce6..000000000 --- a/libraries/filesystem +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 076592ce6e64568521b88a11881aa36b3d3f7048 diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 1db414a53..93cda8e1a 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -6,7 +6,6 @@ apple-sdk_11, extra-cmake-modules, gamemode, - ghc_filesystem, jdk17, kdePackages, libnbtplusplus, @@ -78,7 +77,6 @@ stdenv.mkDerivation { buildInputs = [ cmark - ghc_filesystem kdePackages.qtbase kdePackages.qtnetworkauth kdePackages.quazip diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 9f64f54ed..995867e46 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -9,24 +9,8 @@ #include #include -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ - -#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) -#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif #include From d1eebbceff0a263697bdaf6376af6a9e910004de Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Apr 2025 08:33:27 +0300 Subject: [PATCH 103/106] chore: remove release macos Legacy reference Signed-off-by: Trial97 --- .github/workflows/trigger_release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 134281b2c..411a5bbeb 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -49,7 +49,6 @@ jobs: mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync - mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} @@ -104,5 +103,4 @@ jobs: PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe PrismLauncher-macOS-${{ env.VERSION }}.zip - PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip PrismLauncher-${{ env.VERSION }}.tar.gz From 8bb9b168fb996df9209e1e34be854235eda3d42a Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sat, 12 Apr 2025 01:59:07 +0800 Subject: [PATCH 104/106] Use explicit construction for QFile from QString Signed-off-by: Yihe Li --- launcher/FileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 5d3008aae..7189ca841 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -934,7 +934,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri QDir content = application.path() + "/Contents/"; QDir resources = content.path() + "/Resources/"; QDir binaryDir = content.path() + "/MacOS/"; - QFile info = content.path() + "/Info.plist"; + QFile info(content.path() + "/Info.plist"); if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) { qWarning() << "Couldn't create directories within application"; From 6812d137e67b323fdec32c091cb4f408f8f5874b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 13 Apr 2025 00:51:01 +0000 Subject: [PATCH 105/106] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2c8d3f48d33929642c1c12cd243df4cc7d2ce434?narHash=sha256-F7n4%2BKOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE%3D' (2025-04-02) → 'github:NixOS/nixpkgs/2631b0b7abcea6e640ce31cd78ea58910d31e650?narHash=sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR%2BXhw3kr/3Xd0GPTM%3D' (2025-04-12) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 996d79e22..2d79b8335 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1743583204, - "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", + "lastModified": 1744463964, + "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", + "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", "type": "github" }, "original": { From 7d4034cfa55275edc08cef2c6dd9d9f8f5d063d1 Mon Sep 17 00:00:00 2001 From: Kenneth Chew <79120643+kthchew@users.noreply.github.com> Date: Sun, 13 Apr 2025 00:50:19 -0400 Subject: [PATCH 106/106] Shorten LocalPeer socket names On most systems supporting Unix sockets, the maximum length of a socket name is quite low (e.g. on macOS 104 characters and on Linux 108). If the name is too long, the sockets will not work and thus sending messages to a running instance of the launcher will not work. Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com> --- launcher/Application.cpp | 17 ++++++++++++----- libraries/LocalPeer/src/LocalPeer.cpp | 8 ++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 816f7b8ab..d773d9a1c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -375,19 +375,20 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_peerInstance = new LocalPeer(this, appID); connect(m_peerInstance, &LocalPeer::messageReceived, this, &Application::messageReceived); if (m_peerInstance->isClient()) { + bool sentMessage = false; int timeout = 2000; if (m_instanceIdToLaunch.isEmpty()) { ApplicationMessage activate; activate.command = "activate"; - m_peerInstance->sendMessage(activate.serialize(), timeout); + sentMessage = m_peerInstance->sendMessage(activate.serialize(), timeout); if (!m_urlsToImport.isEmpty()) { for (auto url : m_urlsToImport) { ApplicationMessage import; import.command = "import"; import.args.insert("url", url.toString()); - m_peerInstance->sendMessage(import.serialize(), timeout); + sentMessage = m_peerInstance->sendMessage(import.serialize(), timeout); } } } else { @@ -407,10 +408,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) launch.args["offline_enabled"] = "true"; launch.args["offline_name"] = m_offlineName; } - m_peerInstance->sendMessage(launch.serialize(), timeout); + sentMessage = m_peerInstance->sendMessage(launch.serialize(), timeout); + } + if (sentMessage) { + m_status = Application::Succeeded; + return; + } else { + std::cerr << "Unable to redirect command to already running instance\n"; + // C function not Qt function - event loop not started yet + ::exit(1); } - m_status = Application::Succeeded; - return; } } diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index bd407042f..c1875bf98 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -76,7 +76,7 @@ ApplicationId ApplicationId::fromTraditionalApp() prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); - auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); + auto socketName = QLatin1String("pl") + prefix + QLatin1Char('-') + QString::number(idNum, 16).left(12); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); @@ -98,12 +98,12 @@ ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const Q QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1); QString result = dataPath + QLatin1Char('-') + version; shasum.addData(result.toUtf8()); - return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex())); + return ApplicationId(QLatin1String("pl") + QString::fromLatin1(shasum.result().toHex()).left(12)); } ApplicationId ApplicationId::fromCustomId(const QString& id) { - return ApplicationId(QLatin1String("qtsingleapp-") + id); + return ApplicationId(QLatin1String("pl") + id); } ApplicationId ApplicationId::fromRawString(const QString& id) @@ -139,7 +139,7 @@ bool LocalPeer::isClient() #if defined(Q_OS_UNIX) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + socketName); + QLocalServer::removeServer(socketName); res = server->listen(socketName); } #endif