Fix and document Linux build process

This commit is contained in:
Oliver Hamlet
2025-03-26 17:56:36 +00:00
parent 4e0dd21495
commit 7d2324816a
9 changed files with 106 additions and 49 deletions
+34 -3
View File
@@ -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:
```
+5 -11
View File
@@ -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}
+33 -9
View File
@@ -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.
+3 -3
View File
@@ -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")
+3 -4
View File
@@ -69,11 +69,10 @@ rust::Box<loot::rust::Game> 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));
+1
View File
@@ -24,6 +24,7 @@
#include "loot/metadata/plugin_metadata.h"
#include <cstring>
#include <regex>
#include <stdexcept>
+17 -11
View File
@@ -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
```
+3 -3
View File
@@ -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));
}
}
}
+7 -5
View File
@@ -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)
}