From 0d6a130ef8c02ca65e045ac11b84b3692a7663da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 8 Jul 2024 19:46:46 +0200 Subject: [PATCH 1/4] Move to VCPKG. * Use CMake with presets to generate USVFS solution. * Use preset when building super repository. * Allow overriding VCPKG_ROOT with [paths] entry. * Remove gamebryo and nuget specific stuff. * Remove cmake command. Add done log. * Use VCPKG from VS if no VCPKG_ROOT or path setting. --- mob.ini | 7 +- src/CMakeLists.txt | 2 +- src/cmd/cmake.cpp | 70 --------------- src/cmd/commands.h | 21 ----- src/core/conf.cpp | 44 ++++++---- src/core/conf.h | 43 +++++++--- src/core/op.cpp | 24 ++++++ src/core/op.h | 5 ++ src/core/paths.cpp | 18 ++++ src/core/paths.h | 5 ++ src/main.cpp | 89 +++++++------------ src/tasks/modorganizer.cpp | 131 ++++++++++------------------ src/tasks/sevenz.cpp | 2 +- src/tasks/task.cpp | 1 + src/tasks/tasks.h | 56 ++---------- src/tasks/translations.cpp | 172 +++++++++++++++++++++---------------- src/tasks/usvfs.cpp | 46 +++++----- src/tools/cmake.cpp | 139 ++++++++++++++++++++++++------ src/tools/cmake.h | 30 +++++++ 19 files changed, 469 insertions(+), 436 deletions(-) delete mode 100644 src/cmd/cmake.cpp diff --git a/mob.ini b/mob.ini index b32c74d..047a7bd 100644 --- a/mob.ini +++ b/mob.ini @@ -21,6 +21,10 @@ host = super = cmake_common modorganizer* githubpp plugins = check_fnis bsapacker bsa_extractor diagnose_basic installer_* plugin_python preview_base preview_bsa tool_* game_* +[translations] +mo2-translations = organizer +mo2-game-bethesda = game_creation game_enderal game_enderalse game_fallout3 game_fallout4 game_fallout4vr game_falloutNV game_gamebryo game_morrowind game_nehrim game_oblivion game_skyrim game_skyrimse game_skyrimvr game_ttw + [task] enabled = true mo_org = ModOrganizer2 @@ -150,11 +154,12 @@ install_pdbs = install_dlls = install_loot = install_plugins = +install_extensions = install_stylesheets = install_licenses = install_pythoncore = -install_translations = vs = +vcpkg = qt_install = qt_bin = qt_translations = diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b87b84b..67237f4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ file(GLOB_RECURSE source_files *.cpp) file(GLOB_RECURSE header_files *.h) add_executable(mob ${source_files} ${header_files}) -set_target_properties(mob PROPERTIES CXX_STANDARD 20) +set_target_properties(mob PROPERTIES CXX_STANDARD 23) target_compile_definitions(mob PUBLIC NOMINMAX) target_compile_options(mob PUBLIC "/MT") diff --git a/src/cmd/cmake.cpp b/src/cmd/cmake.cpp deleted file mode 100644 index f6ef356..0000000 --- a/src/cmd/cmake.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "pch.h" -#include "../tasks/tasks.h" -#include "commands.h" - -namespace mob { - - cmake_command::cmake_command() : command(requires_options) {} - - command::meta_t cmake_command::meta() const - { - return {"cmake", "runs cmake in a directory"}; - } - - clipp::group cmake_command::do_group() - { - return clipp::group( - clipp::command("cmake").set(picked_), - - (clipp::option("-h", "--help") >> help_) % "shows this message", - - (clipp::option("-G", "--generator") & clipp::value("GEN") >> gen_) % - ("sets the -G option for cmake [default: VS]"), - - (clipp::option("-c", "--cmd") & clipp::value("CMD") >> cmd_) % - "overrides the cmake command line [default: \"..\"]", - - (clipp::option("--x64").set(x64_, true) | - clipp::option("--x86").set(x64_, false)) % - "whether to use the x64 or x86 vcvars; if -G is not set, " - "whether to pass \"-A Win32\" or \"-A x64\" for the default " - "VS generator [default: x64]", - - (clipp::option("--install-prefix") & clipp::value("PATH") >> prefix_) % - "sets CMAKE_INSTALL_PREFIX [default: empty]", - - (clipp::option("-d", "--debug").set(debug_, true)) % - "whether to configure for debug mode [default: false]", - - (clipp::value("PATH") >> path_) % "path from which to run `cmake`"); - } - - int cmake_command::do_run() - { - auto t = tasks::modorganizer::create_cmake_tool( - fs::path(utf8_to_utf16(path_)), mob::cmake::generate, - debug_ ? config::debug : config::relwithdebinfo); - - t.generator(gen_); - t.cmd(cmd_); - t.prefix(prefix_); - t.output(path_); - - if (!x64_) - t.architecture(arch::x86); - - // copy the global context, the tool will modify it - context cxcopy(gcx()); - - t.run(cxcopy); - - return 0; - } - - std::string cmake_command::do_doc() - { - return "Runs `cmake ..` in the given directory with the same command line\n" - "as the one used for modorganizer projects."; - } - -} // namespace mob diff --git a/src/cmd/commands.h b/src/cmd/commands.h index 267a133..beb177c 100644 --- a/src/cmd/commands.h +++ b/src/cmd/commands.h @@ -367,27 +367,6 @@ namespace mob { std::vector get_repos() const; }; - // runs cmake in a directory with the same parameters as `build` would - // - class cmake_command : public command { - public: - cmake_command(); - meta_t meta() const override; - - protected: - clipp::group do_group() override; - int do_run() override; - std::string do_doc() override; - - private: - std::string gen_; - std::string cmd_; - bool x64_ = true; - bool debug_ = false; - std::string prefix_; - std::string path_; - }; - // lists the inis found by mob // class inis_command : public command { diff --git a/src/core/conf.cpp b/src/core/conf.cpp index e122f13..b8f01da 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -51,24 +51,32 @@ namespace mob::details { // returns a string from conf, bails out if it doesn't exist // - std::string get_string(std::string_view section, std::string_view key) + std::string get_string(std::string_view section, std::string_view key, + std::optional default_) { auto sitor = g_conf.find(section); if (sitor == g_conf.end()) gcx().bail_out(context::conf, "[{}] doesn't exist", section); auto kitor = sitor->second.find(key); - if (kitor == sitor->second.end()) - gcx().bail_out(context::conf, "no key '{}' in [{}]", key, section); + if (kitor == sitor->second.end()) { + if (!default_.has_value()) { + gcx().bail_out(context::conf, "no key '{}' in [{}]", key, section); + } + return *default_; + } return kitor->second; } // calls get_string(), converts to int // - int get_int(std::string_view section, std::string_view key) + int get_int(std::string_view section, std::string_view key, + std::optional default_) { - const auto s = get_string(section, key); + const auto s = get_string(section, key, default_.transform([](auto v) { + return std::to_string(v); + })); try { return std::stoi(s); @@ -80,9 +88,12 @@ namespace mob::details { // calls get_string(), converts to bool // - bool get_bool(std::string_view section, std::string_view key) + bool get_bool(std::string_view section, std::string_view key, + std::optional default_) { - const auto s = get_string(section, key); + const auto s = get_string(section, key, default_.transform([](auto v) { + return v ? "true" : "false"; + })); return bool_from_string(s); } @@ -428,13 +439,8 @@ namespace mob { MOB_ASSERT(!tasks.empty()); - for (auto& t : tasks) { - if (t->name() != task && - details::find_string_for_task(t->name(), key)) { - continue; - } + for (auto& t : tasks) details::set_string_for_task(t->name(), key, value); - } } else { // global task option @@ -509,6 +515,7 @@ namespace mob { set_path_if_empty("pf_x86", find_program_files_x86); set_path_if_empty("pf_x64", find_program_files_x64); set_path_if_empty("vs", find_vs); + set_path_if_empty("vcpkg", find_vcpkg); // set after vs as it will use the VS set_path_if_empty("qt_install", find_qt); set_path_if_empty("temp_dir", find_temp_dir); set_path_if_empty("patches", find_in_root("patches")); @@ -530,7 +537,7 @@ namespace mob { resolve_path("install", p.prefix(), "install"); resolve_path("install_installer", p.install(), "installer"); resolve_path("install_bin", p.install(), "bin"); - resolve_path("install_libs", p.install(), "libs"); + resolve_path("install_libs", p.install(), "lib"); resolve_path("install_pdbs", p.install(), "pdb"); resolve_path("install_dlls", p.install_bin(), "dlls"); resolve_path("install_loot", p.install_bin(), "loot"); @@ -538,7 +545,7 @@ namespace mob { resolve_path("install_licenses", p.install_bin(), "licenses"); resolve_path("install_pythoncore", p.install_bin(), "pythoncore"); resolve_path("install_stylesheets", p.install_bin(), "stylesheets"); - resolve_path("install_translations", p.install_bin(), "translations"); + resolve_path("install_extensions", p.install_bin(), "extensions"); // finally, resolve the tools that are unlikely to be in PATH; all the // other tools (7z, jom, patch, etc.) are assumed to be in PATH (which @@ -684,6 +691,11 @@ namespace mob { return {}; } + conf_translations conf::translation() + { + return {}; + } + conf_prebuilt conf::prebuilt() { return {}; @@ -777,6 +789,8 @@ namespace mob { conf_build_types::conf_build_types() : conf_section("build-types") {} + conf_translations::conf_translations() : conf_section("translations") {} + conf_prebuilt::conf_prebuilt() : conf_section("prebuilt") {} conf_paths::conf_paths() : conf_section("paths") {} diff --git a/src/core/conf.h b/src/core/conf.h index 2589394..9ce92e8 100644 --- a/src/core/conf.h +++ b/src/core/conf.h @@ -9,7 +9,8 @@ namespace mob::details { // returns an option named `key` from the given `section` // - std::string get_string(std::string_view section, std::string_view key); + std::string get_string(std::string_view section, std::string_view key, + std::optional default_ = {}); // convert a string to the given type template @@ -25,11 +26,13 @@ namespace mob::details { // calls get_string(), converts to bool // - bool get_bool(std::string_view section, std::string_view key); + bool get_bool(std::string_view section, std::string_view key, + std::optional default_ = {}); // calls get_string(), converts to in // - int get_int(std::string_view section, std::string_view key); + int get_int(std::string_view section, std::string_view key, + std::optional default_ = {}); // sets the given option, bails out if the option doesn't exist // @@ -60,9 +63,17 @@ namespace mob { template class conf_section { public: - DefaultType get(std::string_view key) const + DefaultType get(std::string_view key, + std::optional default_ = {}) const { - const auto value = details::get_string(name_, key); + const auto value = [=] { + if constexpr (std::is_same_v) { + return details::get_string(name_, key, default_); + } + else { + return details::get_string(name_, key); + } + }(); if constexpr (std::is_convertible_v) { return value; @@ -74,18 +85,18 @@ namespace mob { // undefined template - T get(std::string_view key) const; + T get(std::string_view key, std::optional default_ = {}) const; template <> - bool get(std::string_view key) const + bool get(std::string_view key, std::optional default_) const { - return details::get_bool(name_, key); + return details::get_bool(name_, key, default_); } template <> - int get(std::string_view key) const + int get(std::string_view key, std::optional default_) const { - return details::get_int(name_, key); + return details::get_int(name_, key, default_); } void set(std::string_view key, std::string_view value) @@ -223,6 +234,13 @@ namespace mob { conf_build_types(); }; + // options in [translations] + // + class conf_translations : public conf_section { + public: + conf_translations(); + }; + // options in [prebuilt] // class conf_prebuilt : public conf_section { @@ -257,13 +275,13 @@ namespace mob { VALUE(install_dlls); VALUE(install_loot); - VALUE(install_plugins); + VALUE(install_extensions); VALUE(install_stylesheets); VALUE(install_licenses); VALUE(install_pythoncore); - VALUE(install_translations); VALUE(vs); + VALUE(vcpkg); VALUE(qt_install); VALUE(qt_bin); VALUE(qt_translations); @@ -285,6 +303,7 @@ namespace mob { conf_cmake cmake(); conf_tools tool(); conf_transifex transifex(); + conf_translations translation(); conf_prebuilt prebuilt(); conf_versions version(); conf_build_types build_types(); diff --git a/src/core/op.cpp b/src/core/op.cpp index bf0a051..62fc7e9 100644 --- a/src/core/op.cpp +++ b/src/core/op.cpp @@ -116,6 +116,30 @@ namespace mob::op { } } + void delete_file_glob_recurse(const context& cx, const fs::path& directory, + const fs::path& glob, flags f) + { + cx.trace(context::fs, "deleting glob {}", glob); + + const auto native = glob.native(); + + if (!fs::exists(directory)) + return; + + for (auto&& e : fs::recursive_directory_iterator(directory)) { + const auto p = e.path(); + const auto name = p.filename().native(); + + if (!PathMatchSpecW(name.c_str(), native.c_str())) { + cx.trace(context::fs, "{} did not match {}; skipping", name, glob); + + continue; + } + + delete_file(cx, p, f); + } + } + void remove_readonly(const context& cx, const fs::path& dir, flags f) { cx.trace(context::fs, "removing read-only from {}", dir); diff --git a/src/core/op.h b/src/core/op.h index eafd58a..15d0a4b 100644 --- a/src/core/op.h +++ b/src/core/op.h @@ -65,6 +65,11 @@ namespace mob::op { // void delete_file_glob(const context& cx, const fs::path& glob, flags f = noflags); + // deletes all files matching the glob in the given directory and its subdirectories + // + void delete_file_glob_recurse(const context& cx, const fs::path& directory, + const fs::path& glob, flags f = noflags); + // removes the readonly flag for all files in `dir`, recursive // void remove_readonly(const context& cx, const fs::path& dir, flags f = noflags); diff --git a/src/core/paths.cpp b/src/core/paths.cpp index 8652459..9248a64 100644 --- a/src/core/paths.cpp +++ b/src/core/paths.cpp @@ -254,6 +254,24 @@ namespace mob { } } + fs::path find_vcpkg() + { + const auto env_path = this_env::get().get("VCPKG_ROOT"); + + if (!env_path.empty()) { + return fs::absolute(env_path); + } + + const auto vs_path = conf().path().vs(); + const auto vcpkg_vs_path = vs_path / "VC" / "vcpkg"; + if (!exists(vcpkg_vs_path)) { + gcx().bail_out(context::conf, "vcpkg is not part of VS installation at {}", + vs_path); + } + + return vcpkg_vs_path; + } + fs::path find_qt() { // check from the ini first diff --git a/src/core/paths.h b/src/core/paths.h index 597823a..b36d806 100644 --- a/src/core/paths.h +++ b/src/core/paths.h @@ -33,6 +33,11 @@ namespace mob { // fs::path find_vs(); + // returns the absolute path to VCPKG root directory to be used as VCPKG_ROOT when + // building + // + fs::path find_vcpkg(); + // returns the absolute path to Qt's root directory, the one that contains // bin, include, etc.; bails if not found // diff --git a/src/main.cpp b/src/main.cpp index ce35867..f5be353 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,33 +26,33 @@ namespace mob { // third-party tasks - add_task() - .add_task() - .add_task() - .add_task() - .add_task() - .add_task() - .add_task() - .add_task() - .add_task(); - - add_task() - .add_task() - .add_task() - .add_task(); - - add_task() - .add_task() - .add_task() - .add_task(); - - add_task() - .add_task() - .add_task() - .add_task() - .add_task() - .add_task() - .add_task(); + // add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task(); + + // add_task() + // .add_task() + // .add_task() + // .add_task(); + + // add_task() + // .add_task() + // .add_task() + // .add_task(); + + // add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task() + // .add_task(); // super tasks @@ -61,9 +61,9 @@ namespace mob { // most of the alternate names below are from the transifex slugs, which // are sometimes different from the project names, for whatever reason - add_task() - .add_task("cmake_common") - .add_task("modorganizer-uibase"); + add_task().add_task().add_task("cmake_common"); + + add_task("modorganizer-uibase"); add_task() .add_task("modorganizer-archive") @@ -72,34 +72,12 @@ namespace mob { .add_task("modorganizer-bsatk") .add_task("modorganizer-nxmhandler") .add_task("modorganizer-helper") - .add_task("githubpp") - .add_task("modorganizer-game_gamebryo") .add_task({"modorganizer-bsapacker", "bsa_packer"}) - .add_task("modorganizer-preview_bsa"); - - // the gamebryo flag must be set for all game plugins that inherit from - // the gamebryo classes; this will merge the .ts file from gamebryo with - // the one from the specific plugin - add_task() - .add_task("modorganizer-game_oblivion", mo::gamebryo) - .add_task("modorganizer-game_nehrim", mo::gamebryo) - .add_task("modorganizer-game_fallout3", mo::gamebryo) - .add_task("modorganizer-game_fallout4", mo::gamebryo) - .add_task("modorganizer-game_fallout4vr", mo::gamebryo) - .add_task("modorganizer-game_fallout76", mo::gamebryo) - .add_task("modorganizer-game_falloutnv", mo::gamebryo) - .add_task("modorganizer-game_morrowind", mo::gamebryo) - .add_task("modorganizer-game_skyrim", mo::gamebryo) - .add_task("modorganizer-game_skyrimse", mo::gamebryo) - .add_task("modorganizer-game_skyrimvr", mo::gamebryo) - .add_task("modorganizer-game_starfield", mo::gamebryo) - .add_task("modorganizer-game_ttw", mo::gamebryo) - .add_task("modorganizer-game_enderal", mo::gamebryo) - .add_task("modorganizer-game_enderalse", mo::gamebryo); + .add_task("modorganizer-preview_bsa") + .add_task("modorganizer-game_bethesda"); add_task() .add_task({"modorganizer-tool_inieditor", "inieditor"}) - .add_task({"modorganizer-tool_inibakery", "inibakery"}) .add_task("modorganizer-preview_base") .add_task("modorganizer-diagnose_basic") .add_task("modorganizer-check_fnis") @@ -109,7 +87,7 @@ namespace mob { .add_task("modorganizer-installer_quick") .add_task("modorganizer-installer_fomod") .add_task("modorganizer-installer_fomod_csharp") - .add_task("modorganizer-installer_omod", mo::nuget) + .add_task("modorganizer-installer_omod") .add_task("modorganizer-installer_wizard") .add_task("modorganizer-bsa_extractor") .add_task("modorganizer-plugin_python"); @@ -147,7 +125,6 @@ namespace mob { std::make_unique(), std::make_unique(), std::make_unique(), - std::make_unique(), std::make_unique(), std::make_unique()}; diff --git a/src/tasks/modorganizer.cpp b/src/tasks/modorganizer.cpp index 2ad8f9d..9a053d1 100644 --- a/src/tasks/modorganizer.cpp +++ b/src/tasks/modorganizer.cpp @@ -3,6 +3,15 @@ namespace mob::tasks { + // build CMAKE_PREFIX_PATH for MO2 tasks + // + std::string cmake_prefix_path() + { + return conf().path().qt_install().string() + ";" + + (modorganizer::super_path() / "cmake_common").string() + ";" + + (conf().path().install() / "lib" / "cmake").string(); + } + // given a vector of names (some projects have more than one, see add_tasks() in // main.cpp), this prepends the simplified name to the vector and returns it // @@ -59,18 +68,18 @@ namespace mob::tasks { g.init_repo(); } - modorganizer::modorganizer(std::string long_name, flags f) - : modorganizer(std::vector{long_name}, f) + modorganizer::modorganizer(std::string long_name) + : modorganizer(std::vector{long_name}) { } - modorganizer::modorganizer(std::vector names, flags f) - : modorganizer(std::vector(names.begin(), names.end()), f) + modorganizer::modorganizer(std::vector names) + : modorganizer(std::vector(names.begin(), names.end())) { } - modorganizer::modorganizer(std::vector names, flags f) - : task(make_names(names)), repo_(names[0]), flags_(f) + modorganizer::modorganizer(std::vector names) + : task(make_names(names)), repo_(names[0]) { if (names.size() > 1) { project_ = names[1]; @@ -80,34 +89,15 @@ namespace mob::tasks { } } - bool modorganizer::is_gamebryo_plugin() const - { - return is_set(flags_, gamebryo); - } - - bool modorganizer::is_nuget_plugin() const - { - return is_set(flags_, nuget); - } - fs::path modorganizer::source_path() const { // something like build/modorganizer_super/uibase return super_path() / name(); } - fs::path modorganizer::project_file_path() const - { - // ask cmake for the build path it would use - const auto build_path = create_cmake_tool(source_path()).build_path(); - - // use the INSTALL project - return build_path / (project_ + ".sln"); - } - fs::path modorganizer::super_path() { - return conf().path().build() / "modorganizer_super"; + return conf().path().build(); } url modorganizer::git_url() const @@ -137,11 +127,7 @@ namespace mob::tasks { // cmake clean if (is_set(c, clean::reconfigure)) - run_tool(create_cmake_tool(cmake::clean)); - - // msbuild clean - if (is_set(c, clean::rebuild)) - run_tool(create_msbuild_tool(msbuild::clean)); + run_tool(cmake(cmake::clean).root(source_path())); } void modorganizer::do_fetch() @@ -165,7 +151,7 @@ namespace mob::tasks { void modorganizer::do_build_and_install() { - // adds a git submodule in modorganizer_super for this project; note that + // adds a git submodule in build for this project; note that // git_submodule_adder runs a thread because adding submodules is slow, but // can happen while stuff is building git_submodule_adder::instance().queue( @@ -177,65 +163,42 @@ namespace mob::tasks { // not all modorganizer projects need to actually be built, such as // cmake_common, so don't try if there's no cmake file - if (!fs::exists(source_path() / "CMakeLists.txt")) { + if (!exists(source_path() / "CMakeLists.txt")) { cx().trace(context::generic, "{} has no CMakeLists.txt, not building", repo_); return; } - // run cmake - run_tool(create_cmake_tool()); - - // run restore for nuget - // - // until https://gitlab.kitware.com/cmake/cmake/-/issues/20646 is resolved, - // we need a manual way of running the msbuild -t:restore - if (is_nuget_plugin()) - run_tool(create_msbuild_tool().targets({"restore"})); - - // run msbuild - run_tool(create_msbuild_tool()); - } - - cmake modorganizer::create_cmake_tool(cmake::ops o) - { - return create_cmake_tool(source_path(), o, task_conf().configuration()); - } - - cmake modorganizer::create_cmake_tool(const fs::path& root, cmake::ops o, config c) - { - return std::move( - cmake(o) - .generator(cmake::vs) - .def("CMAKE_INSTALL_PREFIX:PATH", conf().path().install()) - .def("DEPENDENCIES_DIR", conf().path().build()) - .def("BOOST_ROOT", boost::source_path()) - .def("BOOST_LIBRARYDIR", boost::lib_path(arch::x64)) - .def("SPDLOG_ROOT", spdlog::source_path()) - .def("LOOT_PATH", libloot::source_path()) - .def("LZ4_ROOT", lz4::source_path()) - .def("QT_ROOT", qt::installation_path()) - .def("ZLIB_ROOT", zlib::source_path()) - .def("PYTHON_ROOT", python::source_path()) - .def("SEVENZ_ROOT", sevenz::source_path()) - .def("LIBBSARCH_ROOT", libbsarch::source_path()) - .def("BOOST_DI_ROOT", boost_di::source_path()) - // gtest has no RelWithDebInfo, so simply use Debug/Release - .def("GTEST_ROOT", - gtest::build_path(arch::x64, c == config::debug ? config::debug - : config::release)) - .def("OPENSSL_ROOT_DIR", openssl::source_path()) - .def("DIRECTXTEX_ROOT", directxtex::source_path()) - .root(root)); - } + // if there is a CMakeLists.txt, there must be a CMakePresets.json otherwise + // we cannot build + if (!exists(source_path() / "CMakePresets.json")) { + gcx().bail_out(context::generic, + "{} has no CMakePresets.txt, aborting build", repo_); + } - msbuild modorganizer::create_msbuild_tool(msbuild::ops o) - { - return std::move(msbuild(o) - .solution(project_file_path()) - .configuration(task_conf().configuration()) - .architecture(arch::x64)); + // run cmake + run_tool(cmake(cmake::generate) + .generator(cmake::vs) + .def("CMAKE_INSTALL_PREFIX:PATH", conf().path().install()) + .def("CMAKE_PREFIX_PATH", cmake_prefix_path()) + .preset("vs2022-windows") + .root(source_path())); + + // run cmake --build with default target + // TODO: handle rebuild by adding `--clean-first` + // TODO: have a way to specify the `--parallel` value - 16 is useful to build + // game_bethesda that has 15 games, so 15 projects + run_tool(cmake(cmake::build) + .root(source_path()) + .arg("--parallel") + .arg("16") + .configuration(mob::config::relwithdebinfo)); + + // run cmake --install + run_tool(cmake(cmake::install) + .root(source_path()) + .configuration(mob::config::relwithdebinfo)); } } // namespace mob::tasks diff --git a/src/tasks/sevenz.cpp b/src/tasks/sevenz.cpp index f552508..1a107dd 100644 --- a/src/tasks/sevenz.cpp +++ b/src/tasks/sevenz.cpp @@ -79,7 +79,7 @@ namespace mob::tasks { void sevenz::build() { - build_loop(cx(), [&](bool mp) { + build_loop(cx(), [&]([[maybe_unused]] bool mp) { const int exit_code = run_tool(nmake() .path(module_to_build()) .def("CPU=x64") diff --git a/src/tasks/task.cpp b/src/tasks/task.cpp index 451366b..5f411ee 100644 --- a/src/tasks/task.cpp +++ b/src/tasks/task.cpp @@ -413,6 +413,7 @@ namespace mob { cx().info(context::generic, "build and install"); do_build_and_install(); + cx().info(context::generic, "done"); } void task::check_bailed() diff --git a/src/tasks/tasks.h b/src/tasks/tasks.h index 8b9b116..390258c 100644 --- a/src/tasks/tasks.h +++ b/src/tasks/tasks.h @@ -216,34 +216,13 @@ namespace mob::tasks { cmake::ops o = cmake::generate, config config = config::relwithdebinfo); - // flags for some MO projects - enum flags { - noflags = 0x00, - - // gamebryo project, used by the translations task because these - // projects have multiple .ts files that have to be merged - gamebryo = 0x01, - - // project that uses nuget, cmake doesn't support those right now, so - // `msbuild -t:restore` has to be run manually - nuget = 0x02, - }; - // some mo tasks have more than one name, mostly because the transifex slugs // are different than the names on github; the std::string and const char* // overloads are because they're constructed from initializer lists and it's // more convenient that way - modorganizer(std::string name, flags f = noflags); - modorganizer(std::vector names, flags f = noflags); - modorganizer(std::vector names, flags f = noflags); - - // whether this project has the gamebryo flag on - // - bool is_gamebryo_plugin() const; - - // whether this project has the nuget flag on - // - bool is_nuget_plugin() const; + modorganizer(std::string name); + modorganizer(std::vector names); + modorganizer(std::vector names); // url to the git repo // @@ -271,27 +250,6 @@ namespace mob::tasks { private: std::string repo_; std::string project_; - flags flags_; - - // creates the cmake tool for this MO project - // - cmake create_cmake_tool(cmake::ops o = cmake::generate); - - // creates the msbuild tool for this MO project - // - msbuild create_msbuild_tool(msbuild::ops o = msbuild::build); - - // this is the file targeted by the msbuild tool - // - // it's not actually the .sln file because the cmake files have historically - // been inconsistent in what the main project in the solution is and whether - // the INSTALL project was enabled or not, so just building the .sln itself - // might not actually build everything - // - // by targeting the INSTALL project directly, everything will always be - // built correctly, regardless of how the solution file is generated - // - fs::path project_file_path() const; }; class ncc : public basic_task { @@ -623,11 +581,6 @@ namespace mob::tasks { // duplicate warnings std::set warned_; - // whether the given project name is a gamebryo task, `dir` is just for - // logging - // - bool is_gamebryo_plugin(const std::string& dir, const std::string& project); - // parses the directory name, walks all the .ts files, returns a project // object for them // @@ -636,7 +589,7 @@ namespace mob::tasks { // returns a lang object that contains at least the given main_ts_file, // but might contain more if it's a gamebryo plugin // - lang create_lang(bool gamebryo, const std::string& project_name, + lang create_lang(const std::string& project_name, const fs::path& main_ts_file); }; @@ -671,6 +624,7 @@ namespace mob::tasks { void fetch_from_source(); void build_and_install_from_source(); + cmake create_cmake_tool(arch, cmake::ops = cmake::generate) const; msbuild create_msbuild_tool(arch, msbuild::ops = msbuild::build, config = config::release) const; }; diff --git a/src/tasks/translations.cpp b/src/tasks/translations.cpp index bc29c50..1da7b64 100644 --- a/src/tasks/translations.cpp +++ b/src/tasks/translations.cpp @@ -1,6 +1,8 @@ #include "pch.h" #include "../core/env.h" +#include "../utility/string.h" #include "../utility/threading.h" +#include "nlohmann/json.hpp" #include "task_manager.h" #include "tasks.h" @@ -125,9 +127,6 @@ namespace mob::tasks { // walks all the .ts files in the project, creates a `lang` object for // each // - // each project directory is named "mod-organizer-2.project_name", so this - // splits on the dot to get the project name, checks if it's a gamebryo - // plugin, and adds the gamebryo .ts file as well if necessary // splitting const auto dir_name = path_to_utf8(dir.filename()); @@ -150,7 +149,6 @@ namespace mob::tasks { // project project p(project_name); - const bool gamebryo = is_gamebryo_plugin(dir_name, project_name); // for each file for (auto f : fs::directory_iterator(dir)) { @@ -168,14 +166,14 @@ namespace mob::tasks { } // add a new `lang` object for it - p.langs.push_back(create_lang(gamebryo, project_name, f.path())); + p.langs.push_back(create_lang(project_name, f.path())); } return p; } translations::projects::lang - translations::projects::create_lang(bool gamebryo, const std::string& project_name, + translations::projects::create_lang(const std::string& project_name, const fs::path& main_ts_file) { lang lg(path_to_utf8(main_ts_file.stem())); @@ -183,64 +181,9 @@ namespace mob::tasks { // every lang has the .ts file from the project, gamebryo plugins have more lg.ts_files.push_back(main_ts_file); - if (gamebryo) { - // this is a gamebryo plugin, so it needs the gamebryo .ts file as well, - // find it - - // the .ts files for gamebryo are in mod-organizer-2.game_gamebryo/ - const fs::path gamebryo_dir = - conf().transifex().get("project") + "." + "game_gamebryo"; - - // the .ts file has the same name, it's just "lang.ts" - const auto gamebryo_ts = root_ / gamebryo_dir / main_ts_file.filename(); - - if (fs::exists(gamebryo_ts)) { - // found, add it - lg.ts_files.push_back(gamebryo_ts); - } - else { - // not found, that means the plugin was translated into a language, - // but the gamebryo project wasn't; warn once - if (!warned_.contains(gamebryo_ts)) { - warned_.insert(gamebryo_ts); - - warnings_.push_back(::std::format( - "{} is a gamebryo plugin but there is no '{}'; the " - ".qm file will be missing some translations (will " - "only warn once)", - project_name, path_to_utf8(gamebryo_ts))); - } - } - } - return lg; } - bool translations::projects::is_gamebryo_plugin(const std::string& dir, - const std::string& project) - { - const auto* t = task_manager::instance().find_one(project); - - if (!t) { - warnings_.push_back( - ::std::format("directory '{}' was parsed as project '{}', but there's " - "no task with this name", - dir, project)); - - return false; - } - - // gamebryo plugins are all `modorganizer` tasks - const auto* mo_task = static_cast(t); - if (!mo_task) { - // not an mo task, can't be a gamebryo plugin - return false; - } - - // check the flag - return mo_task->is_gamebryo_plugin(); - } - translations::translations() : task("translations") {} fs::path translations::source_path() @@ -256,8 +199,8 @@ namespace mob::tasks { // remove the .qm files in the translations/ directory if (is_set(c, clean::rebuild)) { - op::delete_file_glob(cx(), conf().path().install_translations() / "*.qm", - op::optional); + op::delete_file_glob_recurse(cx(), conf().path().install_extensions(), + "*.qm", op::optional); } } @@ -310,44 +253,122 @@ namespace mob::tasks { } } + void generate_translations_metadata( + std::filesystem::path const& path, + std::vector const& languages) + { + using json = nlohmann::ordered_json; + + json metadata; + { + std::ifstream ifs(path); + metadata = json::parse(ifs); + } + + // fix version + json translations; + for (auto&& lang : languages) { + std::vector files; + files.push_back("translations/" + lang.name + "/*.qm"); + json jsonlang; + jsonlang["files"] = files; + translations[lang.name] = jsonlang; + } + metadata["content"]["translations"] = translations; + + std::ofstream ofs(path); + ofs << metadata.dump(2); + } + void translations::do_build_and_install() { // 1) build the list of projects, languages and .ts files // 2) run `lrelease` for every language in every project // 3) copy builtin qt translations - const auto root = source_path() / "translations"; - const auto dest = conf().path().install_translations(); + const auto root = source_path() / "translations"; + const auto extensions = conf().path().install_extensions(); const projects ps(root); - op::create_directories(cx(), dest); + op::create_directories(cx(), extensions / "mo2-translations"); // log all the warnings added while walking the projects for (auto&& w : ps.warnings()) cx().warning(context::generic, "{}", w); + std::map project_to_extension; + + // go through the list of extensions and find the matching projects + for (auto& p : fs::directory_iterator(extensions)) { + if (!fs::is_directory(p)) + continue; + + const auto name = + mob::replace_all(p.path().filename().string(), "mo2-", ""); + + project_to_extension[name] = p.path().filename(); + project_to_extension[mob::replace_all(name, "-", "_")] = + p.path().filename(); + + const auto s_projects = + conf().translation().get(p.path().filename().string(), ""); + if (!s_projects.empty()) { + const auto extension_projects = mob::split(s_projects, " "); + for (const auto& project : extension_projects) { + project_to_extension[project] = p.path().filename(); + } + } + } + // run `lrelease` in a thread pool parallel_functions v; // for each project for (auto& p : ps.get()) { + if (!project_to_extension.contains(p.name)) { + cx().warning(context::generic, + "found project {} but no matching extension", p.name); + continue; + } + + const auto base = extensions / project_to_extension[p.name]; + + if (!fs::exists(base)) { + cx().warning(context::generic, + "found project {} for extension {} extension is not built", + p.name, project_to_extension[p.name]); + continue; + } + + const auto dest = base / "translations"; + op::create_directories(cx(), dest); + // for each language for (auto& lg : p.langs) { + op::create_directories(cx(), dest / lg.name); // add a functor that will run lrelease - v.push_back( - {lg.name + "." + p.name, [&] { - // run release for the given project name and list of .ts files - run_tool( - lrelease().project(p.name).sources(lg.ts_files).out(dest)); - }}); + v.push_back({lg.name + "." + p.name, [=] { + // run release for the given project name and list of + // .ts files + run_tool(lrelease() + .project(p.name) + .sources(lg.ts_files) + .out(dest / lg.name)); + }}); } } // run all the functors in parallel parallel(v); - if (auto p = ps.find("organizer")) - copy_builtin_qt_translations(*p, dest); + if (auto p = ps.find("organizer")) { + // the empty metadata for mo2-translations is copied from modorganizer and + // then filled here + generate_translations_metadata( + extensions / "mo2-translations" / "metadata.json", p->langs); + copy_builtin_qt_translations(*p, extensions / "mo2-translations" / + "translations"); + } else cx().bail_out(context::generic, "organizer project not found"); } @@ -366,7 +387,8 @@ namespace mob::tasks { if (!fs::exists(src)) return false; - op::copy_file_to_dir_if_better(cx(), src, dest, op::unsafe); + op::create_directories(cx(), dest / lang); + op::copy_file_to_dir_if_better(cx(), src, dest / lang, op::unsafe); return true; }; diff --git a/src/tasks/usvfs.cpp b/src/tasks/usvfs.cpp index a61e277..e2bfe3d 100644 --- a/src/tasks/usvfs.cpp +++ b/src/tasks/usvfs.cpp @@ -40,6 +40,11 @@ namespace mob::tasks { return; } + if (is_set(c, clean::reconfigure)) { + run_tool(create_cmake_tool(arch::x64)); + run_tool(create_cmake_tool(arch::x86)); + } + if (is_set(c, clean::rebuild)) { // msbuild clean run_tool(create_msbuild_tool(arch::x86, msbuild::clean, @@ -69,35 +74,28 @@ namespace mob::tasks { void usvfs::build_and_install_from_source() { - run_tool(create_msbuild_tool(arch::x86)); + run_tool(create_cmake_tool(arch::x64)); + run_tool(create_cmake_tool(arch::x86)); run_tool(create_msbuild_tool(arch::x64)); + run_tool(create_msbuild_tool(arch::x86)); } - msbuild usvfs::create_msbuild_tool(arch a, msbuild::ops o, config c) const + cmake usvfs::create_cmake_tool(arch a, cmake::ops o) const { - // usvfs doesn't use "Win32" for 32-bit, it uses "x86" - // - // note that usvfs_proxy has a custom build step in Release that runs - // usvfs/vsbuild/stage_helper.cmd, which copies everything into - // install/ - - const std::string plat = (a == arch::x64 ? "x64" : "x86"); - - // udis requires python in its custom build step, so make sure it's on the - // path - // - // BOOST_PATH is in the env because external_dependencies.props will - // use it if it exists or reverts to a hardcoded path which might not be using - // the same version as mob if it wasn't updated return std::move( - msbuild(o) - .platform(plat) - .configuration(c) - .targets({"usvfs_proxy"}) - .solution(source_path() / "vsbuild" / "usvfs.sln") - .env(env::vs(a) - .prepend_path(python::build_path()) - .set("BOOST_PATH", path_to_utf8(boost::source_path())))); + cmake(o) + .root(source_path()) + .def("CMAKE_INSTALL_PREFIX:PATH", conf().path().install()) + .generator(cmake::vs) + .preset(a == arch::x64 ? "vs2022-windows-x64" : "vs2022-windows-x86") + .arg("-DBUILD_TESTING=OFF")); + } + + msbuild usvfs::create_msbuild_tool(arch a, msbuild::ops o, config c) const + { + const std::string vsbuild = (a == arch::x64 ? "vsbuild64" : "vsbuild32"); + return std::move(msbuild(o).architecture(a).configuration(c).solution( + source_path() / vsbuild / "usvfs.sln")); } } // namespace mob::tasks diff --git a/src/tools/cmake.cpp b/src/tools/cmake.cpp index 0f277e0..3d8e629 100644 --- a/src/tools/cmake.cpp +++ b/src/tools/cmake.cpp @@ -4,8 +4,23 @@ namespace mob { + namespace { + std::string config_to_string(config c) + { + switch (c) { + case config::debug: + return "Debug"; + case config::release: + return "Release"; + case config::relwithdebinfo: + return "RelWithDebInfo"; + } + gcx().bail_out(context::generic, "unknow configuration type {}", c); + } + } // namespace + cmake::cmake(ops o) - : basic_process_runner("cmake"), op_(o), gen_(jom), arch_(arch::def) + : basic_process_runner("cmake"), op_(o), gen_(vs), arch_(arch::def) { } @@ -62,6 +77,12 @@ namespace mob { return *this; } + cmake& cmake::preset(const std::string& s) + { + preset_ = s; + return *this; + } + cmake& cmake::arg(std::string s) { std::replace(s.begin(), s.end(), '\\', '/'); @@ -75,6 +96,24 @@ namespace mob { return *this; } + cmake& cmake::targets(const std::string& target) + { + targets_ = {target}; + return *this; + } + + cmake& cmake::targets(const std::vector& targets) + { + targets_ = targets; + return *this; + } + + cmake& cmake::configuration(mob::config config) + { + config_ = config; + return *this; + } + cmake& cmake::cmd(const std::string& s) { cmd_ = s; @@ -110,6 +149,16 @@ namespace mob { break; } + case build: { + do_build(); + break; + } + + case install: { + do_install(); + break; + } + default: { cx().bail_out(context::generic, "bad cmake op {}", op_); } @@ -126,43 +175,83 @@ namespace mob { auto p = process() .stdout_encoding(encodings::utf8) .stderr_encoding(encodings::utf8) - .binary(binary()) - .arg("-DCMAKE_BUILD_TYPE=Release") - .arg("-DCMAKE_INSTALL_MESSAGE=" + - conf_cmake::to_string(conf().cmake().install_message())) - .arg("--log-level=ERROR") - .arg("--no-warn-unused-cli"); - - if (genstring_.empty()) { - // there's always a generator name, but some generators don't need - // an architecture flag, like jom, so get_arch() might return an empty - // string - p.arg("-G", "\"" + g.name + "\"") - .arg(g.get_arch(arch_)) - .arg(g.get_host(conf().cmake().host())); - } - else { - // verbatim generator string - p.arg("-G", "\"" + genstring_ + "\""); + .binary(binary()); + + if (!preset_.empty()) { + p = p.arg("--preset").arg(preset_); } + p = p.arg("-DCMAKE_INSTALL_MESSAGE=" + + conf_cmake::to_string(conf().cmake().install_message())) + .arg("--log-level=ERROR") + .arg("--no-warn-unused-cli"); + // prefix if (!prefix_.empty()) p.arg("-DCMAKE_INSTALL_PREFIX=", prefix_); p.args(args_); - // `..` by default, overriden by cmd() - if (cmd_.empty()) - p.arg(".."); - else - p.arg(cmd_); + if (preset_.empty()) { + + if (genstring_.empty()) { + // there's always a generator name, but some generators don't need + // an architecture flag, like jom, so get_arch() might return an empty + // string + p.arg("-G", "\"" + g.name + "\"") + .arg(g.get_arch(arch_)) + .arg(g.get_host(conf().cmake().host())); + } + else { + // verbatim generator string + p.arg("-G", "\"" + genstring_ + "\""); + } + + // `..` by default, overriden by cmd() + if (cmd_.empty()) + p.arg(".."); + else + p.arg(cmd_); + } + + p.env(env::vs(arch_) + .set("CXXFLAGS", "/wd4566") + .set("VCPKG_ROOT", absolute(conf().path().vcpkg()).string())) + .cwd(preset_.empty() ? build_path() : root_); + + execute_and_join(p); + } + + void cmake::do_build() + { + auto p = process() + .stdout_encoding(encodings::utf8) + .stderr_encoding(encodings::utf8) + .binary(binary()) + .arg("--build") + .arg(build_path()) + .arg("--config") + .arg(config_to_string(config_)); - p.env(env::vs(arch_).set("CXXFLAGS", "/wd4566")).cwd(build_path()); + for (auto& target : targets_) { + p = p.arg("--target").arg(target); + } execute_and_join(p); } + void cmake::do_install() + { + execute_and_join(process() + .stdout_encoding(encodings::utf8) + .stderr_encoding(encodings::utf8) + .binary(binary()) + .arg("--install") + .arg(build_path()) + .arg("--config") + .arg(config_to_string(config_))); + } + void cmake::do_clean() { cx().trace(context::rebuild, "deleting all generator directories"); diff --git a/src/tools/cmake.h b/src/tools/cmake.h index ea8a3a6..9420fc5 100644 --- a/src/tools/cmake.h +++ b/src/tools/cmake.h @@ -29,6 +29,12 @@ namespace mob { // generates the build files generate = 1, + // build + build, + + // install + install, + // cleans the build files so they're regenerated from scratch clean }; @@ -54,6 +60,15 @@ namespace mob { // cmake& root(const fs::path& p); + // set the targets for build + // + cmake& targets(const std::string& target); + cmake& targets(const std::vector& target); + + // set the configuration to build or install + // + cmake& configuration(mob::config config); + // overrides the directory in which cmake will write build files // // by default, this is a directory inside what was given in root() with a @@ -75,6 +90,10 @@ namespace mob { cmake& def(const std::string& name, const fs::path& p); cmake& def(const std::string& name, const char* s); + // set a preset to run with cmake --preset + // + cmake& preset(const std::string& s); + // adds an arbitrary argument, passed verbatim // cmake& arg(std::string s); @@ -146,6 +165,9 @@ namespace mob { // what run() does ops op_; + // preset to run + std::string preset_; + // directory where CMakeLists.txt is fs::path root_; @@ -156,6 +178,12 @@ namespace mob { // passed as -DCMAKE_INSTALL_PREFIX fs::path prefix_; + // targets + std::vector targets_; + + // configuration + mob::config config_{mob::config::relwithdebinfo}; + // passed verbatim std::vector args_; @@ -175,6 +203,8 @@ namespace mob { // runs cmake // void do_generate(); + void do_build(); + void do_install(); // returns a list of generators handled by this tool, same ones as in the // `generators` enum on top From 41bb2a3032942780709e9adf66012aca20187352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 9 Aug 2024 17:44:38 +0200 Subject: [PATCH 2/4] Fix issues with translations. --- icons/translations.png | Bin 0 -> 7718 bytes mob.ini | 1 + src/core/conf.cpp | 1 + src/core/conf.h | 1 + src/tasks/translations.cpp | 41 +++++++++++++++++++++++++------------ 5 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 icons/translations.png diff --git a/icons/translations.png b/icons/translations.png new file mode 100644 index 0000000000000000000000000000000000000000..3664bd7905ae4b6f9dedfe62c796592f8d8d464a GIT binary patch literal 7718 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 bvk_jwVgEa3y7k#vHU^j-wK9HSM2Y_|@jHUn literal 0 HcmV?d00001 diff --git a/mob.ini b/mob.ini index 047a7bd..3a3ad52 100644 --- a/mob.ini +++ b/mob.ini @@ -143,6 +143,7 @@ python = release third_party = prefix = cache = +icons = patches = licenses = build = diff --git a/src/core/conf.cpp b/src/core/conf.cpp index b8f01da..1801d99 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -518,6 +518,7 @@ namespace mob { set_path_if_empty("vcpkg", find_vcpkg); // set after vs as it will use the VS set_path_if_empty("qt_install", find_qt); set_path_if_empty("temp_dir", find_temp_dir); + set_path_if_empty("icons", find_in_root("icons")); set_path_if_empty("patches", find_in_root("patches")); set_path_if_empty("licenses", find_in_root("licenses")); set_path_if_empty("qt_bin", qt::installation_path() / "bin"); diff --git a/src/core/conf.h b/src/core/conf.h index 9ce92e8..40ebbda 100644 --- a/src/core/conf.h +++ b/src/core/conf.h @@ -263,6 +263,7 @@ namespace mob { VALUE(third_party); VALUE(prefix); VALUE(cache); + VALUE(icons); VALUE(patches); VALUE(licenses); VALUE(build); diff --git a/src/tasks/translations.cpp b/src/tasks/translations.cpp index 1da7b64..bb0980b 100644 --- a/src/tasks/translations.cpp +++ b/src/tasks/translations.cpp @@ -259,11 +259,23 @@ namespace mob::tasks { { using json = nlohmann::ordered_json; - json metadata; - { - std::ifstream ifs(path); - metadata = json::parse(ifs); - } + json metadata = json::parse(R"( +{ + "id": "mo2-translations", + "name": "Translations for ModOrganizer2", + "version": "1.0.0", + "description": "Multi-language translations for ModOrganizer2 itself.", + "author": { + "name": "Mod Organizer 2", + "homepage": "https://www.modorganizer.org/" + }, + "icon": "translations.png", + "type": "translation", + "content": { + "translations": {} + } +} +)"); // fix version json translations; @@ -334,9 +346,10 @@ namespace mob::tasks { const auto base = extensions / project_to_extension[p.name]; if (!fs::exists(base)) { - cx().warning(context::generic, - "found project {} for extension {} extension is not built", - p.name, project_to_extension[p.name]); + cx().warning( + context::generic, + "found project {} for extension {} but extension is not built", + p.name, project_to_extension[p.name]); continue; } @@ -362,15 +375,17 @@ namespace mob::tasks { parallel(v); if (auto p = ps.find("organizer")) { - // the empty metadata for mo2-translations is copied from modorganizer and - // then filled here - generate_translations_metadata( - extensions / "mo2-translations" / "metadata.json", p->langs); + // copy Qt builting translation, icon and generate metadata copy_builtin_qt_translations(*p, extensions / "mo2-translations" / "translations"); + op::copy_file_to_file_if_better( + cx(), conf().path().icons() / "translations.png", + extensions / "mo2-translations" / "translations.png", op::unsafe); + generate_translations_metadata( + extensions / "mo2-translations" / "metadata.json", p->langs); } else - cx().bail_out(context::generic, "organizer project not found"); + cx().warning(context::generic, "organizer project not found"); } void translations::copy_builtin_qt_translations(const projects::project& p, From c9882c7d99f388acdf2646edf0282be5cb33ef5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 9 Aug 2024 20:53:44 +0200 Subject: [PATCH 3/4] Add uibase to the base translations. --- mob.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mob.ini b/mob.ini index 3a3ad52..27649e7 100644 --- a/mob.ini +++ b/mob.ini @@ -22,7 +22,7 @@ super = cmake_common modorganizer* githubpp plugins = check_fnis bsapacker bsa_extractor diagnose_basic installer_* plugin_python preview_base preview_bsa tool_* game_* [translations] -mo2-translations = organizer +mo2-translations = organizer uibase mo2-game-bethesda = game_creation game_enderal game_enderalse game_fallout3 game_fallout4 game_fallout4vr game_falloutNV game_gamebryo game_morrowind game_nehrim game_oblivion game_skyrim game_skyrimse game_skyrimvr game_ttw [task] From b6d75fd1a31222884e9ab0a007746697d4d33feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 10 Aug 2024 10:57:01 +0200 Subject: [PATCH 4/4] Add plugin_python to base translations. --- mob.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mob.ini b/mob.ini index 27649e7..0f81c0d 100644 --- a/mob.ini +++ b/mob.ini @@ -18,11 +18,11 @@ install_message = never host = [aliases] -super = cmake_common modorganizer* githubpp +super = cmake_common modorganizer* plugins = check_fnis bsapacker bsa_extractor diagnose_basic installer_* plugin_python preview_base preview_bsa tool_* game_* [translations] -mo2-translations = organizer uibase +mo2-translations = organizer uibase plugin_python mo2-game-bethesda = game_creation game_enderal game_enderalse game_fallout3 game_fallout4 game_fallout4vr game_falloutNV game_gamebryo game_morrowind game_nehrim game_oblivion game_skyrim game_skyrimse game_skyrimvr game_ttw [task]