From 35475b292f2f696e32f454de0593d85e835309c6 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Wed, 12 Dec 2018 19:33:21 +0000 Subject: [PATCH] Update to libloot v0.14.0 libloot is the renamed LOOT API. This adds a requirement on C++17, and so MSVC 2017 on Windows. pybind11 doesn't provide handling for std::filesystem::path, so wrapper functions are used to perform the necessary one-way conversion without having to expose the complexity on the Python side. --- CMakeLists.txt | 39 ++++++++++++++++++++--------------- appveyor.yml | 6 +++--- src/convenience.cpp | 50 ++++++++++++++++++++++++++------------------- src/main.cpp | 36 ++++++++++++++++++++++++++------ src/test.py | 8 ++++---- 5 files changed, 89 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cec1eb..5408a2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,10 @@ include(ExternalProject) set(EXTERNAL_PROJECTS_PATH "${CMAKE_BINARY_DIR}/external/src") make_directory(${EXTERNAL_PROJECTS_PATH}) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + ############################## # Get Build Revision ############################## @@ -50,25 +54,25 @@ add_subdirectory(${PYBIND11_EXTRACTED_PATH}) ####################################### if (CMAKE_SYSTEM_NAME MATCHES "Windows") - set(LOOT_API_URL "https://github.com/loot/loot-api/releases/download/0.13.2/loot_api-0.13.2-0-g5956e60_dev-win32.7z") + set(LIBLOOT_URL "https://github.com/loot/libloot/releases/download/0.14.0/libloot-0.14.0-0-g2e074a4_dev-win32.7z") else() - set(LOOT_API_URL "https://github.com/loot/loot-api/releases/download/0.13.2/loot-api.tar.xz") + set(LIBLOOT_URL "https://github.com/loot/libloot/releases/download/0.14.0/libloot.tar.xz") endif() -ExternalProject_Add(loot-api-c++ +ExternalProject_Add(libloot PREFIX "external" - URL ${LOOT_API_URL} + URL ${LIBLOOT_URL} CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "") -ExternalProject_Get_Property(loot-api-c++ SOURCE_DIR) -set(LOOT_API_EXTRACTED_PATH ${SOURCE_DIR}) +ExternalProject_Get_Property(libloot SOURCE_DIR) +set(LIBLOOT_EXTRACTED_PATH ${SOURCE_DIR}) include_directories("${CMAKE_SOURCE_DIR}/src" - "${LOOT_API_EXTRACTED_PATH}/include") -link_directories(${LOOT_API_EXTRACTED_PATH}) -set(LOOT_API_STATIC_LIBRARY "${CMAKE_STATIC_LIBRARY_PREFIX}loot_api${CMAKE_STATIC_LIBRARY_SUFFIX}") -set(LOOT_API_SHARED_LIBRARY "${CMAKE_SHARED_LIBRARY_PREFIX}loot_api${CMAKE_SHARED_LIBRARY_SUFFIX}") + "${LIBLOOT_EXTRACTED_PATH}/include") +link_directories(${LIBLOOT_EXTRACTED_PATH}) +set(LIBLOOT_STATIC_LIBRARY "${CMAKE_STATIC_LIBRARY_PREFIX}loot${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(LIBLOOT_SHARED_LIBRARY "${CMAKE_SHARED_LIBRARY_PREFIX}loot${CMAKE_SHARED_LIBRARY_SUFFIX}") ####################################### # Test Masterlist @@ -85,16 +89,19 @@ ExternalProject_Add(test-masterlist # Python Module ####################################### +# pybind11 v2.1.1 doesn't recognise MSVC as supporting C++17, so force it. +add_definitions(-DPYBIND11_CPP17) + pybind11_add_module(loot_api "${CMAKE_SOURCE_DIR}/src/main.cpp" "${CMAKE_SOURCE_DIR}/src/convenience.cpp" "${CMAKE_BINARY_DIR}/generated/wrapper_version.cpp") -add_dependencies(loot_api loot-api-c++ test-masterlist) +add_dependencies(loot_api libloot test-masterlist) if (CMAKE_SYSTEM_NAME MATCHES "Windows") - target_link_libraries(loot_api PRIVATE ${LOOT_API_STATIC_LIBRARY}) + target_link_libraries(loot_api PRIVATE ${LIBLOOT_STATIC_LIBRARY}) else() - target_link_libraries(loot_api PRIVATE ${LOOT_API_SHARED_LIBRARY}) + target_link_libraries(loot_api PRIVATE ${LIBLOOT_SHARED_LIBRARY}) endif() ######################################## @@ -106,8 +113,8 @@ find_package(PythonInterp REQUIRED) # Copy the API binary to the build directory. add_custom_command(TARGET loot_api POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${LOOT_API_EXTRACTED_PATH}/${LOOT_API_SHARED_LIBRARY}" - "$/${LOOT_API_SHARED_LIBRARY}") + "${LIBLOOT_EXTRACTED_PATH}/${LIBLOOT_SHARED_LIBRARY}" + "$/${LIBLOOT_SHARED_LIBRARY}") # Copy the test masterlist to the build directory. ExternalProject_Get_Property(test-masterlist SOURCE_DIR) @@ -130,7 +137,7 @@ install(TARGETS loot_api DESTINATION "." CONFIGURATIONS Release RelWithDebInfo) -install(FILES "${LOOT_API_EXTRACTED_PATH}/${LOOT_API_SHARED_LIBRARY}" +install(FILES "${LIBLOOT_EXTRACTED_PATH}/${LIBLOOT_SHARED_LIBRARY}" "${CMAKE_SOURCE_DIR}/docs/README.md" DESTINATION "." CONFIGURATIONS Release RelWithDebInfo) diff --git a/appveyor.yml b/appveyor.yml index cc15d52..b341b58 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -os: Visual Studio 2015 +os: Visual Studio 2017 version: "{build}-{branch}" @@ -17,7 +17,7 @@ before_build: - cd %APPVEYOR_BUILD_FOLDER% - ps: mkdir build - cd build - - cmake .. -G "Visual Studio 14 2015" + - cmake .. -G "Visual Studio 15 2017" build: verbosity: minimal @@ -54,7 +54,7 @@ deploy: tag: $(APPVEYOR_REPO_TAG_NAME) release: LOOT API Python Module v$(APPVEYOR_REPO_TAG_NAME) description: | - Requires Windows 7 or later and the [MSVC 2015 x86 redistributable](https://download.microsoft.com/download/6/A/A/6AA4EDFF-645B-48C5-81CC-ED5963AEAD48/vc_redist.x86.exe), and [7-Zip](http://www.7-zip.org/) to extract the archive. + Requires Windows 7 or later and the [MSVC 2017 x86 redistributable](https://download.visualstudio.microsoft.com/download/pr/749aa419-f9e4-4578-a417-a43786af205e/d59197078cc425377be301faba7dd87a/vc_redist.x86.exe), and [7-Zip](http://www.7-zip.org/) to extract the archive. auth_token: secure: yDqT5l/e5MntbW99V6+MHlfFgNv+UIogFfeyUVqtFk5lFRB/dAraLLwKCLl6y+DH artifact: loot_api_python diff --git a/src/convenience.cpp b/src/convenience.cpp index 6aa5c4f..68c380c 100644 --- a/src/convenience.cpp +++ b/src/convenience.cpp @@ -31,20 +31,24 @@ PluginTags GetPluginTags(const std::shared_ptr db, const std: PluginTags tags; auto metadata = db->GetPluginMetadata(plugin, false, evaluateConditions); - for (const auto &tag : metadata.GetTags()) { - if (tag.IsAddition()) - tags.added.insert(tag.GetName()); - else - tags.removed.insert(tag.GetName()); + if (metadata.has_value()) { + for (const auto &tag : metadata.value().GetTags()) { + if (tag.IsAddition()) + tags.added.insert(tag.GetName()); + else + tags.removed.insert(tag.GetName()); + } } metadata = db->GetPluginUserMetadata(plugin, evaluateConditions); - tags.userlist_modified = !metadata.GetTags().empty(); - for (const auto &tag : metadata.GetTags()) { - if (tag.IsAddition()) - tags.added.insert(tag.GetName()); - else - tags.removed.insert(tag.GetName()); + if (metadata.has_value()) { + tags.userlist_modified = !metadata.value().GetTags().empty(); + for (const auto &tag : metadata.value().GetTags()) { + if (tag.IsAddition()) + tags.added.insert(tag.GetName()); + else + tags.removed.insert(tag.GetName()); + } } return tags; @@ -53,19 +57,23 @@ PluginTags GetPluginTags(const std::shared_ptr db, const std: PluginCleanliness GetPluginCleanliness(const std::shared_ptr db, const std::string& plugin, bool evaluateConditions) { auto metadata = db->GetPluginMetadata(plugin, true, evaluateConditions); - if (metadata.GetDirtyInfo().empty()) { - if (metadata.GetCleanInfo().empty()) { + if (metadata.has_value()) { + if (metadata.value().GetDirtyInfo().empty()) { + if (metadata.value().GetCleanInfo().empty()) { + return PluginCleanliness::unknown; + } + else { + return PluginCleanliness::clean; + } + } + else if (!metadata.value().GetCleanInfo().empty()) { return PluginCleanliness::unknown; - } else { - return PluginCleanliness::clean; } - } else if (!metadata.GetCleanInfo().empty()) { - return PluginCleanliness::unknown; - } - for (const auto& info : metadata.GetDirtyInfo()) { - if (info.ChooseInfo("en").GetText().find("Do not clean") != std::string::npos) { - return PluginCleanliness::do_not_clean; + for (const auto& info : metadata.value().GetDirtyInfo()) { + if (info.ChooseInfo("en").GetText().find("Do not clean") != std::string::npos) { + return PluginCleanliness::do_not_clean; + } } } diff --git a/src/main.cpp b/src/main.cpp index c1ba794..f1c988d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,8 +34,32 @@ using pybind11::arg; using pybind11::enum_; using pybind11::class_; using pybind11::metaclass; +using std::filesystem::path; +using std::filesystem::u8path; namespace loot { +namespace py { +std::shared_ptr CreateGameHandle(GameType gameType, std::string gamePath, std::string gameLocalPath) { + return CreateGameHandle(gameType, u8path(gamePath), u8path(gameLocalPath)); +} + +void LoadLists(std::shared_ptr db, std::string masterlistPath, std::string userlistPath) { + return db->LoadLists(u8path(masterlistPath), u8path(userlistPath)); +} + +void WriteMinimalList(std::shared_ptr db, std::string outputFile, bool overwrite) { + return db->WriteMinimalList(u8path(outputFile), overwrite); +} + +bool UpdateMasterlist(std::shared_ptr db, std::string masterlistPath, std::string remoteUrl, std::string remoteBranch) { + return db->UpdateMasterlist(u8path(masterlistPath), remoteUrl, remoteBranch); +} + +MasterlistInfo GetMasterlistRevision(std::shared_ptr db, std::string masterlistPath, bool getShortId) { + return db->GetMasterlistRevision(u8path(masterlistPath), getShortId); +} +} + void bindEnums(pybind11::module& module) { enum_(module, "GameType") .value("tes4", GameType::tes4) @@ -94,7 +118,7 @@ void bindVersionClasses(pybind11::module& module) { .def_readonly_static("minor", &LootVersion::minor) .def_readonly_static("patch", &LootVersion::patch) .def_readonly_static("revision", &LootVersion::revision) - .def_static("string", LootVersion::string); + .def_static("string", LootVersion::GetVersionString); class_(module, "WrapperVersion") .def_readonly_static("major", &WrapperVersion::major) @@ -110,9 +134,9 @@ void bindInterfaceClasses(pybind11::module& module) { .def("get_database", &GameInterface::GetDatabase); class_>(module, "DatabaseInterface") - .def("load_lists", &DatabaseInterface::LoadLists, arg("masterlist_path"), arg("userlist_path") = "") - .def("update_masterlist", &DatabaseInterface::UpdateMasterlist) - .def("get_masterlist_revision", &DatabaseInterface::GetMasterlistRevision) + .def("load_lists", &py::LoadLists, arg("masterlist_path"), arg("userlist_path") = "") + .def("update_masterlist", &py::UpdateMasterlist) + .def("get_masterlist_revision", &py::GetMasterlistRevision) .def("get_plugin_metadata", &DatabaseInterface::GetPluginMetadata, arg("plugin"), arg("includeUserMetadata") = true, @@ -123,7 +147,7 @@ void bindInterfaceClasses(pybind11::module& module) { .def("get_plugin_cleanliness", &GetPluginCleanliness, arg("plugin"), arg("evaluateConditions") = false) - .def("write_minimal_list", &DatabaseInterface::WriteMinimalList); + .def("write_minimal_list", &py::WriteMinimalList); } void bindClasses(pybind11::module& module) { @@ -139,7 +163,7 @@ void bindFunctions(pybind11::module& module) { module.def("initialise_locale", &InitialiseLocale, arg("id") = ""); - module.def("create_game_handle", &CreateGameHandle, + module.def("create_game_handle", &py::CreateGameHandle, arg("game"), arg("game_path"), arg("game_local_path") = ""); diff --git a/src/test.py b/src/test.py index 0da1ec1..7f85592 100644 --- a/src/test.py +++ b/src/test.py @@ -40,14 +40,14 @@ class GameFixture(unittest.TestCase): class TestLootApi(GameFixture): def test_is_compatible(self): self.assertFalse(is_compatible(0, 9, 0)) - self.assertTrue(is_compatible(0, 13, 0)) + self.assertTrue(is_compatible(0, 14, 0)) def test_version(self): self.assertEqual(Version.major, 0) - self.assertEqual(Version.minor, 13) - self.assertEqual(Version.patch, 2) + self.assertEqual(Version.minor, 14) + self.assertEqual(Version.patch, 0) self.assertNotEqual(Version.revision, u'') - self.assertEqual(Version.string(), "0.13.2") + self.assertEqual(Version.string(), "0.14.0") def test_wrapper_version(self): self.assertEqual(WrapperVersion.major, 3)