|
| 1 | +.PHONY: clean deepclean install dev constraints black isort mypy ruff toml-sort lint pre-commit \ |
| 2 | + auto-black auto-isort auto-toml-sort auto-lint \ |
| 3 | + test-run test-run-offline test test-offline \ |
| 4 | + build upload docs-autobuild changelog docs-gen docs-mypy docs-coverage docs |
| 5 | + |
| 6 | +SHELL := /bin/bash |
| 7 | + |
| 8 | +######################################################################################## |
| 9 | +# Variables |
| 10 | +######################################################################################## |
| 11 | + |
| 12 | +# Use pipenv when not in CI and available |
| 13 | +PIPRUN := $(shell [ "$$CI" != "true" ] && command -v pipenv > /dev/null 2>&1 && echo "pipenv run") |
| 14 | + |
| 15 | +# Python version (major.minor) |
| 16 | +PYTHON_VERSION := $(shell echo $${PYTHON_VERSION:-$$(python -V 2>&1 | cut -d ' ' -f 2)} | cut -d '.' -f 1,2) |
| 17 | + |
| 18 | +# Constraints file by Python version |
| 19 | +CONSTRAINTS_DIR := constraints |
| 20 | +CONSTRAINTS_FILE := $(CONSTRAINTS_DIR)/$(PYTHON_VERSION).txt |
| 21 | + |
| 22 | +# Public docs dir (compatible with ReadTheDocs) |
| 23 | +PUBLIC_DIR := $(shell [ "$$READTHEDOCS" = "True" ] && echo "$$READTHEDOCS_OUTPUT/html" || echo "public") |
| 24 | + |
| 25 | +# Changelog (optional) |
| 26 | +CHANGELOG_URL := $(shell echo $${CI_PAGES_URL:-https://example.com/yourproject}/_sources/changelog.md.txt) |
| 27 | +CHANGELOG_PATH := docs/changelog.md |
| 28 | + |
| 29 | +######################################################################################## |
| 30 | +# Development Environment Management |
| 31 | +######################################################################################## |
| 32 | + |
| 33 | +clean: |
| 34 | + -rm -rf \ |
| 35 | + $(PUBLIC_DIR) \ |
| 36 | + .coverage \ |
| 37 | + .mypy_cache \ |
| 38 | + .pytest_cache \ |
| 39 | + .ruff_cache \ |
| 40 | + Pipfile* \ |
| 41 | + coverage.xml \ |
| 42 | + dist \ |
| 43 | + release-notes.md |
| 44 | + find . -name '*.egg-info' -print0 | xargs -0 rm -rf |
| 45 | + find . -name '*.pyc' -print0 | xargs -0 rm -f |
| 46 | + find . -name '*.swp' -print0 | xargs -0 rm -f |
| 47 | + find . -name '.DS_Store' -print0 | xargs -0 rm -f |
| 48 | + find . -name '__pycache__' -print0 | xargs -0 rm -rf |
| 49 | + |
| 50 | +deepclean: clean |
| 51 | + if command -v pre-commit > /dev/null 2>&1; then pre-commit uninstall --hook-type pre-push; fi |
| 52 | + if command -v pipenv >/dev/null 2>&1 && pipenv --venv >/dev/null 2>&1; then pipenv --rm; fi |
| 53 | + |
| 54 | +# Editable install (if this is a package). For this repo we default to requirements.txt |
| 55 | +install: |
| 56 | + @if [ -f setup.py ] || [ -f pyproject.toml ]; then \ |
| 57 | + $(PIPRUN) pip install -e . -c $(CONSTRAINTS_FILE) || $(PIPRUN) pip install -e . ; \ |
| 58 | + else \ |
| 59 | + $(PIPRUN) pip install -r requirements.txt ; \ |
| 60 | + fi |
| 61 | + |
| 62 | +# Developer setup: project deps + common dev tooling |
| 63 | +dev: |
| 64 | + @if [ -f requirements.txt ]; then $(PIPRUN) pip install -r requirements.txt ; fi |
| 65 | + # Common dev tools (safe if already installed) |
| 66 | + $(PIPRUN) pip install -U \ |
| 67 | + black isort ruff mypy \ |
| 68 | + pytest coverage build twine \ |
| 69 | + sphinx sphinx-autobuild git-changelog toml-sort |
| 70 | + @if [ "$(CI)" != "true" ] && command -v pre-commit > /dev/null 2>&1; then pre-commit install --hook-type pre-push; fi |
| 71 | + |
| 72 | +# Generate constraints for current Python version |
| 73 | +constraints: deepclean |
| 74 | + @mkdir -p $(CONSTRAINTS_DIR) |
| 75 | + @if [ -f setup.py ] || [ -f pyproject.toml ]; then \ |
| 76 | + $(PIPRUN) --python $(PYTHON_VERSION) pip install --upgrade -e . ; \ |
| 77 | + fi |
| 78 | + @if [ -f requirements.txt ]; then $(PIPRUN) pip install -r requirements.txt ; fi |
| 79 | + $(PIPRUN) pip freeze --exclude-editable > $(CONSTRAINTS_FILE) |
| 80 | + |
| 81 | +######################################################################################## |
| 82 | +# Lint and pre-commit |
| 83 | +######################################################################################## |
| 84 | + |
| 85 | +black: |
| 86 | + @command -v black >/dev/null 2>&1 || { echo "black not installed. Run 'make dev' first."; exit 1; } |
| 87 | + $(PIPRUN) python -m black --check --diff . -l 120 |
| 88 | + |
| 89 | +isort: |
| 90 | + @command -v isort >/dev/null 2>&1 || { echo "isort not installed. Run 'make dev' first."; exit 1; } |
| 91 | + $(PIPRUN) python -m isort --check . |
| 92 | + |
| 93 | +mypy: |
| 94 | + @command -v mypy >/dev/null 2>&1 || { echo "mypy not installed. Run 'make dev' first."; exit 1; } |
| 95 | + # Narrow the scope if needed |
| 96 | + $(PIPRUN) python -m mypy src || true |
| 97 | + |
| 98 | +ruff: |
| 99 | + @command -v ruff >/dev/null 2>&1 || { echo "ruff not installed. Run 'make dev' first."; exit 1; } |
| 100 | + $(PIPRUN) ruff check src || true |
| 101 | + |
| 102 | +toml-sort: |
| 103 | + @command -v toml-sort >/dev/null 2>&1 || { echo "toml-sort not installed. Run 'make dev' first."; exit 1; } |
| 104 | + $(PIPRUN) toml-sort --check pyproject.toml || true |
| 105 | + |
| 106 | +# Prioritize isort before black to avoid style conflicts |
| 107 | +lint: mypy ruff isort black toml-sort |
| 108 | + |
| 109 | +pre-commit: |
| 110 | + pre-commit run --all-files |
| 111 | + |
| 112 | +######################################################################################## |
| 113 | +# Auto Lint |
| 114 | +######################################################################################## |
| 115 | + |
| 116 | +auto-black: |
| 117 | + @command -v black >/dev/null 2>&1 || { echo "black not installed. Run 'make dev' first."; exit 1; } |
| 118 | + $(PIPRUN) python -m black . -l 120 |
| 119 | + |
| 120 | +auto-isort: |
| 121 | + @command -v isort >/dev/null 2>&1 || { echo "isort not installed. Run 'make dev' first."; exit 1; } |
| 122 | + $(PIPRUN) python -m isort . |
| 123 | + |
| 124 | +auto-toml-sort: |
| 125 | + @command -v toml-sort >/dev/null 2>&1 || { echo "toml-sort not installed. Run 'make dev' first."; exit 1; } |
| 126 | + $(PIPRUN) toml-sort --in-place pyproject.toml >/dev/null 2>&1 || true |
| 127 | + |
| 128 | +auto-lint: auto-isort auto-black auto-toml-sort |
| 129 | + |
| 130 | +######################################################################################## |
| 131 | +# Test |
| 132 | +######################################################################################## |
| 133 | + |
| 134 | +test-run: |
| 135 | + @command -v coverage >/dev/null 2>&1 || { echo "coverage not installed. Run 'make dev' first."; exit 1; } |
| 136 | + @if command -v pytest >/dev/null 2>&1; then \ |
| 137 | + $(PIPRUN) python -m coverage erase; \ |
| 138 | + $(PIPRUN) python -m coverage run --concurrency=multiprocessing -m pytest || true; \ |
| 139 | + $(PIPRUN) python -m coverage combine; \ |
| 140 | + else \ |
| 141 | + echo "pytest not installed or no tests; skipping test-run."; \ |
| 142 | + fi |
| 143 | + |
| 144 | +test-run-offline: |
| 145 | + @command -v coverage >/dev/null 2>&1 || { echo "coverage not installed. Run 'make dev' first."; exit 1; } |
| 146 | + @if command -v pytest >/dev/null 2>&1; then \ |
| 147 | + $(PIPRUN) python -m coverage erase; \ |
| 148 | + $(PIPRUN) python -m coverage run --concurrency=multiprocessing -m pytest -m "offline" || true; \ |
| 149 | + $(PIPRUN) python -m coverage combine; \ |
| 150 | + else \ |
| 151 | + echo "pytest not installed or no tests; skipping test-run-offline."; \ |
| 152 | + fi |
| 153 | + |
| 154 | +test: test-run |
| 155 | + $(PIPRUN) python -m coverage report --fail-under 20 || true |
| 156 | + $(PIPRUN) python -m coverage xml --fail-under 20 || true |
| 157 | + |
| 158 | +test-offline: test-run-offline |
| 159 | + $(PIPRUN) python -m coverage report --fail-under 20 || true |
| 160 | + $(PIPRUN) python -m coverage xml --fail-under 20 || true |
| 161 | + |
| 162 | +######################################################################################## |
| 163 | +# Package |
| 164 | +######################################################################################## |
| 165 | + |
| 166 | +build: |
| 167 | + @command -v python >/dev/null 2>&1 || { echo "python not found"; exit 1; } |
| 168 | + $(PIPRUN) python -m build |
| 169 | + |
| 170 | +upload: |
| 171 | + $(PIPRUN) python -m twine upload dist/* |
| 172 | + |
| 173 | +######################################################################################## |
| 174 | +# Documentation (optional, only if docs/ exists) |
| 175 | +######################################################################################## |
| 176 | + |
| 177 | +docs-autobuild: |
| 178 | + @if [ -d docs ]; then \ |
| 179 | + $(PIPRUN) python -m sphinx_autobuild docs $(PUBLIC_DIR); \ |
| 180 | + else \ |
| 181 | + echo "No docs directory; skipping docs-autobuild."; \ |
| 182 | + fi |
| 183 | + |
| 184 | +changelog: |
| 185 | + @if wget -q --spider $(CHANGELOG_URL); then \ |
| 186 | + echo "Existing Changelog found at '$(CHANGELOG_URL)', download for incremental generation."; \ |
| 187 | + wget -q -O $(CHANGELOG_PATH) $(CHANGELOG_URL); \ |
| 188 | + fi |
| 189 | + @command -v git-changelog >/dev/null 2>&1 || { echo "git-changelog not installed. Run 'make dev' first."; exit 1; } |
| 190 | + $(PIPRUN) LATEST_TAG=$$(git tag --sort=-creatordate | head -n 1); \ |
| 191 | + git-changelog --bump $$LATEST_TAG -Tio docs/changelog.md -c conventional -s build,chore,ci,deps,doc,docs,feat,fix,perf,ref,refactor,revert,style,test,tests || true |
| 192 | + |
| 193 | +release-notes: |
| 194 | + @command -v git-changelog >/dev/null 2>&1 || { echo "git-changelog not installed. Run 'make dev' first."; exit 1; } |
| 195 | + @$(PIPRUN) git-changelog --input $(CHANGELOG_PATH) --release-notes || true |
| 196 | + |
| 197 | +docs-gen: |
| 198 | + @if [ -d docs ]; then \ |
| 199 | + $(PIPRUN) python -m sphinx.cmd.build -W docs $(PUBLIC_DIR); \ |
| 200 | + else \ |
| 201 | + echo "No docs directory; skipping docs-gen."; \ |
| 202 | + fi |
| 203 | + |
| 204 | +docs-mypy: docs-gen |
| 205 | + @if [ -d docs ]; then \ |
| 206 | + $(PIPRUN) python -m mypy src --html-report $(PUBLIC_DIR)/reports/mypy || true; \ |
| 207 | + else \ |
| 208 | + echo "No docs directory; skipping docs-mypy."; \ |
| 209 | + fi |
| 210 | + |
| 211 | +docs-coverage: test-run docs-gen |
| 212 | + @if [ -d docs ]; then \ |
| 213 | + $(PIPRUN) python -m coverage html -d $(PUBLIC_DIR)/reports/coverage --fail-under 20 || true; \ |
| 214 | + else \ |
| 215 | + echo "No docs directory; skipping docs-coverage."; \ |
| 216 | + fi |
| 217 | + |
| 218 | +docs: changelog docs-gen docs-mypy docs-coverage |
| 219 | + |
| 220 | +######################################################################################## |
| 221 | +# End |
| 222 | +######################################################################################## |
| 223 | + |
| 224 | + |
0 commit comments