Contributing
Contributions are welcome. Please read the guidelines below before opening a pull request.
Before you start
If you are unsure whether a feature fits the project, or whether an existing tool could already be combined with mpqcli to achieve the same result, open an issue first. This avoids wasted effort and keeps the project focused.
mpqcli follows the Unix philosophy. The tool is designed to do one thing well and to compose with other tools via pipes and redirection. If you find yourself wanting to add functionality that could be handled by a separate tool - for example, sorting the output of list - the right answer is usually to pipe the output to that tool rather than adding it here.
Prerequisites and setup
Clone the repository and initialise submodules:
git clone https://github.com/thegraydot/mpqcli.git
cd mpqcli
git submodule update --init --recursive
Install the clang lint tools, pinned to the major version the checks are formatted against:
sudo apt-get install -y clang-18 clang-format-18 clang-tidy-18
Makefile reference
Run make help to list all available targets. Common ones:
| Target | Description |
|---|---|
make configure | Configure the cmake build (uses the default compiler) |
make build | Build the project into build/dev |
make test | Run the unit tests |
make test_functional | Run the functional tests against the built binary |
make test_all | Run every test layer in build/dev |
make fetch_integration_data | Download the real archives a few functional tests read (needs filenget) |
make check_all | Run every static check (clang-format, clang-tidy, completion scripts) |
make check_format | Check formatting only (dry run) |
make format | Auto-fix formatting in-place |
make configure_lint | Configure build/lint with clang++ for clang-tidy |
make check_lint | Run clang-tidy static analysis |
make check_embed | Syntax-check the embedded completion scripts |
make clean | Remove all build and docs artefacts |
Requirements for a pull request
1. Builds on your platform
Make sure the project builds cleanly on your development machine before opening a PR:
make build
On Windows without a POSIX shell, use the cmake commands from the building page instead.
A PR automatically triggers the CI build workflow, which compiles and tests across all supported Linux targets (AMD64 and ARM64). You are not expected to reproduce all of those locally.
2. Tests pass
Run the test suite before submitting:
make test
make test_functional
All tests must pass without errors. A few functional tests need two real Blizzard patch archives and are skipped until you fetch them with make fetch_integration_data, which needs filenget. The building page explains more.
3. New features should include tests
If your change adds or modifies user-facing functionality - such as a new subcommand flag or a change in output format - please include a corresponding test under test/. The tests use Catch2 and come in two layers:
test/unit/mirrorssrc/, with onetest_<source>.cppper source file, and calls the core library directly. Prefer it for logic: it is faster, and a failure points at the code.test/functional/has one file per subcommand and runs the builtmpqclibinary, asserting on its exit code, stdout and stderr. Use it for what only the command line shows: argument parsing, exit codes and output.
Shared fixtures live in test/fixtures/, one header each. The existing files, such as test/functional/test_list.cpp and test/unit/util/test_path.cpp, are good references for the style.
4. Linting must pass
All C++ code is formatted with clang-format and analysed with clang-tidy, and the completion scripts are parsed by their shells. make check_all runs all three checks. It configures a clang build tree of its own for clang-tidy, so it needs no prior make configure:
make check_all
If there are formatting violations, auto-fix them with:
make format
Then re-run make check_all to confirm everything passes.
5. Match the existing code style
C++ formatting is enforced by .clang-format (LLVM style base). Static analysis is enforced by .clang-tidy. Both configs live in the repo root. The tests are held to the same formatting, but clang-tidy does not analyse them.
Suppression policy
Suppressions are occasionally necessary for third-party code or intentional patterns. When suppressing a clang-tidy warning:
- Use
// NOLINT(check-name)with the specific check name - bare// NOLINTis not acceptable - Every suppression must have a comment explaining why it is justified
// NOLINT(bugprone-easily-swappable-parameters): parameters validated by CLI11
Disabling clang-format locally
Use // clang-format off / // clang-format on only when the default formatting genuinely hurts readability (e.g. column-aligned tables). Add a brief comment explaining the intent:
// Preserve column-aligned flag-to-char mappings for readability
// clang-format off
if (flags & MPQ_FILE_IMPLODE) result += 'i';
if (flags & MPQ_FILE_COMPRESS) result += 'c';
// clang-format on
Known design constraints
StormLib locale state is global and not thread-safe
SFileSetLocale sets a process-wide locale variable (g_lcFileLocale) inside StormLib. All locale-sensitive operations in src/mpq/ - file open, add, remove, read, extract, and list - call SFileSetLocale immediately before the relevant StormLib call. There is no locale-explicit alternative in StormLib’s public API (SFileOpenFileEx, SFileAddFileEx, etc. all read g_lcFileLocale internally).
This means:
- The
SFileSetLocale+ StormLib-call sequence is not atomic and would be unsafe under concurrency - mpqcli is intentionally single-threaded; do not introduce threads or async I/O without auditing every locale-sensitive call site in
src/mpq/
If you add a new StormLib call that is locale-sensitive, follow the existing pattern: call SFileSetLocale immediately before it, with no intervening calls between the two.
Workflow summary
- Fork the repository and create a branch for your change
- Run
git submodule update --init --recursiveafter cloning - Install the clang tools as shown above
- Make your changes and verify they build:
make build - Run
make check_all, fixing any issues - Run
make testandmake test_functionaland confirm all tests pass - Open a pull request with a clear description of what was changed and why