Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
BasedOnStyle: Google
PointerAlignment: Right
DerivePointerAlignment: false
ColumnLimit: 100
IndentWidth: 4
AccessModifierOffset: -4
IncludeBlocks: Regroup
IncludeIsMainRegex: '([-_]test)?$'
IncludeCategories:
- Regex: '^<(StormLib\.h|CLI/)'
Priority: 2
- Regex: '^<'
Priority: 1
- Regex: '^"'
Priority: 3
ReflowComments: true
BreakBeforeBraces: Attach
Cpp11BracedListStyle: true
AllowShortFunctionsOnASingleLine: InlineOnly
SortIncludes: true
16 changes: 16 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Checks: >
clang-analyzer-core.*,
clang-analyzer-cplusplus.*,
clang-analyzer-deadcode.*,
modernize-use-nullptr,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
-bugprone-narrowing-conversions,
cppcoreguidelines-no-malloc,
-cppcoreguidelines-owning-memory,
performance-unnecessary-value-param,
readability-inconsistent-declaration-parameter-name,
readability-container-size-empty
WarningsAsErrors: "*"
FormatStyle: file
25 changes: 25 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Lint

on:
workflow_call

jobs:
lint_cpp:
runs-on: ubuntu-24.04
steps:
- name: Check out repository code
uses: actions/checkout@v6
with:
submodules: true

- name: Install clang tools
run: make install_clang_tools

- name: Check formatting
run: make lint

- name: Generate compile_commands.json
run: make build_lint/compile_commands.json

- name: Run clang-tidy
run: make lint_cpp
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ permissions:
jobs:
build:
uses: ./.github/workflows/build.yml
lint:
uses: ./.github/workflows/lint.yml
needs: build
test:
uses: ./.github/workflows/test.yml
needs: build
prerelease:
uses: ./.github/workflows/prerelease.yml
needs:
- build
- lint
- test
secrets: inherit
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ permissions:
jobs:
build:
uses: ./.github/workflows/build.yml
lint:
uses: ./.github/workflows/lint.yml
needs: build
test:
uses: ./.github/workflows/test.yml
needs: build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# CUSTOM
/build_lint
/test/data

# C++
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ if(NOT CMAKE_BUILD_TYPE)
endif()

set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Static linking configuration
if(BUILD_STATIC)
Expand Down
84 changes: 80 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,40 @@ If you are unsure whether a feature fits the project, or whether an existing too

**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:

```
make setup
```

## Makefile Reference

Run `make help` to list all available targets. Common ones:

| Target | Description |
|---|---|
| `make setup` | Install clang-format and clang-tidy via apt |
| `make build_linux` | Build for Linux using cmake |
| `make build_windows` | Build for Windows using cmake |
| `make build_clean` | Remove the cmake build directory |
| `make test_create_venv` | Create Python venv and install test dependencies (first-time only) |
| `make test_mpqcli` | Run the pytest test suite |
| `make lint` | Run all C++ linters (clang-format + clang-tidy) |
| `make lint_format` | Check formatting only (dry run) |
| `make lint_format_fix` | Auto-fix formatting in-place |
| `make lint_cpp` | Run clang-tidy static analysis |
| `make clean` | Remove all build and test artifacts |

## Requirements for a Pull Request

### 1. Builds on your platform
Expand Down Expand Up @@ -36,12 +70,54 @@ All tests must pass without errors.

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 in the `test/` directory. The existing test files (`test_list.py`, `test_add.py`, etc.) are good references for the test style and fixtures used.

### 4. Match the existing code style
### 4. Linting must pass

All C++ code is formatted with clang-format and analysed with clang-tidy. Run the full suite before submitting:

```
make lint
```

There is no enforced formatter. Write C++ that looks consistent with the surrounding code, and Python tests that follow the style of the existing test files.
If there are formatting violations, auto-fix them with:

```
make lint_format_fix
```

Then re-run `make lint` to confirm everything passes.

### 5. Match the existing code style

C++ formatting is enforced by `.clang-format` (Google style base). Static analysis is enforced by `.clang-tidy`. Both configs live in the repo root. Python tests should follow the style of the existing test files.

#### 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 `// NOLINT` is not acceptable
- Every suppression must have a comment explaining why it is justified

```cpp
// 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:

```cpp
// clang-format off: preserve column-aligned flag-to-char mappings for readability
if (flags & MPQ_FILE_IMPLODE) result += 'i';
if (flags & MPQ_FILE_COMPRESS) result += 'c';
// clang-format on
```

## Workflow Summary

1. Fork the repository and create a branch for your change
2. Make your changes and verify they build and all tests pass
3. Open a pull request with a clear description of what was changed and why
2. Run `git submodule update --init --recursive` after cloning
3. Run `make install_clang_tools` to install lint dependencies
4. Make your changes and verify they build: `make build_linux`
5. Run `make lint` and fix any issues
6. Run `make test_mpqcli` and confirm all tests pass
7. Open a pull request with a clear description of what was changed and why
109 changes: 80 additions & 29 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,89 +1,149 @@
CMAKE_BUILD_TYPE := Release
BUILD_MPQCLI := ON
CLANG_VERSION := 18
VERSION := $(shell awk '/project\(MPQCLI VERSION/ {gsub(/\)/, "", $$3); print $$3}' CMakeLists.txt)
README := README.md
PACKAGE_URL := https://github.com/TheGrayDot/mpqcli/pkgs/container/mpqcli

GCC_INSTALL_DIR := $(shell dirname "$(shell gcc -print-libgcc-file-name)")

.PHONY: help \
build_linux build_windows build_clean \
setup \
build_linux build_windows build_clean build_lint_clean \
docker_musl_build docker_musl_run docker_glibc_build docker_glibc_run \
test_create_venv test_mpqcli test_clean test_lint \
lint_format lint_format_fix lint_cpp lint \
clean \
bump_stormlib bump_cli11 bump_submodules \
fetch_downloads tag_release

help: ## Show this help menu
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n\nTargets:\n"} \
/^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
## Show this help menu
help:
@awk 'BEGIN {FS = ":"; printf "\nUsage:\n make \033[36m<target>\033[0m\n\nTargets:\n"} \
/^## / {desc = substr($$0, 4); next} \
/^[a-zA-Z0-9_-]+:/ {if (desc) printf " \033[36m%-22s\033[0m %s\n", $$1, desc; desc = ""; next} \
{desc = ""}' $(MAKEFILE_LIST)

## Install clang lint dependencies
install_clang_tools:
sudo apt-get install -y clang-format-$(CLANG_VERSION) clang-tidy-$(CLANG_VERSION)

# BUILD
build_linux: ## Build for Linux using cmake
## Build for Linux using cmake
build_linux:
cmake -B build \
-DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \
-DBUILD_MPQCLI=$(BUILD_MPQCLI)
cmake --build build

build_windows: ## Build for Windows using cmake
## Build for Windows using cmake
build_windows:
cmake -B build \
-DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \
-DBUILD_MPQCLI=$(BUILD_MPQCLI)
cmake --build build --config $(CMAKE_BUILD_TYPE)

build_clean: ## Remove cmake build directory
## Remove cmake build directory
build_clean:
rm -rf build

## Generate compile_commands.json for clang-tidy
build_lint/compile_commands.json: CMakeLists.txt src/CMakeLists.txt
cmake -B build_lint \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBUILD_MPQCLI=ON \
-DCMAKE_CXX_COMPILER=clang++-$(CLANG_VERSION) \
-DCMAKE_CXX_FLAGS="--gcc-install-dir=$(GCC_INSTALL_DIR)"

## Remove cmake lint build directory
build_lint_clean:
rm -rf build_lint

# DOCKER
docker_musl_build: ## Build Docker image using musl
## Build Docker image using musl
docker_musl_build:
docker build -t mpqcli:$(VERSION) -f Dockerfile.musl .

docker_musl_run: ## Run the musl Docker image
## Run the musl Docker image
docker_musl_run:
@docker run -it mpqcli:$(VERSION) version

docker_glibc_build: ## Build Docker image using glibc
## Build Docker image using glibc
docker_glibc_build:
docker build -t mpqcli:$(VERSION) -f Dockerfile.glibc .

docker_glibc_run: ## Run the glibc Docker image
## Run the glibc Docker image
docker_glibc_run:
@docker run -it mpqcli:$(VERSION) version

# TEST
test_create_venv: ## Create Python venv and install test dependencies
## Create Python venv and install test dependencies
test_create_venv:
python3 -m venv ./.venv
. ./.venv/bin/activate && \
pip3 install -r test/requirements.txt

test_mpqcli: ## Run pytest test suite
## Run pytest test suite
test_mpqcli:
. ./.venv/bin/activate && \
python3 -m pytest test -s

test_clean: ## Remove test data directory
## Remove test data directory
test_clean:
rm -rf test/data

test_lint: ## Run ruff linter on test directory
## Run ruff linter on test directory
test_lint:
. ./.venv/bin/activate && \
ruff check ./test

# LINT
## Check C++ formatting with clang-format
lint_format:
find src \( -name "*.cpp" -o -name "*.h" \) \
| xargs clang-format-$(CLANG_VERSION) --dry-run --Werror

## Auto-fix C++ formatting with clang-format
lint_format_fix:
find src \( -name "*.cpp" -o -name "*.h" \) \
| xargs clang-format-$(CLANG_VERSION) -i

## Run clang-tidy static analysis
lint_cpp: build_lint/compile_commands.json
clang-tidy-$(CLANG_VERSION) \
--quiet -p build_lint --header-filter="$(CURDIR)/src/.*" src/*.cpp

## Run all C++ linters
lint: lint_format lint_cpp

# CLEAN
clean: build_clean test_clean ## Remove all build and test artifacts
## Remove all build and test artifacts
clean: build_clean build_lint_clean test_clean

# SUBMODULES
bump_stormlib: ## Bump StormLib submodule to latest remote
## Bump StormLib submodule to latest remote
bump_stormlib:
@read -rp "[*] Bump StormLib? (y/N) " yn; \
case $$yn in \
[yY] ) git submodule update --init --remote extern/StormLib;; \
* ) echo "[*] Skipping...";; \
esac

bump_cli11: ## Bump CLI11 submodule to latest remote
## Bump CLI11 submodule to latest remote
bump_cli11:
@read -rp "[*] Bump CLI11? (y/N) " yn; \
case $$yn in \
[yY] ) git submodule update --init --remote extern/CLI11;; \
* ) echo "[*] Skipping...";; \
esac

bump_submodules: bump_stormlib bump_cli11 ## Bump all submodules to latest remote
## Bump all submodules to latest remote
bump_submodules: bump_stormlib bump_cli11

# RELEASE
fetch_downloads: ## Fetch package downloads and update README.md badge
## Fetch package downloads and update README.md badge
fetch_downloads:
@DOWNLOADS=$$(curl -s "$(PACKAGE_URL)" \
| grep -A2 "Total downloads" \
| grep -o '<h3 title="[0-9]*">[0-9]*</h3>' \
Expand All @@ -92,12 +152,3 @@ fetch_downloads: ## Fetch package downloads and update README.md badge
| head -1); \
sed -i "s/package_downloads-[0-9]*-green/package_downloads-$$DOWNLOADS-green/" $(README); \
echo "[*] Updated package downloads badge: $$DOWNLOADS"

tag_release: ## Tag and push the current project version
@echo "[*] Current version: v$(VERSION)"
@read -rp "[*] Tag and Release? (y/N) " yn; \
case $$yn in \
[yY] ) git tag "v$(VERSION)" && git push --tags && echo "[*] Tagged and pushed v$(VERSION)";; \
[nN] ) echo "[*] Exiting...";; \
* ) echo "[*] Invalid response... Exiting"; exit 1;; \
esac
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

![Release Version](https://img.shields.io/github/v/release/TheGrayDot/mpqcli?style=flat)

![Release downloads](https://img.shields.io/github/downloads/thegraydot/mpqcli/total?label=release_downloads) ![Package downloads](https://img.shields.io/badge/package_downloads-353-green)
![Release downloads](https://img.shields.io/github/downloads/thegraydot/mpqcli/total?label=release_downloads) ![Package downloads](https://img.shields.io/badge/package_downloads-497-green)

A command-line tool to create, add, remove, list, extract, read, and verify MPQ archives using the [StormLib library](https://github.com/ladislav-zezula/StormLib).

Expand Down
Loading