-
Notifications
You must be signed in to change notification settings - Fork 1
/
Makefile
294 lines (243 loc) · 12 KB
/
Makefile
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
PYTHON_BINS ?= ./.venv/bin
PYTHON ?= ${PYTHON_BINS}/python
DJANGO_SETTINGS_MODULE ?= project.settings.development
SUB_MAKE = ${MAKE} --no-print-directory
.DEFAULT_GOAL := help
help:
# @link https://github.com/marmelab/javascript-boilerplate/blob/master/makefile
@grep -P '^[.a-zA-Z/_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: install
install: bin/uv .venv ./node_modules ## Install the Python and frontend dependencies
bin/uv sync --all-extras
${PYTHON_BINS}/pre-commit install
${SUB_MAKE} .venv/bin/black
.PHONY: dev
dev: .env.local db.sqlite3
dev: ## Start Django in "development" mode, as well as our frontend assets compilers in "watch" mode
@${SUB_MAKE} frontend/img
@./node_modules/.bin/concurrently --names "django,css,js" --prefix-colors "blue,yellow,green" \
"${SUB_MAKE} backend/watch" \
"${SUB_MAKE} frontend/css/watch" \
"${SUB_MAKE} frontend/js/watch"
# Here starts the backend stuff
.PHONY: download_assets
download_assets: download_assets_opts ?=
download_assets:
${PYTHON_BINS}/python scripts/download_assets.py ${download_assets_opts}
.PHONY: backend/watch
backend/watch: address ?= localhost
backend/watch: port ?= 8000
backend/watch: dotenv_file ?= .env.local
backend/watch: ## Start the Django development server
@${SUB_MAKE} django/manage cmd='runserver ${address}:${port}'
.PHONY: backend/resetdb
backend/resetdb: dotenv_file ?= .env.local
backend/resetdb: # Destroys the SQLite database and recreates it from scratch
rm -f db.sqlite3
@${SUB_MAKE} db.sqlite3
.PHONY: backend/createsuperuser
backend/createsuperuser: dotenv_file ?= .env.local
backend/createsuperuser: email ?= admin@zakuchess.localhost
backend/createsuperuser: password ?= localdev
backend/createsuperuser: ## Creates a Django superuser for the development environment
@${SUB_MAKE} django/manage cmd='createsuperuser --noinput' \
env_vars='DJANGO_SUPERUSER_USERNAME=${email} DJANGO_SUPERUSER_EMAIL=${email} DJANGO_SUPERUSER_PASSWORD=${password}'
@echo 'You can log in to http://localhost:8000/admin/ with the following credentials: ${email} / ${password}'
.PHONY: test
test: dotenv_file ?= .env.local
test: pytest_opts ?=
test: ## Launch the pytest tests suite
@${PYTHON_BINS}/dotenv -f '${dotenv_file}' run -- \
${PYTHON_BINS}/pytest ${pytest_opts}
.PHONY: code-quality/all
code-quality/all: code-quality/ruff_check code-quality/ruff_lint code-quality/mypy ## Run all our code quality tools
.PHONY: code-quality/ruff_check
code-quality/ruff_check: ruff_opts ?=
code-quality/ruff_check: ## Automated 'a la Prettier' code formatting
# @link https://docs.astral.sh/ruff/formatter/
@${PYTHON_BINS}/ruff format ${ruff_opts} src/
.PHONY: code-quality/ruff_lint
code-quality/ruff_lint: ruff_opts ?= --fix
code-quality/ruff_lint: ## Fast linting
# @link https://docs.astral.sh/ruff/linter/
@${PYTHON_BINS}/ruff check src/ ${ruff_opts}
.PHONY: code-quality/mypy
code-quality/mypy: mypy_opts ?=
code-quality/mypy: ## Python's equivalent of TypeScript
# @link https://mypy.readthedocs.io/en/stable/
@${PYTHON_BINS}/mypy src/ ${mypy_opts}
# Here starts the frontend stuff
.PHONY: frontend/watch
frontend/watch: ## Compile the CSS & JS assets of our various Django apps, in 'watch' mode
@./node_modules/.bin/concurrently --names "img,css,js" --prefix-colors "yellow,green" \
"${SUB_MAKE} frontend/img" \
"${SUB_MAKE} frontend/css/watch" \
"${SUB_MAKE} frontend/js/watch"
.PHONY: frontend/img
frontend/img: ## Copy some assets from the apps' "static-src" folders to their "static" ones
## No 'watch' mode on this at the moment...
@${SUB_MAKE} frontend/img/copy_assets
.PHONY: frontend/css/watch
frontend/css/watch: ## Compile the CSS assets of our various Django apps, in 'watch' mode
@${SUB_MAKE} frontend/css/compile \
tailwind_compile_opts='--watch'
.PHONY: frontend/css/compile
frontend/css/compile:
@./node_modules/.bin/concurrently --names "tailwind" \
"${SUB_MAKE} frontend/css/compile/tailwind"
frontend/css/compile/tailwind: tailwind_compile_opts ?=
frontend/css/compile/tailwind: ## Compile the Tailwind-based stylesheet
@./node_modules/.bin/tailwind ${tailwind_compile_opts} \
-i ./src/apps/webui/static-src/webui/css/zakuchess.css \
-o ./src/apps/webui/static/webui/css/zakuchess.css
.PHONY: frontend/js/watch
frontend/js/watch: ## Compile the JS assets of our various Django apps, in 'watch' mode
@${SUB_MAKE} frontend/js/compile esbuild_compile_opts='--watch'
.PHONY: frontend/js/compile
frontend/js/compile: js_webui_src ?= src/apps/webui/static-src/webui/ts
frontend/js/compile: js_webui_dest ?= src/apps/webui/static/webui/js
frontend/js/compile: js_chess_src ?= src/apps/chess/static-src/chess/ts
frontend/js/compile: js_chess_dest ?= src/apps/chess/static/chess/js
frontend/js/compile: ## Compile the JS assets of our various Django apps
@./node_modules/.bin/concurrently --names "webui,chess" \
"${SUB_MAKE} frontend/js/compile_app_files src='${js_webui_src}/main.ts' dest=${js_webui_dest}" \
"${SUB_MAKE} frontend/js/compile_app_files src='${js_chess_src}/chess-main.ts' dest=${js_chess_dest}"
.PHONY: frontend/js/compile_app_files
frontend/js/compile_app_files: esbuild_compile_opts ?=
frontend/js/compile_app_files:
@./node_modules/.bin/esbuild ${esbuild_compile_opts} \
${src} \
--bundle --sourcemap --target=chrome87,firefox78,safari14,edge88 \
--outdir="${dest}"
.PHONY: frontend/img/copy_assets
frontend/img/copy_assets: img_chess_src ?= src/apps/chess/static-src/chess/img
frontend/img/copy_assets: img_chess_dest ?= src/apps/chess/static/chess
frontend/img/copy_assets:
@mkdir -p '${img_chess_dest}'
@cp -r -p '${img_chess_src}' '${img_chess_dest}'
@echo "Copied image assets from Django apps' 'static-src' to 'static' folders."
# Here starts the "misc util targets" stuff
bin/uv: uv_version ?= 0.4.4
bin/uv: # Install `uv` and `uvx` locally in the "bin/" folder
curl -LsSf "https://astral.sh/uv/${uv_version}/install.sh" | \
CARGO_DIST_FORCE_INSTALL_DIR="$$(pwd)" INSTALLER_NO_MODIFY_PATH=1 sh
@echo "We'll use 'bin/uv' to manage Python dependencies."
.venv: ## Initialises the Python virtual environment in a ".venv" folder, via uv
bin/uv venv
.env.local:
cp .env.dist .env.local
.venv/bin/black: .venv ## A simple and stupid shim to use the IDE's Black integration with Ruff
@echo '#!/usr/bin/env sh\n$$(dirname "$$0")/ruff format $$@' > ${PYTHON_BINS}/black
@chmod +x ${PYTHON_BINS}/black
db.sqlite3: dotenv_file ?= .env.local
db.sqlite3: ## Initialises the SQLite database
touch db.sqlite3
@${SUB_MAKE} django/manage cmd='migrate'
@DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} \
${PYTHON_BINS}/dotenv -f '${dotenv_file}' run -- \
${PYTHON_BINS}/python scripts/optimise_db.py
django/manage: env_vars ?=
django/manage: dotenv_file ?= .env.local
django/manage: cmd ?= --help
django/manage: .venv .env.local ## Run a Django management command
@DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} ${env_vars} \
${PYTHON_BINS}/dotenv -f '${dotenv_file}' run -- \
${PYTHON} manage.py ${cmd}
./.venv/bin/django: .venv install
./node_modules: frontend/install
frontend/install:
npm install
# Here starts the "Lichess database" stuff
data/lichess_db_puzzle.csv: ## Download the Lichess puzzles database (CSV format)
@curl 'https://database.lichess.org/lichess_db_puzzle.csv.zst' \
| zstd -d > data/lichess_db_puzzle.csv
data/lichess_db_puzzle.sqlite3: batch_size ?= 1000
data/lichess_db_puzzle.sqlite3: sqlite_utils_ops ?=
data/lichess_db_puzzle.sqlite3: data/lichess_db_puzzle.csv ## Convert the Lichess puzzles database into a SQLite database
# @link https://sqlite-utils.datasette.io/en/stable/cli.html#inserting-csv-or-tsv-data
# N.B. use `make data/lichess_db_puzzle.csv sqlite_utils_ops='--stop-after 50'` for quick tests
@rm -f data/puzzles.sqlite3
@${PYTHON_BINS}/sqlite-utils create-database data/lichess_db_puzzle.sqlite3 --enable-wal
@${PYTHON_BINS}/sqlite-utils insert data/lichess_db_puzzle.sqlite3 puzzles data/lichess_db_puzzle.csv \
--csv --detect-types --pk PuzzleId --batch-size ${batch_size} \
${sqlite_utils_ops}
@${PYTHON_BINS}/sqlite-utils create-index data/lichess_db_puzzle.sqlite3 puzzles \
PuzzleId Rating Popularity NbPlays
@${SUB_MAKE} data/lichess_db_puzzle.sqlite3/stats
.PHONY: data/lichess_db_puzzle.sqlite3/stats
data/lichess_db_puzzle.sqlite3/stats:
@${PYTHON_BINS}/sqlite-utils query data/lichess_db_puzzle.sqlite3 \
'select count(*) as puzzles_count from puzzles' --csv
@${PYTHON_BINS}/sqlite-utils query data/lichess_db_puzzle.sqlite3 \
'select avg(Rating), avg(Popularity) from puzzles' --csv
# Here starts Load tesing-related stuff
load_testing/locust: processes_count ?= 8
load_testing/locust: ## Start a load testing session, powered by Locust
@${PYTHON_BINS}/locust \
--web-host 127.0.0.1 \
--processes ${processes_count}
# Here starts Docker-related stuff
DOCKER_IMG_NAME ?= zakuchess
DOCKER_TAG ?= latest
.PHONY: docker/build
docker/build: use_buildkit ?= 1 # @link https://docs.docker.com/develop/develop-images/build_enhancements/
docker/build: docker_build_args ?=
docker/build: ## Docker: build the image
DOCKER_BUILDKIT=${use_buildkit} docker build -t ${DOCKER_IMG_NAME}:${DOCKER_TAG} ${docker_build_args} .
.PHONY: docker/local/run
docker/local/run: port ?= 8080
docker/local/run: port_exposed ?= 8080
docker/local/run: docker_args ?= --rm -it
docker/local/run: docker_env ?= -e SECRET_KEY=does-not-matter-here -e DATABASE_URL=sqlite:////app/shared_volume/db.sqlite3 -e ALLOWED_HOSTS=* -e SECURE_SSL_REDIRECT=
docker/local/run: cmd ?= scripts/start_server.sh
docker/local/run: GUNICORN_CMD_ARGS ?= --bind :8080 --workers 2 --max-requests 120 --max-requests-jitter 20 --timeout 8
docker/local/run: user_id ?= $$(id -u)
docker/local/run: ## Docker: launch the previously built image, listening on port 8080
docker run -p ${port_exposed}:${port} -v "${PWD}/.docker/:/app/shared_volume/" \
-u ${user_id} \
${docker_env} ${docker_args} \
-e DJANGO_SETTINGS_MODULE=project.settings.production \
-e GUNICORN_CMD_ARGS='${GUNICORN_CMD_ARGS}' \
${DOCKER_IMG_NAME}:${DOCKER_TAG} \
${cmd}
.PHONY: docker/local/shell
docker/local/shell: docker_args ?= --rm -it --user app
docker/local/shell: docker_env ?= -e SECRET_KEY=does-not-matter-here -e DATABASE_URL=sqlite:////app/shared_volume/db.sqlite3 -e ALLOWED_HOSTS=* -e SECURE_SSL_REDIRECT=
docker/local/shell: cmd ?= bash
docker/local/shell: user_id ?= $$(id -u)
docker/local/shell:
docker run -v "${PWD}/.docker/:/app/shared_volume/" \
-u ${user_id} \
${docker_env} ${docker_args} \
-e DJANGO_SETTINGS_MODULE=project.settings.production \
--entrypoint ${cmd} \
${DOCKER_IMG_NAME}:${DOCKER_TAG}
.PHONY: docker/local/migrate
docker/local/migrate:
${SUB_MAKE} docker/local/shell \
cmd='/app/.venv/bin/python manage.py migrate'
# Here starts Fly.io-related stuff
.PHONY: fly.io/deploy
fly.io/deploy: deploy_build_args ?=
fly.io/deploy: version ?= $$(date --iso-8601)::$$(git rev-parse --short HEAD)
fly.io/deploy: ## Fly.io: deploy the previously built Docker image
@echo "Deploying version '${version}'..."
flyctl deploy ${deploy_build_args} \
--env ZAKUCHESS_VERSION="${version}"
.PHONY: fly.io/db/local_backup
fly.io/db/local_backup: backup_name ?= $$(date --iso-8601=seconds | cut -d + -f 1)
fly.io/db/local_backup: ## Fly.io: backup the SQLite database locally
@flyctl ssh sftp get /zakuchess_sqlite_dbs/zakuchess.dev.sqlite3
@mv zakuchess.dev.sqlite3 "zakuchess.prod.backup.${backup_name}.sqlite3"
@echo "Saved to 'zakuchess.prod.backup.${backup_name}.sqlite3'"
.PHONY: fly.io/db/prod_to_local
fly.io/db/prod_to_local: local_db ?= ./db.sqlite3
fly.io/db/prod_to_local: backup_name ?= ./db.local.backup.$$(date --iso-8601=seconds | cut -d + -f 1).sqlite3
fly.io/db/prod_to_local: ## Fly.io: replace our local SQLite database with the one from the prod environment
@mv "${local_db}" "${backup_name}"
@flyctl ssh sftp get /zakuchess_sqlite_dbs/zakuchess.dev.sqlite3
@mv zakuchess.dev.sqlite3 "${local_db}"
@echo "Replaced local DB with a copy from the production DB. The previous local DB has been saved as '${backup_name}'."
.PHONY: fly.io/ssh
fly.io/ssh: ## Fly.io: start a SSH session within our app
flyctl ssh console