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
68 changes: 46 additions & 22 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
VENV_DIR := .venv
PYTHON := $(VENV_DIR)/bin/python
# The interpreter that creates $(VENV_DIR), which only hosts Hatch, and that `make install-ci`
# installs into: `make install PYTHON=python3.12`. Hatch still provisions its own default
# environment (Python 3.11, see [tool.hatch.envs.default] in pyproject.toml).
PYTHON ?= python3
PIP := $(VENV_DIR)/bin/pip
HATCH := $(VENV_DIR)/bin/hatch
SPEC_PREVIEW_PNG := docs/assets/webhook_receiver_openapi_spec_preview.png
Expand All @@ -11,16 +14,13 @@ SWAGGER_PREVIEW_PORT := 8123
SPEC_PREVIEW_PORT := 8124
EXAMPLES_PREVIEW_PORT := 8130

# Idempotent: the virtual environment is created only while it is missing, Hatch is installed only
# while it is not an executable regular file (`-x` alone also accepts a directory), and pip is
# never upgraded.
.PHONY: bootstrap
bootstrap:
@if [ ! -d "$(VENV_DIR)" ]; then \
echo "Creating virtual environment..."; \
python3 -m venv $(VENV_DIR); \
fi
@echo "Ensuring Hatch is installed in virtual environment..."
@$(PIP) install --upgrade pip > /dev/null
@$(PIP) install hatch > /dev/null
@echo "Hatch installed at $(HATCH)"
@test -d "$(VENV_DIR)" || { echo "Creating virtual environment..."; $(PYTHON) -m venv "$(VENV_DIR)"; }
@{ [ -f "$(HATCH)" ] && [ -x "$(HATCH)" ]; } || { echo "Installing Hatch..."; "$(PIP)" install --disable-pip-version-check --quiet hatch; }

.PHONY: ensure-hatch
ensure-hatch: bootstrap
Expand All @@ -34,6 +34,13 @@ install: ensure-hatch
$(MAKE) precommit-install; \
fi

# Installs into the interpreter you select and never through Hatch, whose default environment is
# pinned to Python 3.11, so a CI matrix cell keeps its own Python: `make install-ci PYTHON=python3.12`.
.PHONY: install-ci
install-ci:
@$(PYTHON) -c 'import sys; sys.exit(sys.version_info < (3, 11) and "install-ci needs Python 3.11 or newer, got " + sys.version.split()[0])'
@$(PYTHON) -m pip install -e ".[dev]"

.PHONY: shell
shell: ensure-hatch
@$(HATCH) shell
Expand Down Expand Up @@ -96,10 +103,10 @@ security-development:
security-browser:
@$(SECURITY_PYTHON) -m tools.browser_integrity --output "$(SECURITY_OUTPUT)"

# `hatch run lint` is `style` followed by `typecheck`, so `check` is `lint`: a further
# `make typecheck` would run the type check a second time.
.PHONY: check
check: ensure-hatch
@$(MAKE) lint
@$(MAKE) typecheck
check: lint
@echo "Lint and type check passed."

.PHONY: lint-workflows
Expand Down Expand Up @@ -171,16 +178,18 @@ version: ensure-hatch
@echo "Current version:"
@$(HATCH) version

# The CI docs gate (`mkdocs build --strict` after `pip install -e ".[docs]"`): a broken link or nav
# entry fails the build, the dependencies come from pyproject.toml, and a failed install stops the
# build with pip's own error instead of building with whatever was already installed.
.PHONY: docs
docs:
@if [ -n "$$CI" ]; then \
echo "CI detected: running MkDocs directly"; \
python -m pip install --upgrade pip > /dev/null 2>&1 || true; \
pip install "mkdocs<2.0" "mkdocs-material<10.0" "mkdocstrings[python]<1.0" > /dev/null 2>&1; \
mkdocs build; \
pip install --disable-pip-version-check --quiet -e ".[docs]" && \
mkdocs build --strict; \
else \
$(MAKE) ensure-hatch > /dev/null; \
$(HATCH) run mkdocs build; \
$(MAKE) ensure-hatch > /dev/null && \
$(HATCH) run mkdocs build --strict; \
fi

.PHONY: docs-serve
Expand Down Expand Up @@ -280,13 +289,28 @@ demo-examples: ensure-hatch
kill $$SERVER_PID 2>/dev/null || true; \
wait $$SERVER_PID 2>/dev/null || true

# Read-only prerequisite check: it installs and writes nothing, and fails when a required tool is
# missing. A tool counts only if `command -v` resolves it to an executable regular file, because
# `command -v` also reports a non-executable file and `-x` alone also accepts a directory. Python
# 3.11 or newer is looked up on PATH, $(HATCH) is the Hatch this Makefile runs (`make install`
# creates it), and func and azurite are needed only to run a local Functions host.
.PHONY: doctor
doctor:
@echo "Python version:" && $(PYTHON) --version
@echo "Installed packages:" && $(HATCH) env run pip list || echo "No hatch environment found"
@echo "Azure Function Core Tools version:" && func --version || echo "func not found"
@echo "Pre-commit hook installed:"
@if [ -f .git/hooks/pre-commit ]; then echo yes; else echo no; fi
@missing=0; \
have() { p="$$(command -v "$$1")"; [ -f "$$p" ] && [ -x "$$p" ]; }; \
py=; \
for tool in python3.14 python3.13 python3.12 python3.11 python3; do \
if have "$$tool" && "$$tool" -c 'import sys; sys.exit(sys.version_info < (3, 11))'; then py=$$tool; break; fi; \
done; \
if [ -n "$$py" ]; then echo "ok $$py ($$("$$py" --version))"; \
else echo "MISSING python >= 3.11 (required)"; missing=1; fi; \
if have "$(HATCH)"; then echo "ok $(HATCH)"; \
else echo "MISSING $(HATCH) (required; make install creates it)"; missing=1; fi; \
for tool in func azurite; do \
if have "$$tool"; then echo "ok $$tool"; \
else echo "missing $$tool (optional: needed only to run a local Functions host)"; fi; \
done; \
exit $$missing

.PHONY: clean
clean:
Expand Down
4 changes: 2 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ Describe what changed and why. Reference related issues with `Fixes #N` or `Clos
| `make test` | Run pytest |
| `make cov` | Run tests with coverage (gated at 95%) |
| `make lint-workflows` | Lint release workflows, action pins, Hatch matrix |
| `make check` | Run lint + typecheck |
| `make check` | Run `make lint`, which includes the type check, once |
| `make check-all` | Run lint-workflows + check + test + security (full gate) |
| `make docs` | Build the MkDocs site |
| `make docs` | Build the MkDocs site with `mkdocs build --strict`, like the CI docs gate |

See [Development Guide](development.md#makefile-targets) for the complete list.

Expand Down
20 changes: 17 additions & 3 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ Use these as the **golden commands** for local validation and CI parity. Prefer
| Target | Description |
|--------|-------------|
| `make install` | Create Hatch env and install pre-commit hooks |
| `make install-ci` | Install `.[dev]` into the interpreter you select (`PYTHON=python3.12`), never through Hatch |
| `make shell` | Open a shell inside the Hatch env |
| `make format` | Format code (ruff format) |
| `make format-check` | Check formatting without writing (ruff format --check) |
Expand All @@ -141,17 +142,17 @@ Use these as the **golden commands** for local validation and CI parity. Prefer
| `make e2e-local` | Run e2e tests against local Azurite on `:7071` |
| `make e2e-azure` | Run e2e tests against Azure (`E2E_BASE_URL` required) |
| `make lint-workflows` | Lint release workflows, action pins, and the Hatch matrix |
| `make check` | Run lint + typecheck |
| `make check` | Run `make lint`, which includes the type check, once |
| `make check-all` | Run lint-workflows + check + test + security |
| `make docs` | Build the MkDocs site |
| `make docs` | Build the MkDocs site with `mkdocs build --strict`, like the CI docs gate |
| `make docs-serve` | Serve the docs locally with live reload |
| `make demo` | Run the Swagger UI and example demos |
| `make build` | Build package |
| `make version` | Show the current version |
| `make publish-test` | Publish to TestPyPI |
| `make precommit` | Run all pre-commit hooks |
| `make precommit-install` | Install pre-commit hooks |
| `make doctor` | Show environment diagnostic info |
| `make doctor` | Check the prerequisites (read-only); fails when a required tool is missing |
| `make reset` | Deep clean, then reinstall the env |
| `make hatch-clean` | Remove the Hatch environment |
| `make clean` | Remove build artifacts |
Expand All @@ -161,6 +162,19 @@ Use these as the **golden commands** for local validation and CI parity. Prefer
Coverage is enforced at **95%** (`fail_under = 95` in `pyproject.toml`); a drop
below that fails the build.

`make install` creates `.venv` only while it is missing and installs Hatch only while `.venv/bin/hatch`
is missing or not an executable file; no target upgrades pip. `PYTHON` selects the interpreter that
creates `.venv` (default `python3`, for example `make install PYTHON=python3.12`); Hatch still
provisions its own default environment on Python 3.11. `make install-ci PYTHON=<interpreter>` installs
`.[dev]` into exactly that interpreter, which must be Python 3.11 or newer, and never goes through
Hatch, so a CI matrix cell keeps its own Python.

`make doctor` checks this repository's prerequisites and installs or writes nothing. A Python 3.11 or
newer on `PATH` and `.venv/bin/hatch` (created by `make install`) are required: each prints `ok` or
`MISSING ...`, and a missing one makes the target fail. `func` and `azurite` are optional, because
only a local Functions host needs them. A Python 3.11 that only Hatch manages is not on `PATH`, so
`doctor` reports it as missing.

> For the full release workflow, see [Release Process](release_process.md).

---
Expand Down
Loading
Loading