Compare commits

...
24 Commits
Author SHA1 Message Date
pre-commit-ci[bot] e9d55a50a7 [pre-commit.ci] Pre-commit autoupdate. (#159)
updates:
- [github.com/pre-commit/mirrors-clang-format: v22.1.2 → v22.1.5](https://github.com/pre-commit/mirrors-clang-format/compare/v22.1.2...v22.1.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-08 15:54:09 +02:00
Jonathan Feenstra 17840a0d97 Fix typo in "Python Initialization failed" message (#158) 2026-06-04 09:01:06 +02:00
Mikaël Capelle 0b0f34b2da Change IExecutablesList.titleExists to contains to match UIBase name. (#157) 2026-05-08 12:54:40 +02:00
Jeremy Rimpo 1720aeedf8 Update vcpkg targets (#155) 2026-04-15 09:31:56 +02:00
Jeremy Rimpo 1c8ab3f1c1 Add blueprint prefix to game plugin (#156) 2026-04-15 09:01:02 +02:00
pre-commit-ci[bot] 5003bf75a7 [pre-commit.ci] Pre-commit autoupdate. (#153)
updates:
- [github.com/pre-commit/mirrors-clang-format: v21.1.8 → v22.1.2](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.8...v22.1.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-04-07 09:00:58 +02:00
Jonathan Feenstra b1ecd89c50 Add instance manager to plugin API (#152) 2026-02-08 10:28:25 +01:00
Jonathan Feenstra 076244faab Add executables list to plugin API (#151) 2026-01-31 16:49:51 +01:00
Jonathan Feenstra b81646562c Add instanceName and profiles methods to plugin API (#150) 2026-01-11 17:57:34 +01:00
pre-commit-ci[bot] 648db4db99 [pre-commit.ci] Pre-commit autoupdate. (#149)
updates:
- [github.com/pre-commit/mirrors-clang-format: v21.1.2 → v21.1.8](https://github.com/pre-commit/mirrors-clang-format/compare/v21.1.2...v21.1.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-05 19:32:00 +01:00
pre-commit-ci[bot] 15d74e5e76 [pre-commit.ci] Pre-commit autoupdate. (#148)
updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/pre-commit/mirrors-clang-format: v20.1.7 → v21.1.2](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.7...v21.1.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-10-07 15:00:39 +02:00
Mikaël Capelle 4f3a9891fa Add missing IPlugin inheritance in installer bindings. (#147) 2025-09-30 20:22:52 +02:00
Jonathan Feenstra 6bed428d04 Extend Python plugin API with author and uploader data (#145) 2025-08-18 07:55:42 +02:00
Mikaël Capelle f16e51867a Allow non-cache build in CI when Azure variables are not available. (#146) 2025-08-17 12:00:35 +02:00
Mikaël Capelle c05027b653 Update following move of IFileTree glob() and walk() to free function. (#144) 2025-07-08 19:33:04 +02:00
pre-commit-ci[bot] 5726e65504 [pre-commit.ci] Pre-commit autoupdate. (#143)
* [pre-commit.ci] Pre-commit autoupdate.

updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.5 → v20.1.7](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.5...v20.1.7)

* [pre-commit.ci] Auto fixes from pre-commit.com hooks.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-07-07 20:38:02 +02:00
Mikaël Capelle b0d2b48524 Add bindings for IFileTree glob() and walk() generators (#142)
* Adding binding for new IFileTree glob and walk.
* Fix issues with Version binding.
2025-07-06 19:38:16 +02:00
Mikaël Capelle 00a6d5969b Use exact Python version and fix missing DLLs for plugin. (#141) 2025-07-02 08:16:47 +02:00
Mikaël Capelle 1010bfda55 Move to VCPKG (#133) 2025-05-29 11:03:51 +02:00
Jeremy Rimpo 9a788c9f3f Add game interfaces (#140)
- modDataDirectory defines mod directory that maps to 'Data'
- getModMappings allows mapping directories to multiple locations
2025-05-21 18:43:47 +02:00
Jonathan Feenstra 84bf90d575 Add form and header versions to IPluginList (#139) 2025-01-31 20:07:33 +01:00
Jonathan Feenstra 8c47cea270 Add startDownloadNexusFileForGame (#138) 2025-01-02 14:11:06 +01:00
Jeremy RimpoandJonathan Feenstra 5cdf0f6f19 Blueprint support (#136)
---------

Co-authored-by: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com>
2024-10-03 09:45:05 +02:00
Jonathan Feenstra 4409375fd8 Add author and description methods to IPluginList (#135) 2024-09-28 19:58:34 +02:00
50 changed files with 918 additions and 277 deletions
+23 -10
View File
@@ -2,24 +2,37 @@ name: Build & Test Plugin Python
on:
push:
branches: master
branches: [master]
pull_request:
types: [opened, synchronize, reopened]
env:
VCPKG_BINARY_SOURCES: ${{ vars.AZ_BLOB_VCPKG_URL != '' && format('clear;x-azblob,{0},{1},readwrite', vars.AZ_BLOB_VCPKG_URL, secrets.AZ_BLOB_SAS) || '' }}
jobs:
build:
runs-on: windows-2022
steps:
- name: Build Plugin Python
id: build-plugin-python
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Configure Plugin Python
id: configure-plugin-python
uses: ModOrganizer2/build-with-mob-action@master
with:
mo2-third-parties: gtest python spdlog boost sip pyqt pybind11
mo2-dependencies: cmake_common uibase
mo2-cmake-command: -DPLUGIN_PYTHON_TESTS=1 ..
- name: Build Plugin Python Tests
run: cmake --build vsbuild --config RelWithDebInfo -j4 --target python-tests --target runner-tests
working-directory: ${{ steps.build-plugin-python.outputs.working-directory }}
mo2-dependencies: uibase
mo2-skip-build: true
- name: Build Plugin Python
working-directory: ${{ steps.configure-plugin-python.outputs.working-directory }}
run: cmake --build vsbuild --config RelWithDebInfo --verbose `
--target python-tests --target runner-tests --target proxy
- name: Test Plugin Python
working-directory: ${{ steps.configure-plugin-python.outputs.working-directory }}
run: ctest --test-dir vsbuild -C RelWithDebInfo --output-on-failure
working-directory: ${{ steps.build-plugin-python.outputs.working-directory }}
- name: Install Plugin Python
working-directory: ${{ steps.configure-plugin-python.outputs.working-directory }}
run: cmake --build vsbuild --config RelWithDebInfo --target INSTALL
+20
View File
@@ -0,0 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-case-conflict
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v22.1.5
hooks:
- id: clang-format
'types_or': [c++, c]
ci:
autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks."
autofix_prs: true
autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate."
autoupdate_schedule: quarterly
submodules: false
+27 -26
View File
@@ -2,44 +2,45 @@ cmake_minimum_required(VERSION 3.16)
cmake_policy(SET CMP0144 NEW)
if(DEFINED DEPENDENCIES_DIR)
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake)
else()
include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake)
endif()
project(plugin_python CXX)
set(PYTHON_BUILD_PATH ${PYTHON_ROOT}/PCBuild/amd64)
# we need mo2-cmake to obtain the Python version, but mo2-cmake will set
# CMAKE_MAP_IMPORTED_CONFIG_* which will trigger a tons of CMP0111 warnings for Python
# below so we need to reset these before finding Python and then reset them after
find_package(mo2-cmake CONFIG REQUIRED)
# find Python - lots of "Hints" since we have a weird setup
set(Python_USE_STATIC_LIBS False)
set(Python_INCLUDE_DIR ${PYTHON_ROOT}/Include)
set(Python_EXECUTABLE ${PYTHON_BUILD_PATH}/python.exe)
if (EXISTS "${PYTHON_BUILD_PATH}/python_d.exe")
set(Python_EXECUTABLE ${PYTHON_BUILD_PATH}/python_d.exe)
endif()
file(GLOB Python_LIBRARY ${PYTHON_BUILD_PATH}/python[0-9][0-9]*.lib)
find_package(Python COMPONENTS Interpreter Development REQUIRED)
set(_CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL ${CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL})
set(_CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO ${CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO})
set(_CMAKE_MAP_IMPORTED_CONFIG_RELEASE ${CMAKE_MAP_IMPORTED_CONFIG_RELEASE})
# pybind11 needs uppercase (at least EXECUTABLE and LIBRARY)
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
set(PYTHON_INCLUDE_DIR ${Python_INCLUDE_DIR})
set(PYTHON_LIBRARY ${Python_LIBRARY})
set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL "")
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO "")
set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE "")
# find Python before include mo2-cmake, otherwise this will trigger a bunch of CMP0111
# due to the imported configuration mapping variables defined in mo2.cmake
find_package(Python ${MO2_PYTHON_VERSION} EXACT COMPONENTS Interpreter Development REQUIRED)
find_package(pybind11 CONFIG REQUIRED)
set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL ${_CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL})
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO ${_CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO})
set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE ${_CMAKE_MAP_IMPORTED_CONFIG_RELEASE})
get_filename_component(Python_HOME ${Python_EXECUTABLE} PATH)
set(Python_DLL_DIR "${Python_HOME}/DLLs")
set(Python_LIB_DIR "${Python_HOME}/Lib")
mo2_python_install_pyqt()
# useful for naming DLL, zip, etc. (3.10 -> 310)
set(Python_VERSION_SHORT ${Python_VERSION_MAJOR}${Python_VERSION_MINOR})
# pybind11
add_subdirectory(${MO2_BUILD_PATH}/pybind11 ${CMAKE_CURRENT_BINARY_DIR}/pybind11)
# projects
add_subdirectory(src)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT proxy)
# tests (if requested)
set(PLUGIN_PYTHON_TESTS ${PLUGIN_PYTHON_TESTS} CACHE BOOL "build tests for plugin_python")
if (PLUGIN_PYTHON_TESTS)
set(BUILD_TESTING ${BUILD_TESTING} CACHE BOOL "build tests for plugin_python")
if (BUILD_TESTING)
enable_testing()
add_subdirectory(tests)
endif()
+73
View File
@@ -0,0 +1,73 @@
{
"configurePresets": [
{
"errors": {
"deprecated": true
},
"hidden": true,
"name": "cmake-dev",
"warnings": {
"deprecated": true,
"dev": true
}
},
{
"cacheVariables": {
"VCPKG_MANIFEST_NO_DEFAULT_FEATURES": {
"type": "BOOL",
"value": "ON"
}
},
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"hidden": true,
"name": "vcpkg"
},
{
"cacheVariables": {
"VCPKG_MANIFEST_FEATURES": {
"type": "STRING",
"value": "testing"
}
},
"hidden": true,
"inherits": ["vcpkg"],
"name": "vcpkg-dev"
},
{
"binaryDir": "${sourceDir}/vsbuild",
"architecture": {
"strategy": "set",
"value": "x64"
},
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/EHsc /MP /W4",
"VCPKG_TARGET_TRIPLET": {
"type": "STRING",
"value": "x64-windows-static-md"
}
},
"generator": "Visual Studio 17 2022",
"inherits": ["cmake-dev", "vcpkg-dev"],
"name": "vs2022-windows",
"toolset": "v143"
},
{
"cacheVariables": {
"VCPKG_MANIFEST_FEATURES": {
"type": "STRING",
"value": "standalone;testing"
}
},
"inherits": "vs2022-windows",
"name": "vs2022-windows-standalone"
}
],
"buildPresets": [
{
"name": "vs2022-windows",
"resolvePackageReferences": "on",
"configurePreset": "vs2022-windows"
}
],
"version": 4
}
+30 -4
View File
@@ -1,12 +1,38 @@
cmake_minimum_required(VERSION 3.16)
find_package(Qt6 COMPONENTS Core)
find_package(mo2-uibase CONFIG REQUIRED)
pybind11_add_module(mobase MODULE)
mo2_configure_library(mobase
SOURCE_TREE
mo2_default_source_group()
mo2_configure_target(mobase
NO_SOURCES
WARNINGS 4
EXTERNAL_WARNINGS 4
AUTOMOC ON
TRANSLATIONS OFF
PRIVATE_DEPENDS uibase Qt::Core
)
target_link_libraries(mobase PRIVATE pybind11::qt pybind11::utils)
mo2_target_sources(mobase
FOLDER src
PRIVATE
deprecation.cpp
deprecation.h
mobase.cpp
pybind11_all.h
)
mo2_target_sources(mobase
FOLDER src/wrappers
PRIVATE
./wrappers/basic_classes.cpp
./wrappers/game_features.cpp
./wrappers/known_folders.h
./wrappers/pyfiletree.cpp
./wrappers/pyfiletree.h
./wrappers/pyplugins.cpp
./wrappers/pyplugins.h
./wrappers/utils.cpp
./wrappers/widgets.cpp
./wrappers/wrappers.cpp
./wrappers/wrappers.h
)
target_link_libraries(mobase PRIVATE pybind11::qt pybind11::utils mo2::uibase Qt6::Core)
+1 -1
View File
@@ -7,7 +7,7 @@
#include <QCoreApplication>
#include "log.h"
#include <uibase/log.h>
namespace py = pybind11;
-17
View File
@@ -12,23 +12,6 @@
#include "wrappers/pyfiletree.h"
#include "wrappers/wrappers.h"
// TODO: remove these include (only for testing)
#include <QDir>
#include <QFile>
#include <QWidget>
#include <iplugin.h>
#include <iplugindiagnose.h>
#include <ipluginfilemapper.h>
#include <iplugingame.h>
#include <iplugininstaller.h>
#include <iplugininstallersimple.h>
#include <ipluginlist.h>
#include <ipluginmodpage.h>
#include <ipluginpreview.h>
#include <iplugintool.h>
#include <isavegame.h>
#include <isavegameinfowidget.h>
using namespace MOBase;
namespace py = pybind11;
+4 -3
View File
@@ -13,12 +13,13 @@
#include "pybind11_qt/pybind11_qt.h"
#include "pybind11_utils/functional.h"
#include "pybind11_utils/generator.h"
#include "pybind11_utils/shared_cpp_owner.h"
#include "pybind11_utils/smart_variant_wrapper.h"
#include <game_feature.h>
#include <isavegame.h>
#include <pluginrequirements.h>
#include <uibase/game_features/game_feature.h>
#include <uibase/isavegame.h>
#include <uibase/pluginrequirements.h>
namespace mo2::python {
+176 -19
View File
@@ -3,22 +3,28 @@
#include "../pybind11_all.h"
#include <format>
#include <memory>
#include <executableinfo.h>
#include <filemapping.h>
#include <guessedvalue.h>
#include <idownloadmanager.h>
#include <igamefeatures.h>
#include <iinstallationmanager.h>
#include <imodinterface.h>
#include <imodrepositorybridge.h>
#include <imoinfo.h>
#include <iplugin.h>
#include <iplugindiagnose.h>
#include <iplugingame.h>
#include <ipluginlist.h>
#include <pluginsetting.h>
#include <versioninfo.h>
#include <pybind11_utils/generator.h>
#include <uibase/executableinfo.h>
#include <uibase/filemapping.h>
#include <uibase/game_features/igamefeatures.h>
#include <uibase/guessedvalue.h>
#include <uibase/idownloadmanager.h>
#include <uibase/iexecutable.h>
#include <uibase/iexecutableslist.h>
#include <uibase/iinstallationmanager.h>
#include <uibase/iinstance.h>
#include <uibase/iinstancemanager.h>
#include <uibase/imodinterface.h>
#include <uibase/imodrepositorybridge.h>
#include <uibase/imoinfo.h>
#include <uibase/iplugin.h>
#include <uibase/iplugindiagnose.h>
#include <uibase/iplugingame.h>
#include <uibase/ipluginlist.h>
#include <uibase/pluginsetting.h>
#include <uibase/versioninfo.h>
#include "../deprecation.h"
#include "pyfiletree.h"
@@ -33,6 +39,89 @@ namespace mo2::python {
void add_versioninfo_classes(py::module_ m)
{
// Version
py::class_<Version> pyVersion(m, "Version");
py::enum_<Version::ReleaseType>(pyVersion, "ReleaseType")
.value("DEVELOPMENT", Version::Development)
.value("ALPHA", Version::Alpha)
.value("BETA", Version::Beta)
.value("RELEASE_CANDIDATE", Version::ReleaseCandidate)
.export_values();
py::enum_<Version::ParseMode>(pyVersion, "ParseMode")
.value("SEMVER", Version::ParseMode::SemVer)
.value("MO2", Version::ParseMode::MO2);
py::enum_<Version::FormatMode>(pyVersion, "FormatMode", py::arithmetic{})
.value("FORCE_SUBPATCH", Version::FormatMode::ForceSubPatch)
.value("NO_SEPARATOR", Version::FormatMode::NoSeparator)
.value("SHORT_ALPHA_BETA", Version::FormatMode::ShortAlphaBeta)
.value("NO_METADATA", Version::FormatMode::NoMetadata)
.value("CONDENSED",
static_cast<Version::FormatMode>(Version::FormatCondensed.toInt()))
.export_values()
.def("__xor__",
py::overload_cast<Version::FormatMode, Version::FormatModes>(
&operator^))
.def("__and__",
py::overload_cast<Version::FormatMode, Version::FormatModes>(
&operator&))
.def("__or__", py::overload_cast<Version::FormatMode, Version::FormatModes>(
&operator|))
.def("__rxor__",
py::overload_cast<Version::FormatMode, Version::FormatModes>(
&operator^))
.def("__rand__",
py::overload_cast<Version::FormatMode, Version::FormatModes>(
&operator&))
.def("__ror__",
py::overload_cast<Version::FormatMode, Version::FormatModes>(
&operator|));
pyVersion
.def_static("parse", &Version::parse, "value"_a,
"mode"_a = Version::ParseMode::SemVer)
.def(py::init<int, int, int, QString>(), "major"_a, "minor"_a, "patch"_a,
"metadata"_a = "")
.def(py::init<int, int, int, int, QString>(), "major"_a, "minor"_a,
"patch"_a, "subpatch"_a, "metadata"_a = "")
.def(py::init<int, int, int, Version::ReleaseType, QString>(), "major"_a,
"minor"_a, "patch"_a, "type"_a, "metadata"_a = "")
.def(py::init<int, int, int, int, Version::ReleaseType, QString>(),
"major"_a, "minor"_a, "patch"_a, "subpatch"_a, "type"_a,
"metadata"_a = "")
.def(py::init<int, int, int, Version::ReleaseType, int, QString>(),
"major"_a, "minor"_a, "patch"_a, "type"_a, "prerelease"_a,
"metadata"_a = "")
.def(py::init<int, int, int, int, Version::ReleaseType, int, QString>(),
"major"_a, "minor"_a, "patch"_a, "subpatch"_a, "type"_a,
"prerelease"_a, "metadata"_a = "")
.def(py::init<int, int, int, int,
std::vector<std::variant<int, Version::ReleaseType>>,
QString>(),
"major"_a, "minor"_a, "patch"_a, "subpatch"_a, "prereleases"_a,
"metadata"_a = "")
.def("isPreRelease", &Version::isPreRelease)
.def_property_readonly("major", &Version::major)
.def_property_readonly("minor", &Version::minor)
.def_property_readonly("patch", &Version::patch)
.def_property_readonly("subpatch", &Version::subpatch)
.def_property_readonly("prereleases", &Version::preReleases)
.def_property_readonly("build_metadata", &Version::buildMetadata)
.def("string", &Version::string, "mode"_a = Version::FormatModes{})
.def("__str__",
[](Version const& version) {
return version.string(Version::FormatCondensed);
})
.def(py::self < py::self)
.def(py::self > py::self)
.def(py::self <= py::self)
.def(py::self >= py::self)
.def(py::self != py::self)
.def(py::self == py::self);
// VersionInfo
py::enum_<MOBase::VersionInfo::ReleaseType>(m, "ReleaseType")
.value("final", MOBase::VersionInfo::RELEASE_FINAL)
.value("candidate", MOBase::VersionInfo::RELEASE_CANDIDATE)
@@ -114,6 +203,27 @@ namespace mo2::python {
.def("forced", &ExecutableForcedLoadSetting::forced)
.def("library", &ExecutableForcedLoadSetting::library)
.def("process", &ExecutableForcedLoadSetting::process);
py::class_<IExecutable>(m, "IExecutable")
.def("title", &IExecutable::title)
.def("binaryInfo", &IExecutable::binaryInfo)
.def("arguments", &IExecutable::arguments)
.def("steamAppID", &IExecutable::steamAppID)
.def("workingDirectory", &IExecutable::workingDirectory)
.def("isShownOnToolbar", &IExecutable::isShownOnToolbar)
.def("usesOwnIcon", &IExecutable::usesOwnIcon)
.def("minimizeToSystemTray", &IExecutable::minimizeToSystemTray)
.def("hide", &IExecutable::hide);
py::class_<IExecutablesList>(m, "IExecutablesList")
.def("executables",
[](IExecutablesList* executablesList) {
return make_generator(executablesList->executables(),
py::return_value_policy::reference);
})
.def("getByTitle", &IExecutablesList::getByTitle, "title"_a)
.def("getByBinary", &IExecutablesList::getByBinary, "info"_a)
.def("contains", &IExecutablesList::contains, "title"_a);
}
void add_modinterface_classes(py::module_ m)
@@ -148,6 +258,9 @@ namespace mo2::python {
.def("url", &IModInterface::url)
.def("primaryCategory", &IModInterface::primaryCategory)
.def("categories", &IModInterface::categories)
.def("author", &IModInterface::author)
.def("uploader", &IModInterface::uploader)
.def("uploaderUrl", &IModInterface::uploaderUrl)
.def("trackedState", &IModInterface::trackedState)
.def("endorsedState", &IModInterface::endorsedState)
.def("fileTree", &IModInterface::fileTree)
@@ -215,7 +328,10 @@ namespace mo2::python {
.def_readwrite("fileCategory", &ModRepositoryFileInfo::fileCategory)
.def_readwrite("fileTime", &ModRepositoryFileInfo::fileTime)
.def_readwrite("repository", &ModRepositoryFileInfo::repository)
.def_readwrite("userData", &ModRepositoryFileInfo::userData);
.def_readwrite("userData", &ModRepositoryFileInfo::userData)
.def_readwrite("author", &ModRepositoryFileInfo::author)
.def_readwrite("uploader", &ModRepositoryFileInfo::uploader)
.def_readwrite("uploaderUrl", &ModRepositoryFileInfo::uploaderUrl);
}
void add_guessedstring_classes(py::module_ m)
@@ -320,9 +436,15 @@ namespace mo2::python {
.def("hasMasterExtension", &IPluginList::hasMasterExtension, "name"_a)
.def("isMediumFlagged", &IPluginList::isMediumFlagged, "name"_a)
.def("isLightFlagged", &IPluginList::isLightFlagged, "name"_a)
.def("isBlueprintFlagged", &IPluginList::isBlueprintFlagged, "name"_a)
.def("hasLightExtension", &IPluginList::hasLightExtension, "name"_a)
.def("hasNoRecords", &IPluginList::hasNoRecords, "name"_a)
.def("formVersion", &IPluginList::formVersion, "name"_a)
.def("headerVersion", &IPluginList::headerVersion, "name"_a)
.def("author", &IPluginList::author, "name"_a)
.def("description", &IPluginList::description, "name"_a)
// Kept but deprecated for backward compatibility:
.def(
"onPluginStateChanged",
@@ -441,13 +563,24 @@ namespace mo2::python {
py::class_<IOrganizer>(m, "IOrganizer")
.def("createNexusBridge", &IOrganizer::createNexusBridge,
py::return_value_policy::reference)
.def("instanceName", &IOrganizer::instanceName)
.def("profileName", &IOrganizer::profileName)
.def("profilePath", &IOrganizer::profilePath)
.def("downloadsPath", &IOrganizer::downloadsPath)
.def("overwritePath", &IOrganizer::overwritePath)
.def("basePath", &IOrganizer::basePath)
.def("modsPath", &IOrganizer::modsPath)
.def("appVersion", &IOrganizer::appVersion)
.def("appVersion",
[](IOrganizer& o) {
mo2::python::show_deprecation_warning(
"appVersion", "IOrganizer::appVersion() is deprecated, use "
"IOrganizer::version() instead.");
#pragma warning(push)
#pragma warning(disable : 4996)
return o.appVersion();
#pragma warning(pop)
})
.def("version", &IOrganizer::version)
.def("createMod", &IOrganizer::createMod,
py::return_value_policy::reference, "name"_a)
.def("getGame", &IOrganizer::getGame, py::return_value_policy::reference,
@@ -513,14 +646,20 @@ namespace mo2::python {
.def("virtualFileTree", &IOrganizer::virtualFileTree)
.def("instanceManager", &IOrganizer::instanceManager,
py::return_value_policy::reference)
.def("downloadManager", &IOrganizer::downloadManager,
py::return_value_policy::reference)
.def("pluginList", &IOrganizer::pluginList,
py::return_value_policy::reference)
.def("modList", &IOrganizer::modList, py::return_value_policy::reference)
.def("executablesList", &IOrganizer::executablesList,
py::return_value_policy::reference)
.def("gameFeatures", &IOrganizer::gameFeatures,
py::return_value_policy::reference)
.def("profile", &IOrganizer::profile, py::return_value_policy::reference)
.def("profile", &IOrganizer::profile)
.def("profileNames", &IOrganizer::profileNames)
.def("getProfile", &IOrganizer::getProfile, "name"_a)
// custom implementation for startApplication and
// waitForApplication because 1) HANDLE (= void*) is not properly
@@ -664,12 +803,29 @@ namespace mo2::python {
.def_static("getPluginDataPath", &IOrganizer::getPluginDataPath);
}
void add_iinstance_manager_classes(py::module_ m)
{
py::class_<IInstance, std::shared_ptr<IInstance>>(m, "IInstance")
.def("displayName", &IInstance::displayName)
.def("gameName", &IInstance::gameName)
.def("gameDirectory", &IInstance::gameDirectory)
.def("isPortable", &IInstance::isPortable);
py::class_<IInstanceManager>(m, "IInstanceManager")
.def("currentInstance", &IInstanceManager::currentInstance)
.def("globalInstancePaths", &IInstanceManager::globalInstancePaths)
.def("getGlobalInstance", &IInstanceManager::getGlobalInstance);
}
void add_idownload_manager_classes(py::module_ m)
{
py::class_<IDownloadManager>(m, "IDownloadManager")
.def("startDownloadURLs", &IDownloadManager::startDownloadURLs, "urls"_a)
.def("startDownloadNexusFile", &IDownloadManager::startDownloadNexusFile,
"mod_id"_a, "file_id"_a)
.def("startDownloadNexusFileForGame",
&IDownloadManager::startDownloadNexusFileForGame, "game_name"_a,
"mod_id"_a, "file_id"_a)
.def("downloadPath", &IDownloadManager::downloadPath, "id"_a)
.def("onDownloadComplete", &IDownloadManager::onDownloadComplete,
"callback"_a)
@@ -772,7 +928,7 @@ namespace mo2::python {
// must be done BEFORE imodlist because there is a default argument to a
// IProfile* in the modlist class
py::class_<IProfile>(m, "IProfile")
py::class_<IProfile, std::shared_ptr<IProfile>>(m, "IProfile")
.def("name", &IProfile::name)
.def("absolutePath", &IProfile::absolutePath)
.def("localSavesEnabled", &IProfile::localSavesEnabled)
@@ -787,6 +943,7 @@ namespace mo2::python {
add_ipluginlist_classes(m);
add_imodlist_classes(m);
add_iinstance_manager_classes(m);
add_idownload_manager_classes(m);
add_iinstallation_manager_classes(m);
add_iorganizer_classes(m);
+21 -15
View File
@@ -4,19 +4,19 @@
#include "../pybind11_all.h"
#include <ipluginlist.h>
#include <isavegameinfowidget.h>
#include <uibase/ipluginlist.h>
#include <uibase/isavegameinfowidget.h>
#include <bsainvalidation.h>
#include <dataarchives.h>
#include <gameplugins.h>
#include <igamefeatures.h>
#include <localsavegames.h>
#include <moddatachecker.h>
#include <moddatacontent.h>
#include <savegameinfo.h>
#include <scriptextender.h>
#include <unmanagedmods.h>
#include <uibase/game_features/bsainvalidation.h>
#include <uibase/game_features/dataarchives.h>
#include <uibase/game_features/gameplugins.h>
#include <uibase/game_features/igamefeatures.h>
#include <uibase/game_features/localsavegames.h>
#include <uibase/game_features/moddatachecker.h>
#include <uibase/game_features/moddatacontent.h>
#include <uibase/game_features/savegameinfo.h>
#include <uibase/game_features/scriptextender.h>
#include <uibase/game_features/unmanagedmods.h>
#include "pyfiletree.h"
@@ -89,11 +89,15 @@ namespace mo2::python {
}
bool lightPluginsAreSupported() override
{
PYBIND11_OVERRIDE_PURE(bool, GamePlugins, lightPluginsAreSupported, );
PYBIND11_OVERRIDE(bool, GamePlugins, lightPluginsAreSupported, );
}
bool mediumPluginsAreSupported() override
{
PYBIND11_OVERRIDE_PURE(bool, GamePlugins, mediumPluginsAreSupported, );
PYBIND11_OVERRIDE(bool, GamePlugins, mediumPluginsAreSupported, );
}
bool blueprintPluginsAreSupported() override
{
PYBIND11_OVERRIDE(bool, GamePlugins, blueprintPluginsAreSupported, );
}
};
@@ -259,7 +263,9 @@ namespace mo2::python {
.def("readPluginLists", &GamePlugins::readPluginLists, "plugin_list"_a)
.def("getLoadOrder", &GamePlugins::getLoadOrder)
.def("lightPluginsAreSupported", &GamePlugins::lightPluginsAreSupported)
.def("mediumPluginsAreSupported", &GamePlugins::mediumPluginsAreSupported);
.def("mediumPluginsAreSupported", &GamePlugins::mediumPluginsAreSupported)
.def("blueprintPluginsAreSupported",
&GamePlugins::blueprintPluginsAreSupported);
// LocalSavegames
+33 -8
View File
@@ -5,8 +5,8 @@
#include "../pybind11_all.h"
#include <ifiletree.h>
#include <log.h>
#include <uibase/ifiletree.h>
#include <uibase/log.h>
namespace py = pybind11;
using namespace MOBase;
@@ -49,7 +49,7 @@ namespace mo2::detail {
return std::make_shared<PyFileTree>(parent, name, m_Callback);
}
bool doPopulate(std::shared_ptr<const IFileTree> parent,
bool doPopulate([[maybe_unused]] std::shared_ptr<const IFileTree> parent,
std::vector<std::shared_ptr<FileTreeEntry>>&) const override
{
return true;
@@ -83,7 +83,6 @@ namespace mo2::python {
void add_ifiletree_bindings(pybind11::module_& m)
{
// FileTreeEntry class:
auto fileTreeEntryClass =
py::class_<FileTreeEntry, std::shared_ptr<FileTreeEntry>>(m,
@@ -164,6 +163,13 @@ namespace mo2::python {
.value("SKIP", IFileTree::WalkReturn::SKIP)
.export_values();
// in C++ this is not an inner enum due to the conditional feature of glob(),
// but in Python this makes more sense as a inner enum
py::enum_<GlobPatternType>(iFileTreeClass, "GlobPatternType")
.value("GLOB", GlobPatternType::GLOB)
.value("REGEX", GlobPatternType::REGEX)
.export_values();
// Non-mutable operations:
iFileTreeClass.def("exists",
py::overload_cast<QString, IFileTree::FileTypes>(
@@ -175,10 +181,29 @@ namespace mo2::python {
iFileTreeClass.def("pathTo", &IFileTree::pathTo, py::arg("entry"),
py::arg("sep") = "\\");
// Note: walk() would probably be better as a generator in python, but
// it is likely impossible to construct from the C++ walk() method.
iFileTreeClass.def("walk", &IFileTree::walk, py::arg("callback"),
py::arg("sep") = "\\");
iFileTreeClass.def(
"walk",
py::overload_cast<
std::function<IFileTree::WalkReturn(
QString const&, std::shared_ptr<const FileTreeEntry>)>,
QString>(&IFileTree::walk, py::const_),
py::arg("callback"), py::arg("sep") = "\\");
// the walk() and glob() generator version are free functions in C++ due to the
// conditional nature, but in Python, it makes more sense to have them as method
// of IFileTree directly
iFileTreeClass.def("walk", [](std::shared_ptr<const IFileTree> tree) {
return make_generator(walk(tree));
});
iFileTreeClass.def(
"glob",
[](std::shared_ptr<const IFileTree> tree, QString pattern,
GlobPatternType patternType) {
return make_generator(glob(tree, pattern, patternType));
},
py::arg("pattern"), py::arg("type") = GlobPatternType::GLOB);
// Kind-of-static operations:
iFileTreeClass.def("createOrphanTree", &IFileTree::createOrphanTree,
+1 -1
View File
@@ -3,7 +3,7 @@
#include "../pybind11_all.h"
#include <ifiletree.h>
#include <uibase/ifiletree.h>
namespace pybind11 {
template <>
+6 -3
View File
@@ -55,6 +55,7 @@ namespace mo2::python {
.def("gameIcon", &IPluginGame::gameIcon)
.def("gameDirectory", &IPluginGame::gameDirectory)
.def("dataDirectory", &IPluginGame::dataDirectory)
.def("modDataDirectory", &IPluginGame::modDataDirectory)
.def("secondaryDataDirectories", &IPluginGame::secondaryDataDirectories)
.def("setGamePath", &IPluginGame::setGamePath, "path"_a)
.def("documentsDirectory", &IPluginGame::documentsDirectory)
@@ -75,6 +76,7 @@ namespace mo2::python {
.def("iniFiles", &IPluginGame::iniFiles)
.def("DLCPlugins", &IPluginGame::DLCPlugins)
.def("CCPlugins", &IPluginGame::CCPlugins)
.def("blueprintPrefix", &IPluginGame::blueprintPrefix)
.def("loadOrderMechanism", &IPluginGame::loadOrderMechanism)
.def("sortMechanism", &IPluginGame::sortMechanism)
.def("nexusModOrganizerID", &IPluginGame::nexusModOrganizerID)
@@ -82,7 +84,8 @@ namespace mo2::python {
.def("looksValid", &IPluginGame::looksValid, "directory"_a)
.def("gameVersion", &IPluginGame::gameVersion)
.def("getLauncherName", &IPluginGame::getLauncherName)
.def("getSupportURL", &IPluginGame::getSupportURL);
.def("getSupportURL", &IPluginGame::getSupportURL)
.def("getModMappings", &IPluginGame::getModMappings);
}
// multiple installers
@@ -109,7 +112,7 @@ namespace mo2::python {
py::return_value_policy::reference);
py::class_<IPluginInstallerSimple, PyPluginInstallerSimple, IPluginInstaller,
std::unique_ptr<IPluginInstallerSimple, py::nodelete>>(
IPlugin, std::unique_ptr<IPluginInstallerSimple, py::nodelete>>(
m, "IPluginInstallerSimple", py::multiple_inheritance())
.def(py::init<>())
@@ -126,7 +129,7 @@ namespace mo2::python {
"name"_a, "tree"_a, "version"_a, "nexus_id"_a);
py::class_<IPluginInstallerCustom, PyPluginInstallerCustom, IPluginInstaller,
std::unique_ptr<IPluginInstallerCustom, py::nodelete>>(
IPlugin, std::unique_ptr<IPluginInstallerCustom, py::nodelete>>(
m, "IPluginInstallerCustom", py::multiple_inheritance())
.def(py::init<>())
.def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported,
+24 -11
View File
@@ -3,18 +3,18 @@
#include "../pybind11_all.h"
#include <iinstallationmanager.h>
#include <uibase/iinstallationmanager.h>
#include <iplugin.h>
#include <iplugindiagnose.h>
#include <ipluginfilemapper.h>
#include <iplugingame.h>
#include <iplugininstaller.h>
#include <iplugininstallercustom.h>
#include <iplugininstallersimple.h>
#include <ipluginmodpage.h>
#include <ipluginpreview.h>
#include <iplugintool.h>
#include <uibase/iplugin.h>
#include <uibase/iplugindiagnose.h>
#include <uibase/ipluginfilemapper.h>
#include <uibase/iplugingame.h>
#include <uibase/iplugininstaller.h>
#include <uibase/iplugininstallercustom.h>
#include <uibase/iplugininstallersimple.h>
#include <uibase/ipluginmodpage.h>
#include <uibase/ipluginpreview.h>
#include <uibase/iplugintool.h>
// these needs to be defined in a header file for automoc - this file is included only
// in pyplugins.cpp
@@ -397,6 +397,10 @@ namespace mo2::python {
{
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, dataDirectory, );
}
QString modDataDirectory() const override
{
PYBIND11_OVERRIDE(QString, IPluginGame, modDataDirectory, );
}
QMap<QString, QDir> secondaryDataDirectories() const override
{
using string_dir_map = QMap<QString, QDir>;
@@ -479,6 +483,10 @@ namespace mo2::python {
{
PYBIND11_OVERRIDE(QStringList, IPluginGame, CCPlugins, );
}
QString blueprintPrefix() const override
{
PYBIND11_OVERRIDE(QString, IPluginGame, blueprintPrefix, );
}
LoadOrderMechanism loadOrderMechanism() const override
{
PYBIND11_OVERRIDE(LoadOrderMechanism, IPluginGame, loadOrderMechanism, );
@@ -511,6 +519,11 @@ namespace mo2::python {
{
PYBIND11_OVERRIDE(QString, IPluginGame, getSupportURL, );
}
QMap<QString, QStringList> getModMappings() const override
{
using vfs_map = QMap<QString, QStringList>;
PYBIND11_OVERRIDE(vfs_map, IPluginGame, getModMappings, );
}
};
} // namespace mo2::python
+2 -2
View File
@@ -2,8 +2,8 @@
#include "../pybind11_all.h"
#include <report.h>
#include <utility.h>
#include <uibase/report.h>
#include <uibase/utility.h>
#include "known_folders.h"
+1 -1
View File
@@ -2,7 +2,7 @@
#include "../pybind11_all.h"
#include <report.h>
#include <uibase/report.h>
namespace py = pybind11;
using namespace MOBase;
+4 -4
View File
@@ -10,10 +10,10 @@
// IOrganizer must be bring here to properly compile the Python bindings of
// plugin requirements
#include <imoinfo.h>
#include <isavegame.h>
#include <isavegameinfowidget.h>
#include <pluginrequirements.h>
#include <uibase/imoinfo.h>
#include <uibase/isavegame.h>
#include <uibase/isavegameinfowidget.h>
#include <uibase/pluginrequirements.h>
using namespace pybind11::literals;
namespace py = pybind11;
+1 -1
View File
@@ -10,7 +10,7 @@
#include <QList>
#include <QObject>
#include <iplugingame.h>
#include <uibase/iplugingame.h>
namespace mo2::python {
+37 -16
View File
@@ -1,9 +1,12 @@
cmake_minimum_required(VERSION 3.16)
find_package(mo2-uibase CONFIG REQUIRED)
set(PLUGIN_NAME "plugin_python")
add_library(proxy SHARED)
add_library(proxy SHARED proxypython.cpp proxypython.h)
mo2_configure_plugin(proxy
NO_SOURCES
WARNINGS 4
EXTERNAL_WARNINGS 4
TRANSLATIONS OFF
@@ -11,15 +14,16 @@ mo2_configure_plugin(proxy
${CMAKE_CURRENT_SOURCE_DIR}/../runner
${CMAKE_CURRENT_SOURCE_DIR}/../mobase
${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
target_link_libraries(proxy PRIVATE runner)
mo2_default_source_group()
target_link_libraries(proxy PRIVATE runner mo2::uibase)
set_target_properties(proxy PROPERTIES OUTPUT_NAME ${PLUGIN_NAME})
mo2_install_target(proxy FOLDER)
mo2_install_plugin(proxy FOLDER)
set(PLUGIN_PYTHON_DIR ${MO2_INSTALL_PATH}/bin/plugins/${PLUGIN_NAME})
set(PLUGIN_PYTHON_DIR bin/plugins/${PLUGIN_NAME})
# install runner
target_link_options(proxy PRIVATE "/DELAYLOAD:runner.dll")
mo2_install_target(runner INSTALLDIR ${PLUGIN_PYTHON_DIR}/dlls)
install(FILES $<TARGET_FILE:runner> DESTINATION ${PLUGIN_PYTHON_DIR}/dlls)
# translations (custom location)
mo2_add_translations(proxy
@@ -33,23 +37,40 @@ mo2_add_translations(proxy
# install DLLs files needed
set(DLL_DIRS ${PLUGIN_PYTHON_DIR}/dlls)
file(GLOB dlls_to_install
${PYTHON_BUILD_PATH}/libffi*.dll
${PYTHON_BUILD_PATH}/python${Python_VERSION_SHORT}*.dll)
${Python_HOME}/dlls/libffi*.dll
${Python_HOME}/dlls/sqlite*.dll
${Python_HOME}/dlls/libssl*.dll
${Python_HOME}/dlls/libcrypto*.dll
${Python_HOME}/python${Python_VERSION_MAJOR}*.dll)
install(FILES ${dlls_to_install} DESTINATION ${DLL_DIRS})
# install Python files
# install Python .pyd files
set(PYLIB_DIR ${PLUGIN_PYTHON_DIR}/libs)
file(GLOB libs_to_install ${PYTHON_BUILD_PATH}/pythoncore/*.pyd)
file(GLOB libs_to_install ${Python_DLL_DIR}/*.pyd)
install(FILES ${libs_to_install} DESTINATION ${PYLIB_DIR})
install(FILES ${PYTHON_BUILD_PATH}/pythoncore/python${Python_VERSION_SHORT}.zip
DESTINATION ${PYLIB_DIR} RENAME pythoncore.zip)
# generate + install standard library
set(pythoncore_zip "${CMAKE_CURRENT_BINARY_DIR}/pythoncore.zip")
add_custom_command(
TARGET proxy POST_BUILD
COMMAND ${Python_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}\\build_pythoncore.py"
${pythoncore_zip}
)
install(FILES ${pythoncore_zip} DESTINATION ${PYLIB_DIR})
# install mobase
install(TARGETS mobase DESTINATION ${PYLIB_DIR})
# install PyQt6
#file(GLOB PYQT_DIR ${MO2_BUILD_PATH}/PyQt${QT_MAJOR_VERSION}*)
set(PYQT_LIB_DIR ${PYTHON_ROOT}/Lib/site-packages/PyQt${QT_MAJOR_VERSION})
set(PYQT_TARGET_DIR ${PYLIB_DIR}/PyQt${QT_MAJOR_VERSION})
file(GLOB pyqt_files ${PYQT_LIB_DIR}/*.py ${PYQT_LIB_DIR}/*.pyd ${PYQT_LIB_DIR}/*.pyi)
install(FILES ${pyqt_files} DESTINATION ${PYQT_TARGET_DIR})
install(
DIRECTORY ${CMAKE_BINARY_DIR}/pylibs/PyQt${MO2_QT_VERSION_MAJOR}
DESTINATION ${PYLIB_DIR}
PATTERN "*.pyd"
PATTERN "*.pyi"
PATTERN "__pycache__" EXCLUDE
PATTERN "bindings" EXCLUDE
PATTERN "lupdate" EXCLUDE
PATTERN "Qt6" EXCLUDE
PATTERN "uic" EXCLUDE
)
+14
View File
@@ -0,0 +1,14 @@
import sys
import zipfile
from pathlib import Path
_EXCLUDE_MODULES = ["ensurepip", "idlelib", "test", "tkinter", "turtle_demo", "venv"]
libdir = Path(sys.executable).parent.joinpath("Lib")
assert libdir.exists()
with zipfile.PyZipFile(sys.argv[1], optimize=2, mode="w") as fp:
fp.writepy(libdir) # pyright: ignore[reportArgumentType]
for path in libdir.iterdir():
if path.is_dir() and path.name not in _EXCLUDE_MODULES:
fp.writepy(path) # pyright: ignore[reportArgumentType]

Some files were not shown because too many files have changed in this diff Show More