diff --git a/README.md b/README.md index d6d94549..2f298770 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ If this experiment is successful it will probably end up being merged into the l ## Outstanding issues -- I haven't yet attempted to build it on Linux. - There is no CI. - It relies on some unreleased improvements to the Rust APIs of libloadorder and loot-condition-interpreter. - It uses more memory than the C++ implementation, and I haven't finished investigating why or if there's anything I can/should do about that. @@ -25,19 +24,51 @@ If this experiment is successful it will probably end up being merged into the l Make sure you have [Rust](https://www.rust-lang.org/) installed. -To build the library, set the `LIBLOOT_REVISION` env var and then run Cargo. Using PowerShell: +To build the library, set the `LIBLOOT_REVISION` env var and then run Cargo. + +Using PowerShell: ```powershell $env:LIBLOOT_REVISION = git rev-parse --short HEAD cargo build --release ``` -The tests include a complete port of libloot's tests, and can be run by first extracting the [testing-plugins](https://github.com/Ortham/testing-plugins) archive to this readme's directory (so that there's a `testing-plugins` directory there), then running: +Using a POSIX shell: + +```sh +export LIBLOOT_REVISION=$(git rev-parse --short HEAD) +cargo build --release +``` + +`LIBLOOT_REVISION` is used to embed the commit hash into the build, if it's not defined then `unknown` will be used instead. + +### Tests + +The tests include a complete port of libloot's tests, and can be run by first extracting the [testing-plugins](https://github.com/Ortham/testing-plugins) archive to this readme's directory (so that there's a `testing-plugins` directory there). + +To do that using `curl` and `tar` in a POSIX shell: + +```sh +curl -sSfL https://github.com/Ortham/testing-plugins/archive/refs/tags/1.6.2.tar.gz | tar -xz --strip=1 --one-top-level=testing-plugins +``` + +To do that in PowerShell: + +```powershell +Invoke-WebRequest https://github.com/Ortham/testing-plugins/archive/refs/tags/1.6.2.zip -OutFile testing-plugins-1.6.2.zip +Expand-Archive testing-plugins-1.6.2.zip . +Move-Item testing-plugins-1.6.2 testing-plugins +Remove-Item testing-plugins-1.6.2.zip +``` + +The tests can then be run using: ``` cargo test ``` +### API documentation + The public API has doc comments copied from libloot, and the API documentation can be built and viewed using: ``` diff --git a/cxx/CMakeLists.txt b/cxx/CMakeLists.txt index 68e71ae3..119255a7 100644 --- a/cxx/CMakeLists.txt +++ b/cxx/CMakeLists.txt @@ -25,9 +25,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) ############################## add_library(libloot-cxx STATIC IMPORTED) -# TODO: Define the Rust libloot library path here for Linux builds. if(MSVC) set(LIBLOOT_CXX_FILENAME "libloot_cxx.lib") +else() + set(LIBLOOT_CXX_FILENAME "liblibloot_cxx.a") endif() set_target_properties(libloot-cxx PROPERTIES @@ -145,20 +146,11 @@ target_include_directories(loot SYSTEM PRIVATE ${LIBLOOT_COMMON_SYSTEM_INCLUDE_DIRS}) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - target_compile_definitions(loot PRIVATE - UNICODE _UNICODE LOOT_EXPORT YAML_CPP_STATIC_DEFINE) + target_compile_definitions(loot PRIVATE UNICODE _UNICODE LOOT_EXPORT) set(LOOT_LIBS ntdll ws2_32 bcrypt) - if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") - set(LOOT_LIBS ${LOOT_LIBS} tbb_static) - endif() - target_link_libraries(loot PRIVATE ${LOOT_LIBS}) -else() - set(LOOT_LIBS ICU::data ICU::uc pthread TBB::tbb) - - target_link_libraries(loot PUBLIC ${LOOT_LIBS}) endif() if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -282,6 +274,8 @@ install(FILES ######################################## if(NOT DEFINED CPACK_PACKAGE_VERSION) + find_package(Git) + if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long --always --abbrev=7 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} diff --git a/cxx/README.md b/cxx/README.md index ff19250a..1c929778 100644 --- a/cxx/README.md +++ b/cxx/README.md @@ -2,39 +2,63 @@ This is an **experimental** wrapper around the Rust reimplementation of libloot that provides a C++ interface that's ABI-compatible with libloot v0.25.5. -## Building - The wrapper has two layers: - a static library built using Cargo, which provides a C++ interface - a shared library built using CMake, which wraps that C++ interface to provide another that is ABI-compatible with C++ libloot. -To build the wrapper: +## Building + +### Windows + +To build a release build with debug info: ``` cargo build --release cmake -B build . -cmake --build build --config RelWithDebInfo +cmake --build build --parallel --config RelWithDebInfo ``` -Debug builds need a little extra config to get the static and shared libraries to use the same C runtime library on Windows. +Debug builds need a little extra config to get the static and shared libraries to use the same C runtime library on Windows: ``` cargo build --config ../.cargo/msvcd-config.toml cmake -B build . -cmake --build build --config Debug +cmake --build build --parallel --config Debug ``` -This also builds a copy of the public API tests from C++ libloot v0.25.5, which can be run using: +### Linux + +To build a release build with debug info: ``` -ctest --test-dir build --output-on-failure -V +cargo build --release +cmake -B build . -DCMAKE_BUILD_TYPE=RelWithDebInfo +cmake --build build --parallel +``` + +to build a debug build: + +``` +cargo build +cmake -B build . -DCMAKE_BUILD_TYPE=Debug +cmake --build build --parallel +``` + +### Tests & Packaging + +The build process also builds a copy of the public API tests from C++ libloot v0.25.5 by default. To skip building the tests, pass `-DLIBLOOT_BUILD_TESTS=OFF` when first running CMake. + +If built, the tests can be run using: + +``` +ctest --test-dir build --output-on-failure --parallel -V ``` To package the build: ``` -cpack --config build/CPackConfig.cmake +cpack --config build/CPackConfig.cmake -C RelWithDebInfo ``` This repository (and so the created package) doesn't currently include any of libloot's documentation, besides the API documentation included in the Rust source code. diff --git a/cxx/cmake/tests.cmake b/cxx/cmake/tests.cmake index efdc73fd..f415887e 100644 --- a/cxx/cmake/tests.cmake +++ b/cxx/cmake/tests.cmake @@ -121,10 +121,10 @@ target_compile_definitions(libloot_internals_tests PRIVATE if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") target_compile_definitions(libloot_tests PRIVATE LOOT_STATIC) endif() -endif() -target_link_libraries(libloot_internals_tests PRIVATE ${LOOT_LIBS}) -target_link_libraries(libloot_tests PRIVATE ${LOOT_LIBS}) + target_link_libraries(libloot_internals_tests PRIVATE ${LOOT_LIBS}) + target_link_libraries(libloot_tests PRIVATE ${LOOT_LIBS}) +endif() if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(libloot_internals_tests PRIVATE "-Wall" "-Wextra") diff --git a/cxx/src/api/game.cpp b/cxx/src/api/game.cpp index 836044b8..324149eb 100644 --- a/cxx/src/api/game.cpp +++ b/cxx/src/api/game.cpp @@ -69,11 +69,10 @@ rust::Box constructGame( const std::filesystem::path& localDataPath) { try { if (localDataPath.empty()) { - return std::move( - loot::rust::new_game(convert(gameType), gamePath.u8string())); + return loot::rust::new_game(convert(gameType), gamePath.u8string()); } else { - return std::move(loot::rust::new_game_with_local_path( - convert(gameType), gamePath.u8string(), localDataPath.u8string())); + return loot::rust::new_game_with_local_path( + convert(gameType), gamePath.u8string(), localDataPath.u8string()); } } catch (const ::rust::Error& e) { std::rethrow_exception(loot::mapError(e)); diff --git a/cxx/src/api/metadata/plugin_metadata.cpp b/cxx/src/api/metadata/plugin_metadata.cpp index c3c9b3f5..2297c2a2 100644 --- a/cxx/src/api/metadata/plugin_metadata.cpp +++ b/cxx/src/api/metadata/plugin_metadata.cpp @@ -24,6 +24,7 @@ #include "loot/metadata/plugin_metadata.h" +#include #include #include diff --git a/pyo3/README.md b/pyo3/README.md index 087e5267..588f7161 100644 --- a/pyo3/README.md +++ b/pyo3/README.md @@ -4,26 +4,32 @@ An **experimental** Python wrapper around the libloot Rust implementation, built ## Build -To build, first set up a Python venv and install [maturin](https://github.com/PyO3/maturin): +To build, first set up a Python virtual environment and install [maturin](https://github.com/PyO3/maturin): -``` +```powershell python -m venv .venv .\.venv\Scripts\activate pip install maturin +``` + +or in a POSIX shell: + +```sh +python -m venv .venv +. .venv/bin/activate +pip install maturin +``` + +Then build the library in the virtual environment: + +``` maturin develop ``` -Then build the library: +The library can then be imported in Python: ``` -.\.venv\Scripts\activate -maturin develop -``` - -Then import it in Python: - -``` -py +python > import loot ``` diff --git a/src/archive/find.rs b/src/archive/find.rs index aa8aea3e..a6e87061 100644 --- a/src/archive/find.rs +++ b/src/archive/find.rs @@ -174,7 +174,7 @@ fn are_file_paths_equivalent(lhs: &Path, rhs: &Path) -> bool { return true; } - use std::fs::unix::fs::MetadataExt; + use std::os::unix::fs::MetadataExt; let lhs_metadata = match lhs.metadata() { Ok(m) => m, @@ -474,7 +474,7 @@ mod tests { #[test] #[cfg(not(windows))] fn should_be_false_if_given_case_insensitively_equal_paths_that_exist() { - let tmp_dir = tempdir(); + let tmp_dir = tempdir().unwrap(); let file_path1 = tmp_dir.path().join("test"); let file_path2 = tmp_dir.path().join("TEST"); @@ -483,7 +483,7 @@ mod tests { assert!(file_path1.exists()); assert!(file_path2.exists()); - assert!(!are_file_paths_equivalent(file_path1, file_path2)); + assert!(!are_file_paths_equivalent(&file_path1, &file_path2)); } } } diff --git a/src/game.rs b/src/game.rs index 1b6e218c..39dc6c0d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -893,6 +893,8 @@ mod tests { #[cfg(not(windows))] #[apply(all_game_types)] fn should_succeed_for_morrowind_if_given_valid_game_path(game_type: GameType) { + let fixture = Fixture::new(game_type); + if matches!(game_type, GameType::TES3 | GameType::OpenMW) { assert!(Game::new(fixture.game_type, &fixture.game_path).is_ok()); } else { @@ -902,7 +904,7 @@ mod tests { #[test] fn should_succeed_if_given_a_relative_game_path() { - let fixture = Fixture::new(GameType::TES4); + let fixture = Fixture::new(GameType::TES3); let game_path = make_relative(&fixture.game_path); assert!(game_path.is_relative()); @@ -912,7 +914,7 @@ mod tests { #[test] fn should_succeed_if_given_an_absolute_game_path() { - let fixture = Fixture::new(GameType::TES4); + let fixture = Fixture::new(GameType::TES3); assert!(fixture.game_path.is_absolute()); assert!(Game::new(fixture.game_type, &fixture.game_path).is_ok()); @@ -920,7 +922,7 @@ mod tests { #[test] fn should_succeed_if_given_a_symlink_path() { - let fixture = Fixture::new(GameType::TES4); + let fixture = Fixture::new(GameType::TES3); let game_path = fixture.game_path.with_extension("symlink"); symlink_dir(&fixture.game_path, &game_path); @@ -932,7 +934,7 @@ mod tests { #[cfg(windows)] #[test] fn should_succeed_if_given_a_junction_link_path() { - let fixture = Fixture::new(GameType::TES4); + let fixture = Fixture::new(GameType::TES3); let game_path = fixture.game_path.with_extension("junction"); junction_link(&fixture.game_path, &game_path); @@ -943,7 +945,7 @@ mod tests { #[test] fn should_error_if_given_a_game_path_that_does_not_exist() { let game_path = Path::new("missing"); - match Game::new(GameType::TES4, game_path) { + match Game::new(GameType::TES3, game_path) { Err(GameHandleCreationError::NotADirectory(p)) => { assert_eq!(game_path, p) }