-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMakefile
377 lines (305 loc) · 11.5 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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
.PHONY: dummy
.DEFAULT_GOAL := setup
PYTHONPATH := $(CURDIR):test
# The absolute path to the coverage directory.
COVERAGE_DIR := $(CURDIR)/coverage
# A list of app names in the form "app.[name of app]".
APPS := $(wildcard apps/*)
APPS := $(notdir $(APPS))
APPS := $(filter-out __%,$(APPS))
APPS := $(addprefix apps., $(APPS))
# A list of app-specific icons.
APP_ICONS := $(wildcard apps/*/static/app-icon.svg)
APP_ICONS := $(patsubst %.svg,%.png,$(APP_ICONS))
# A list of parsers with test suites in the form "parsers.[name of parser]".
PARSERS := $(wildcard parsers/*_test.py)
PARSERS := $(notdir $(PARSERS))
PARSERS := $(basename $(PARSERS))
PARSERS := $(subst _test,,$(PARSERS))
PARSERS := $(addprefix parsers., $(PARSERS))
# A list of plugins with test suites in the form "plugins.[name of plugin]".
PLUGINS := $(wildcard plugins/*_test.py)
PLUGINS := $(notdir $(PLUGINS))
PLUGINS := $(basename $(PLUGINS))
PLUGINS := $(subst _test,,$(PLUGINS))
PLUGINS := $(addprefix plugins., $(PLUGINS))
# A list of resources with test suites in the form "resources.[name of resource]".
RESOURCES := $(wildcard resources/*_test.py)
RESOURCES := $(notdir $(RESOURCES))
RESOURCES := $(basename $(RESOURCES))
RESOURCES := $(subst _test,,$(RESOURCES))
RESOURCES := $(addprefix resources., $(RESOURCES))
# A list of HTTP scripts in the form of http.[name of script]".
HTTP_SCRIPTS := $(wildcard http/*.py)
HTTP_SCRIPTS := $(notdir $(HTTP_SCRIPTS))
HTTP_SCRIPTS := $(basename $(HTTP_SCRIPTS))
HTTP_SCRIPTS := $(subst _shared,,$(HTTP_SCRIPTS))
HTTP_SCRIPTS := $(addprefix http., $(HTTP_SCRIPTS))
SHARED_JS_DIR := $(CURDIR)/apps/static/js
TMUX_SESSION_NAME := medley
VENV_ACTIVATE := venv/bin/activate$(PYTHON_VENV_ACTIVATE_EXTENSION)
vpath %.cov coverage
export PATH := ./venv/bin:$(PATH)
# Make utility to print the value of a variable for debugging
#
# Example: make print-PLUGIN_DIR
print-%:
@echo $* = $($*)
# Set up a virtualenv
#
# The sed command fixes an error with fish shell during sourcing.
#
# See https://github.com/pypa/virtualenv/pull/1379/commits
venv:
@echo "Creating a new virtual environment..."
@/usr/bin/env python3 -m venv --system-site-packages venv
# Install third-party Python libraries
setup: venv
./venv/bin/python -m pip install --quiet --upgrade pip setuptools
./venv/bin/python -m pip install --quiet --disable-pip-version-check -r requirements.txt
# Install dev-specific third-party Python libraries
#
# This is isolated from the setup target for the benefit of CI, where
# dev packages are unused.
setup-dev: setup
sudo dnf install python3-evdev
./venv/bin/python -m pip install --quiet --disable-pip-version-check -r requirements-dev.txt
# Build the application
medley: setup
rsync -a --filter='merge .rsync-build-filters' --delete --delete-excluded . build/
./venv/bin/python -m compileall -j 0 -q build
./venv/bin/python -m pip install --compile \
--disable-pip-version-check \
--no-color \
--quiet \
-r requirements.txt \
--target build \
--upgrade
find build -depth -type d -name '*.dist-info' -exec rm -rf {} \;
./venv/bin/python -m shiv --site-packages build -p "/usr/bin/env python3" -o medley -e medley.main
# Install the application on the production host
install: medley
ansible-playbook --skip-tags "firstrun" ansible/install.yml
# Run a local development webserver.
serve: export MEDLEY_memorize_hashes=False
serve: export MEDLEY_etags=True
serve: export MEDLEY_tracebacks=True
serve: export MEDLEY_prefetch=False
serve:
python medley.py
# Rename coverage files to comply with coverage utility's
# expectations.
#
# Coverage files start out with a .cov suffix, but the coverage
# reporter needs a different naming convention.
.coverage.%:
@-cp $(COVERAGE_DIR)/$*.cov $(COVERAGE_DIR)/.coverage.$*
# Build a coverage report for all available coverage files.
#
# This rule merges the indiviual files within the coverage directory
# into a master .coverage file at the project root.
#
# Two reports are generated: a plain-text version for the command line
# and an HTML version stored in the static directory of the coverage app.
#
# The merging process will remove the intermediate .coverage files,
# making this rule ineligible for skipping.
coverage: $(addprefix .coverage., $(APPS) $(PLUGINS) $(PARSERS) $(RESOURCES))
coverage combine $(COVERAGE_DIR)
coverage report
coverage html -d apps/static/coverage
curl -X DELETE 'http://localhost:8085/maintenance/memorize'
# Run the tests for everything.
test: $(APPS) $(PARSERS) $(PLUGINS) $(RESOURCES) coverage
# Generate test coverage for a single app.
#
# This is a pattern rule based on the coverage file generated by pytest.
#
# Its target is the file coverage/apps.[name of app]/cov
#
# Its prerequesites are the application's main.py and its test file.
#
# Customizing the name of the coverage file allows Make to skip the
# tests if nothing has changed. This is also why the coverage file is
# deleted if the tests fail.
apps.%.cov: apps/%/main.py apps/%/main_test.py
mkdir -p $(COVERAGE_DIR)
COVERAGE_FILE=coverage/$@ \
python -m pytest --cov-config=.coveragerc --cov=apps.$* apps/$* \
|| (rm $(COVERAGE_DIR)/$@ && exit 1)
# Generate test coverage for a single parser.
#
# This is a pattern rule based on the coverage file generated by pytest.
#
# Its target is a file coverage/parsers.[name of parser].cov
#
# Its prerequisites are the parser class and its test file.
#
# Customizing the name of the coverage file allows Make to skip the
# tests if nothing has changed. This is also why the coverage file is
# deleted if the tests fail.
parsers.%.cov: parsers/%.py parsers/%_test.py
mkdir -p $(COVERAGE_DIR)
COVERAGE_FILE=coverage/$@ \
python -m pytest --cov-config=.coveragerc --cov=parsers.$* parsers/$*_test.py \
|| (rm $(COVERAGE_DIR)/$@ && exit 1)
# Generate test coverage for a single plugin.
#
# This is a pattern rule based on the coverage file generated by pytest.
#
# Its target is a file coverage/plugins.[name of parser].cov
#
# Its prerequisites are the plugin class and its test file.
#
# Customizing the name of the coverage file allows Make to skip the
# tests if nothing has changed. This is also why the coverage file is
# deleted if the tests fail.
plugins.%.cov: plugins/%.py plugins/%_test.py
mkdir -p $(COVERAGE_DIR)
COVERAGE_FILE=coverage/$@ \
python -m pytest --cov-config=.coveragerc --cov=plugins.$* plugins/$*_test.py \
|| (rm $(COVERAGE_DIR)/$@ && exit 1)
# Generate test coverage for a single resource.
#
# This is a pattern rule based on the coverage file generated by pytest.
#
# Its target is a file coverage/resources.[name of parser].cov
#
# Its prerequisites are the plugin class and its test file.
#
# Customizing the name of the coverage file allows Make to skip the
# tests if nothing has changed. This is also why the coverage file is
# deleted if the tests fail.
resources.%.cov: resources/%.py resources/%_test.py
mkdir -p $(COVERAGE_DIR)
COVERAGE_FILE=coverage/$@ \
python -m pytest --cov-config=.coveragerc --cov=resources.$* resources/$*_test.py \
|| (rm $(COVERAGE_DIR)/$@ && exit 1)
# Test a single app.
#
# This rule is a shortcut for the "apps.%.cov" pattern rule so that
# the ".cov" suffix can be omitted.
$(APPS):
@$(MAKE) --no-print-directory [email protected]
# Test a single parser
#
# This rule is a shortcut for the "parsers.%.cov" pattern rule so that
# the ".cov" suffix can be omitted.
$(PARSERS):
@$(MAKE) --no-print-directory [email protected]
# Test a single plugin
#
# This rule is a shortcut for the "plugins.%.cov" pattern rule so that
# the ".cov" suffix can be omitted.
$(PLUGINS):
@$(MAKE) --no-print-directory [email protected]
# Test a single resource
#
# This rule is a shortcut for the "resources.%.cov" pattern rule so that
# the ".cov" suffix can be omitted.
$(RESOURCES):
@$(MAKE) --no-print-directory [email protected]
# Run a single HTTP script
$(HTTP_SCRIPTS):
python http/$(patsubst http.%,%,$@).py | less -R -E -X
# Run lint checks across the project
#
# This will consider plugins app controllers and their tests, and the
# main server file.
#
# Two linters are used for the sake of being comprehensive.
#
# These commands are also present in the Git pre-commit hook, but
# are only applied to changed files.
lint: lint-mypy lint-ruff lint-pylint
lint-mypy: dummy
mypy \
--sqlite-cache \
--html-report apps/static/mypy \
apps parsers plugins testing tools medley.py \
| grep -v "note:"
lint-ruff: dummy
ruff check apps parsers plugins testing tools medley.py
lint-pylint: dummy
pylint \
--rcfile=.pylintrc \
apps parsers plugins testing tools medley.py
lint-eslint: dummy
eslint apps/ledger
# Empty the logindex database and re-index
#
# For use when changes to the logindex or visitors apps require a
# database do-over.
logindex-reset: dummy
rm db/logindex.sqlite
touch medley.py
# Download third-party front-end assets.
#
assets: assets-flags
# Asset download of flag-icon-css library used in visitors app
#
# These files are used in the visitors app, but only a subset of what
# the project offers is needed.
assets-flags: dummy
rm -rf master.zip apps/static/flag-icon-css
curl --max-time 10 --silent -L -O 'https://github.com/lipis/flag-icon-css/archive/master.zip'
unzip master.zip
mkdir -p apps/static/flag-icon-css/flags/4x3
mkdir -p apps/static/flag-icon-css/css
mv flag-icon-css-master/flags/4x3 apps/static/flag-icon-css/flags/
mv flag-icon-css-master/css/flag-icon.min.css apps/static/flag-icon-css/css/
mv flag-icon-css-master/LICENSE apps/static/flag-icon-css/LICENSE
rm -rf master.zip flag-icon-css-master
# Set up git hooks
#
# This is independent of other targets like venv and setup so that it
# doesn't interfere with CI. The suggestion printed by the venv target
# is enough of a reminder, given how infrequently this target is
# needed.
hooks: dummy
ln -sf ../../hooks/pre-commit .git/hooks/pre-commit
# Build the application favicon.
favicon: dummy
convert -density 900 -background none -geometry 48x48 apps/static/app-icon.svg temp-48.png
convert -density 900 -background none -geometry 32x32 apps/static/app-icon.svg temp-32.png
convert -density 900 -background none -geometry 16x16 apps/static/app-icon.svg temp-16.png
convert temp-16.png temp-32.png temp-48.png apps/static/favicon.ico
rm temp-48.png temp-32.png temp-16.png
cd apps/static && optipng -quiet -o 3 *.png
ng: dummy
ng build --watch --configuration development ledger
ngtest: dummy
ng test
ngbuild: dummy
ng build
# Tmux automation
workspace:
# 0: Editor
tmux new-session -d -s "$(TMUX_SESSION_NAME)" "$$SHELL"
tmux send-keys -t "$(TMUX_SESSION_NAME)" "$(EDITOR) ." C-m
# 1: Shell
tmux new-window -a -t "$(TMUX_SESSION_NAME)" "$$SHELL"
tmux send-keys -t "$(TMUX_SESSION_NAME)" "source $(VENV_ACTIVATE)" C-m
# 2: Npm
tmux new-window -a -t "$(TMUX_SESSION_NAME)" -n "ng" "make ng"
# 3: Dev server
tmux new-window -a -t "$(TMUX_SESSION_NAME)" -n "devserver" "source $(VENV_ACTIVATE); make serve"
tmux select-window -t "$(TMUX_SESSION_NAME)":0
tmux attach-session -t "$(TMUX_SESSION_NAME)"
satellite:
tmux new-session -d -s "$(TMUX_SESSION_NAME)-satellite"
tmux link-window -s '$(TMUX_SESSION_NAME):ng' -t '$(TMUX_SESSION_NAME)-satellite:1'
tmux link-window -s '$(TMUX_SESSION_NAME):devserver' -t '$(TMUX_SESSION_NAME)-satellite:2'
tmux kill-window -t :0
# tmux select-window -t "$(TMUX_SESSION_NAME)-satellite":2
tmux attach-session -t "$(TMUX_SESSION_NAME)-satellite"
# Perform sundry cleanup tasks.
reset:
rm -r coverage
rm -r apps/static/coverage
rm .coverage
# Push the repository to GitHub.
mirror:
git push --force [email protected]:lovett/medley.git master:master
tags:
etags medley.py apps/*.py plugins/*.py parsers/*.py resources/*.py tools/*.py testing/*.py http/*.py