-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
265 lines (225 loc) · 9.18 KB
/
Makefile
File metadata and controls
265 lines (225 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# Makefile for Python projects using uv (VS Code–first)
#
# Environment loading:
# - Loads (later overrides earlier):
# .env-build
# .env-build.local
# .env-build.$(ENV)
#
# Help text convention (required):
# - Each target MUST have:
# <target>: <deps><space><space>## <help text>
# - Exactly two spaces before `##`
# - Only matching lines appear in `make help`
SHELL := /bin/bash
.DEFAULT_GOAL := help
# ----------------------------------------------------------------------------
# Environment (.env-build support)
# ----------------------------------------------------------------------------
ENV ?= local
ENV_FILES := \
.env-build \
.env-build.local \
.env-build.$(ENV)
-include $(ENV_FILES)
export $(shell sed -n 's/^\([A-Za-z_][A-Za-z0-9_]*\)=.*/\1/p' $(ENV_FILES) 2>/dev/null)
# ----------------------------------------------------------------------------
# Tooling defaults (override in .env-build)
# ----------------------------------------------------------------------------
UV ?= uv
PYTHON ?= python3
PYTEST ?= pytest
RUFF ?= ruff
PYRIGHT ?= pyright
TWINE ?= twine
PIP_AUDIT ?= pip-audit
BANDIT ?= bandit
BUMP ?= bump
UV_SYNC_ARGS ?= --all-extras --dev --locked
BUILD_ARGS ?=
TEST_ARGS ?=
COV_ARGS ?= --cov --cov-report=term-missing --cov-report=xml --cov-report=html
PUBLISH_REPO ?= pypi
# pip-audit args:
PIP_AUDIT_ARGS ?=
#
# Bandit args:
# --configfile <file> : specify config file (default: pyproject.toml)
# --severity-level <level> : report only issues at or above this severity (low, medium, high)
# --confidence-level <level> : report only issues at or above this confidence level (low, medium, high)
# --recursive <path> : recursively scan directories from the path
#
BANDIT_ARGS ?= --configfile ./pyproject.toml --severity-level medium --confidence-level medium --recursive .
#
# Other
#
PACKAGE ?= $(shell $(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])" 2>/dev/null || echo .)
# Python import module name is typically the distribution name with '-' replaced by '_'
PACKAGE_MODULE ?= $(subst -,_,$(PACKAGE))
# ----------------------------------------------------------------------------
# Colors
# ----------------------------------------------------------------------------
YELLOW := \033[33m
CYAN := \033[36m
GREEN := \033[32m
RED := \033[31m
BLUE := \033[34m
RESET := \033[0m
# ----------------------------------------------------------------------------
# Logging levels
# ----------------------------------------------------------------------------
INFO := [$(GREEN)INFO$(RESET)]
WARN := [$(YELLOW)WARN$(RESET)]
ERROR := [$(RED)ERROR$(RESET)]
# ----------------------------------------------------------------------------
# Guards
# ----------------------------------------------------------------------------
.PHONY: check-uv
check-uv: ## Check uv exists and print version
@command -v $(UV) >/dev/null 2>&1 || { \
printf "$(ERROR) '$(UV)' command not found. Install from: $(BLUE)https://docs.astral.sh/uv/$(RESET)\n"; \
exit 1; \
}
@printf "$(INFO) Using %s\n" "$$($(UV) --version)"
# ----------------------------------------------------------------------------
# Targets
# ----------------------------------------------------------------------------
.PHONY: help
help: ## Show available make targets
@printf "$(CYAN)Usage:$(RESET) make <target>\n\n"
@printf "$(CYAN)Environment:$(RESET) ENV=$(ENV)\n"
@printf "$(CYAN)Env files:$(RESET) $(ENV_FILES)\n\n"
@printf "$(CYAN)Targets:$(RESET)\n"
@awk 'BEGIN {FS=":.* ## "} /^[a-zA-Z0-9_.-]+:.* ## / {printf " $(YELLOW)%-16s$(RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort
.PHONY: all
all: clean setup format lint coverage scan ## Run build & test pipeline
.PHONY: clean
clean: ## Remove build, cache and temp files
@printf "$(INFO) Cleaning project artifacts and caches...\n"
@rm -rf \
.venv/ \
.build/ \
.build_excluded/ \
.coverage \
.dist/ \
.mypy_cache/ \
.pytest_cache/ \
.ruff_cache/ \
.tox/ \
build/ \
dist/ \
htmlcov/ \
site/ \
coverage.xml \
junit.xml \
*.egg-info \
**/*.egg-info
@find . -type d -name "__pycache__" -prune -exec rm -rf {} +
@find . -type f -name "*.pyc" -delete
@find . -type f -name "*.pyo" -delete
@printf "$(INFO) Clean complete.\n\n"
.PHONY: setup
setup: check-uv ## Install dependencies
@printf "$(INFO) Upgrading pip in uv environment...\n"
@$(UV) run pip install --upgrade pip
@printf "$(INFO) Syncing uv environment with dependencies...\n"
@$(UV) sync $(UV_SYNC_ARGS)
.PHONY: build
build: check-uv ## Build sdist and wheel
@printf "$(INFO) Building distribution artifacts...\n"
@printf "$(INFO) Filtering commands for packaging...\n"
@$(PYTHON) scripts/filter_commands.py prepare || { \
printf "$(ERROR) Failed to filter commands\n"; \
exit 1; \
}
@$(UV) build $(BUILD_ARGS) || { \
printf "$(ERROR) Build failed, restoring commands...\n"; \
$(PYTHON) scripts/filter_commands.py restore; \
exit 1; \
}
@printf "$(INFO) Restoring excluded commands...\n"
@$(PYTHON) scripts/filter_commands.py restore
@printf "$(INFO) Build complete. Artifacts in 'dist/' directory.\n\n"
.PHONY: format
format: check-uv ## Format code and apply fixes
@printf "$(INFO) Formatting code...\n"
@$(UV) run $(RUFF) format .
@$(UV) run $(RUFF) check . --fix
@printf "$(INFO) Formatting complete.\n\n"
.PHONY: lint
lint: check-uv ## Lint code and type-check
@printf "$(INFO) Linting code using ruff...\n"
@$(UV) run $(RUFF) check .
@printf "\n"
@printf "$(INFO) Type-checking code using pyright...\n"
@$(UV) run $(PYRIGHT)
@printf "$(INFO) Type-checking complete.\n\n"
.PHONY: test
test: check-uv ## Run test suite
@printf "$(INFO) Running test suite...\n"
@$(UV) run $(PYTEST) $(TEST_ARGS)
@printf "$(INFO) Test suite complete.\n\n"
.PHONY: coverage
coverage: check-uv ## Run tests with coverage reports
@printf "$(INFO) Running tests with coverage reports...\n"
@$(UV) run $(PYTEST) $(TEST_ARGS) $(COV_ARGS)
@printf "$(INFO) Coverage reports complete.\n\n"
.PHONY: scan
scan: check-uv ## Scan for security issues
# @printf "$(INFO) Scanning for security issues...\n"
# @$(UV) run $(PIP_AUDIT) --version >/dev/null 2>&1 || { \
# printf "$(ERROR) '$(PIP_AUDIT)' not installed in uv env.\n"; \
# printf "Fix: make setup (or: $(UV) sync $(UV_SYNC_ARGS))\n"; \
# exit 1; \
# }
# @$(UV) run $(PIP_AUDIT) $(PIP_AUDIT_ARGS)
@$(UV) run $(BANDIT) --version >/dev/null 2>&1 || { \
printf "$(ERROR) '$(BANDIT)' not installed in uv env.\n"; \
printf "Fix: make setup (or: $(UV) sync $(UV_SYNC_ARGS))\n"; \
exit 1; \
}
@$(UV) run $(BANDIT) $(BANDIT_ARGS)
.PHONY: version
version: ## Show current version
@$(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"
.PHONY: bump
bump: check-uv ## Bump patch version (X.Y.Z -> X.Y.Z+1)
@printf "$(INFO) Bumping patch version...\n"
@old_version=$$($(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"); \
$(UV) run $(BUMP) >/dev/null 2>&1; \
new_version=$$($(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"); \
printf "$(INFO) $$old_version$(RESET) --> $(GREEN)$$new_version$(RESET)\n"
.PHONY: bump-minor
bump-minor: check-uv ## Bump minor version (X.Y.Z -> X.Y+1.0)
@printf "$(INFO) Bumping minor version...\n"
@old_version=$$($(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"); \
$(UV) run $(BUMP) --minor --reset >/dev/null 2>&1; \
new_version=$$($(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"); \
printf "$(INFO) $$old_version$(RESET) --> $(GREEN)$$new_version$(RESET)\n"
.PHONY: bump-major
bump-major: check-uv ## Bump major version (X.Y.Z -> X+1.0.0)
@printf "$(INFO) Bumping major version...\n"
@old_version=$$($(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"); \
$(UV) run $(BUMP) --major --reset >/dev/null 2>&1; \
new_version=$$($(PYTHON) -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])"); \
printf "$(INFO) $$old_version$(RESET) --> $(GREEN)$$new_version$(RESET)\n"
.PHONY: package
package: check-uv ## Clean and build artifacts
@printf "$(INFO) Packaging project...\n"
@$(MAKE) clean
@$(MAKE) build
@printf "$(INFO) Packaging complete.\n\n"
.PHONY: publish
publish: check-uv ## Upload artifacts to PyPI
@printf "$(INFO) Publishing package to repository '$(PUBLISH_REPO)'...\n"
@test -d dist || (printf "$(ERROR) dist/ missing; run make build\n" && exit 1)
@$(UV) run $(TWINE) check dist/*
@$(UV) run $(TWINE) upload $(PYPI_BASE_URL)/$(PUBLISH_REPO) dist/*
@printf "$(INFO) Publishing complete.\n\n"
.PHONY: install
install: check-uv ## Install package in editable mode
@printf "$(INFO) Installing package in editable mode...\n"
@$(UV) run pip install -e .
@printf "$(INFO) Installation complete.\n\n"
@printf "$(INFO) You can now run help via Python: $(YELLOW)uv run python -m %s.main --help$(RESET)\n" "$(PACKAGE_MODULE)"
@printf "$(INFO) You can now execute the package in the terminal: $(YELLOW)uv run $(PACKAGE) --help$(RESET)\n"