Skip to content

Makefile Convention

The Python repositories of Entirius (libraries, Django modules, the service) run their development tasks through make, with the same target names: make test means the same thing in a module and in the service. The frontends and the documentation repository have no Makefile and use their package manager’s scripts.

  • No installation: make is on every development machine and CI runner; just would be one more binary.
  • Familiar: no onboarding cost.
  • One tool across the platform: the harness entirius-zeno runs on make too, with targets of its own for the stack.

Make syntax (tabs, .PHONY, $$) is clumsier than just. For one-line wrappers around uv run ... that does not matter.

The Makefiles to copy from: entirius-django-pim (module) and entirius-service-volkanos (service).

A library or a module has the first five. The service adds the targets marked with it.

TargetWhereClassDoes
help (default)everywheresafelists the targets with their descriptions
installeverywheresafeuv sync --all-extras in libraries and modules, uv sync in the service
checkeverywheresaferuff check, ruff format --check, and the secret-scanning config guard
fixeverywheredevruff check --fix and ruff format; modifies files
testeverywheredev / CIpytest -x -q; in the service, makemigrations --check --dry-run first
test-legacymodules that kept their own API keysdev / CIthe same suite with the access module switched off
healthserviceprod-safemanage.py check, check --database default, migrate --check
runservicedevdevelopment server (runserver)
migrate / makemigrationsservicedevapply or create migrations
shellservicedevmanage.py shell
schemaservicedevOpenAPI file from drf-spectacular
settings-exampleservicedevregenerates the module list of main/settings_example.py from modules.yaml
first-channelservicedevcreates the first channel from a TOML file (CONFIG=, default scripts/first-channel.example.toml)
settings-localservicedev-onlycopies main/settings_example.py to main/settings_local.py when it is missing; the service does not boot without that file
test-usersservicedev-onlycreates the test users from scripts/test_users.yaml
initservicedev-onlysettings-local, migrate, test-users
reinstallservicedev-onlyrebuilds the virtual environment from the lock
recreate-dbservicedev-only, destructivedeletes the local sqlite database file
rebuildservicedev-onlyreinstall, recreate-db, init, check, test

A new target name for libraries, modules or the service is added to this table first. Behaviour and end-to-end tests do not live in the service or in a module: they are in the test package entirius-test-package-emporium, whose Makefile keeps help, install, check and fix and has test, bdd and e2e targets of its own.

make check test runs the same ruff and pytest commands as the CI lint and test jobs, with two differences: the CI lint job also checks the license headers, and the service’s migration check runs in make test only. check also runs a guard that needs the maintainers’ secret-scanning config and exits 1 without it; what to run instead: Secret Scanning.

Targets are classified by whether they change state:

  • prod-safe (health, check): safe to run in any environment, production included. They write nothing to the database or to files. migrate --check exits non-zero when the database is missing migrations and applies nothing.
  • dev-only (settings-local, test-users, init, reinstall, recreate-db, rebuild): they change the database, the settings file or the virtual environment. Development machines only.

pytest creates a throwaway test database, so test is not prod-safe and never part of health.

Every dev-only target depends on a guard-dev target that refuses to run outside a development environment:

  • Fail-closed: ENVIRONMENT defaults to production; a dev-only target runs only with development or local. Set ENVIRONMENT=development once in .env (gitignored); the Makefile loads that file.
  • The guard is a first line, not a guarantee: anyone with a shell can set the variable. What protects a production database is a database role without DROP and CREATE rights.

Test users are declared in YAML. test-users and init create them through scripts/ensure_test_users.py, which refuses to run unless the Django setting ENVIRONMENT is development or local: a second guard, independent of make.

Real passwords are never committed. The repository carries scripts/test_users.example.yaml with placeholders; scripts/test_users.yaml is gitignored. The script reads the local file and falls back to the example, so make init works on a fresh clone.

make with no arguments runs help (.DEFAULT_GOAL := help). Every public target carries a ## description comment on its line, and help collects them with grep, so the list cannot drift from the Makefile.