forked from tanaylab/metacells
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMakefile
398 lines (315 loc) · 11.3 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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
NAME = metacells
MAX_LINE_LENGTH = 120
ALL_SOURCE_FILES = $(shell git ls-files)
PY_SOURCE_FILES = $(filter %.py, $(ALL_SOURCE_FILES))
RST_SOURCE_FILES = $(filter %.rst, $(ALL_SOURCE_FILES))
DOCS_SOURCE_FILES = $(filter docs/%, $(ALL_SOURCE_FILES))
H_SOURCE_FILES = $(filter %.h, $(ALL_SOURCE_FILES))
CPP_SOURCE_FILES = $(filter %.cpp, $(ALL_SOURCE_FILES))
.DEFAULT_GOAL := help
define PRINT_HELP_PYSCRIPT
import re, sys
for line in sys.stdin:
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
if match:
target, help = match.groups()
print("%-20s %s" % (target, help.replace('TODO-', 'TODO')))
endef
export PRINT_HELP_PYSCRIPT
help:
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
clean: clean-make clean-build clean-pyc clean-test clean-docs ## remove all build, test, coverage and Python artifacts
clean-make:
rm -fr .make.*
clean-build:
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
rm -fr metacells/*.so
clean-pyc:
find . -name .mypy_cache -exec rm -fr {} +
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test:
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
rm -fr .pytest_cache
clean-docs:
rm -fr docs/_build
TODO = todo$()x
pc: is_dev $(TODO) history format smells dist pytest docs staged tox ## check everything before commit
ci: history format smells dist docs tox ## check everything in a CI server
history: ## check to-be-done version is described in HISTORY.rst
@version=`grep 'current_version =' setup.cfg | sed 's/.* //;s/.dev.*//;'`; \
if grep -q "^$$version" HISTORY.rst; \
then true; \
else \
echo 'No entry in HISTORY.rst (run `make start_history`).'; \
false; \
fi
staged: ## check everything is staged for git commit
@if git status . | grep -q 'Changes not staged\|Untracked files'; \
then \
git status; \
echo 'There are unstaged changes (run `git add .`).'; \
false; \
else true; \
fi
format: trailingspaces linebreaks backticks fstrings isort black flake8 clang-format ## check code format
trailingspaces: .make.trailingspaces ## check for trailing spaces
REAL_SOURCE_FILES = \
$(filter-out %.png, \
$(filter-out %.svg, \
$(ALL_SOURCE_FILES)))
# TODO: Remove setup.cfg exception when bumpversion is fixed.
SP_SOURCE_FILES = $(filter-out setup.cfg, $(REAL_SOURCE_FILES))
.make.trailingspaces: $(SP_SOURCE_FILES)
@echo "trailingspaces"
@if grep -Hn '\s$$' $(SP_SOURCE_FILES); \
then \
echo 'Files contain trailing spaces (run `make reformat` or `make stripspaces`).'; \
false; \
else true; \
fi
touch $@
linebreaks: .make.linebreaks ## check line breaks in Python code
.make.linebreaks: $(PY_SOURCE_FILES)
@echo "linebreaks"
@if grep -Hn "[^=*][^][/<>\"'a-zA-Z0-9_,:()#}{.?!\\=\`+-]$$" $(PY_SOURCE_FILES) | grep -v -- '--$$\|import \*$$'; \
then \
echo 'Files wrap lines after instead of before an operator (fix manually).'; \
false; \
fi
touch $@
backticks: .make.backticks ## check usage of backticks in documentation
.make.backticks: $(PY_SOURCE_FILES) $(RST_SOURCE_FILES)
@echo "backticks"
@OK=true; \
for FILE in $(PY_SOURCE_FILES) $(RST_SOURCE_FILES); \
do \
if sed 's/``\([^`]*\)``/\1/g;s/:`\([^`]*\)`/:\1/g;s/`\([^`]*\)`_/\1_/g' "$$FILE" \
| grep --label "$$FILE" -n -H '`' \
| sed 's//`/g' \
| grep '.'; \
then OK=false; \
fi; \
done; \
if $$OK; \
then true; \
else \
echo 'Documentation contains invalid ` markers (fix manually).'; \
false; \
fi
touch $@
fstrings: .make.fstrings ## check f-strings in Python code
.make.fstrings: $(PY_SOURCE_FILES)
@echo "fstrings"
@if grep -Hn '^[^"]*\("\([^"]\|\\"\)*"[^"]*\)*[^f]"\([^"]\|\\"\)*{' $(PY_SOURCE_FILES) | grep -v 'NOT F-STRING'; \
then \
echo 'Strings appear to be f-strings, but are not (fix manually).'; \
false; \
fi
touch $@
isort: .make.isort ## check imports with isort
.make.isort: $(PY_SOURCE_FILES)
isort --line-length $(MAX_LINE_LENGTH) --force-single-line-imports --check $(NAME) tests bin
touch $@
$(TODO): .make.$(TODO) ## check there are no leftover TODO-X
.make.$(TODO): $(REAL_SOURCE_FILES)
@echo 'grep -n -i $(TODO) `git ls-files | grep -v pybind11`'
@if grep -n -i $(TODO) `git ls-files | grep -v pybind11`; \
then \
echo "Files contain $(TODO) markers (fix manually)."; \
false; \
else true; \
fi
touch $@
black: .make.black ## check format with black
.make.black: $(PY_SOURCE_FILES)
black --line-length $(MAX_LINE_LENGTH) --check $(NAME) tests
touch $@
flake8: .make.flake8 ## check format with flake8
.make.flake8: $(PY_SOURCE_FILES)
flake8 --max-line-length $(MAX_LINE_LENGTH) --ignore E203,F401,F403,W503 $(NAME) tests bin
touch $@
clang-format: .make.clang-format ## check format with clang-format
.make.clang-format: .clang-format $(H_SOURCE_FILES) $(CPP_SOURCE_FILES)
@echo "clang-format"
@if clang-format --dry-run -Werror $(H_SOURCE_FILES) $(CPP_SOURCE_FILES) 2> /dev/null; \
then true; \
else \
echo "clang-format would like to make changes"; \
false; \
fi
touch $@
reformat: stripspaces isortify blackify clang-reformat ## reformat code
stripspaces: # strip trailing spaces
@echo stripspaces
@for FILE in $$(grep -l '\s$$' $$(git ls-files | grep -v setup.cfg)); \
do sed -i -s 's/\s\s*$$//' $$FILE; \
done
isortify: ## sort imports with isort
isort --line-length $(MAX_LINE_LENGTH) --force-single-line-imports $(NAME) tests bin
blackify: ## reformat with black
black --line-length $(MAX_LINE_LENGTH) $(NAME) tests bin
clang-reformat: $(H_SOURCE_FILES) $(CPP_SOURCE_FILES)
@echo "clang-format -i"
@clang-format -i $(H_SOURCE_FILES) $(CPP_SOURCE_FILES)
smells: mypy pylint ## check for code smells
pylint: .make.pylint ## check code with pylint
.make.pylint: $(PY_SOURCE_FILES)
pylint --max-line-length $(MAX_LINE_LENGTH) $(NAME) tests bin
touch $@
mypy: .make.mypy ## check code with mypy
.make.mypy: $(PY_SOURCE_FILES)
mypy $(NAME) tests bin
touch $@
pytest: .make.pytest ## run tests on the active Python with pytest
.make.pytest: .make.build
pytest -s --cov=$(NAME) --cov-report=html --cov-report=term --no-cov-on-fail tests
touch $@
tox: .make.tox ## run tests on a clean Python version with tox
.make.tox: $(PY_SOURCE_FILES) $(H_SOURCE_FILES) $(CPP_SOURCE_FILES) tox.ini
tox
touch $@
.PHONY: docs
docs: .make.docs ## generate HTML documentation
RST_GENERATED_FILES = docs/timing_script.rst
.make.docs: $(DOCS_SOURCE_FILES) $(PY_SOURCE_FILES) $(RST_SOURCE_FILES) $(RST_GENERATED_FILES)
$(MAKE) -C docs clean
$(MAKE) -C docs html
@echo "Results in docs/_build/html/index.html"
touch $@
build: .make.build ## build the C++ extensions
.make.build: $(PY_SOURCE_FILES) $(H_SOURCE_FILES) $(CPP_SOURCE_FILES)
python setup.py build_ext --inplace
python setup.py build
touch $@
docs/timing_script.rst: \
docs/timing_script.part.1 \
docs/timing_script.part.2 \
docs/timing_script.part.3 \
docs/timing_script.part.4 \
metacells/scripts/timing.py
( cat docs/timing_script.part.1 \
; python metacells/scripts/timing.py --help 2>&1 \
| sed 's/timing.py/metacells_timing.py/;s/^\(.\)/ \1/;s/`/``/g' \
; cat docs/timing_script.part.2 \
; python metacells/scripts/timing.py combine --help 2>&1 \
| sed 's/timing.py/metacells_timing.py/;s/^\(.\)/ \1/;s/`/``/g' \
; cat docs/timing_script.part.3 \
; python metacells/scripts/timing.py sum --help 2>&1 \
| sed 's/timing.py/metacells_timing.py/;s/^\(.\)/ \1/;s/`/``/g' \
; cat docs/timing_script.part.4 \
; python metacells/scripts/timing.py flame --help 2>&1 \
| sed 's/timing.py/metacells_timing.py/;s/^\(.\)/ \1/;s/`/``/g' \
; cat docs/timing_script.part.5 \
) > $@
committed: staged ## check everything is committed in git
@if [ -z "$$(git status --short)" ]; \
then true; \
else \
git status; \
echo "There are uncommitted changes (run `git commit -m ...`)." \
false; \
fi
install: committed clean ## install the package into the active Python
python setup.py install
dist: .make.dist ## builds the release distribution package
.make.dist: staged $(ALL_SOURCE_FILES)
rm -fr dist/
python setup.py sdist
twine check dist/*
touch $@
upload: committed is_not_dev .make.dist ## upload the release distribution package
twine upload dist/*
current_version: # report the current version number
@grep 'current_version =' setup.cfg
start_patch: committed ## start working on the next patch version
@if grep -q 'current_version.*dev' setup.cfg; \
then \
read -p "Skip over releasing the current development version [n]? " answer; \
case "$$answer" in \
y|yes|Y|Yes|YES) bumpversion patch;; \
*) false;; \
esac; \
else bumpversion patch; \
fi
@grep 'current_version =' setup.cfg
start_minor: committed ## start working on the next minor version
@if grep -q 'current_version.*dev' setup.cfg; \
then \
read -p "Skip over releasing the current development version [n]? " answer; \
case "$$answer" in \
y|yes|Y|Yes|YES) bumpversion minor;; \
*) false ;; \
esac; \
else bumpversion minor; \
fi
@grep 'current_version =' setup.cfg
start_major: committed ## start working on the next major version
@if grep -q 'current_version.*dev' setup.cfg; \
then \
read -p "Skip over releasing the current development version [n]? " answer; \
case "$$answer" in \
y|yes|Y|Yes|YES) bumpversion major;; \
*) false ;; \
esac; \
else bumpversion major; \
fi
@grep 'current_version =' setup.cfg
start_history: is_dev ## append a history section for the development version
@version=`grep 'current_version =' setup.cfg | sed 's/.* //;s/.dev.*//;'`; \
if grep -q "^$$version (WIP)\$$" HISTORY.rst; \
then true; \
else \
echo >> HISTORY.rst; \
echo "$$version (WIP)" >> HISTORY.rst; \
echo "$$version" | sed 's/./-/g' >> HISTORY.rst; \
echo >> HISTORY.rst; \
echo "* ..." >> HISTORY.rst; \
fi
bump_dev: committed is_dev ## bump the development version indicator
bumpversion dev
@grep 'current_version =' setup.cfg
done_dev: committed done_history is_dev dist ## remove the development version indicator
bumpversion rel
@grep 'current_version =' setup.cfg
done_history: ## check to-be-done version is described in HISTORY.rst
@version=`grep 'current_version =' setup.cfg | sed 's/.* //;s/.dev.*//;'`; \
if grep -q "^$$version\$$" HISTORY.rst; \
then true; \
else \
echo "No finalized entry in HISTORY.rst (fix manually)."; \
false; \
fi
is_not_dev:
@if grep -q 'current_version.*dev' setup.cfg; \
then \
echo "`grep 'current_version =' setup.cfg` is a development version."; \
false; \
fi
is_dev:
@if grep -q 'current_version.*dev' setup.cfg; \
then true; \
else \
echo "`grep 'current_version =' setup.cfg` is not a development version."; \
false; \
fi
tags: $(PY_SOURCE_FILES) ## generate a tags file for vi
ctags $(PY_SOURCE_FILES)
flame: flame.html ## generate a flame graph from a timing.csv file
flame.html: timing.csv
python metacells/scripts/timing.py combine timing.csv \
| python metacells/scripts/timing.py flame -s \
| flameview.py --sizename 'Total Elapsed Time' --sortby size \
> flame.html
sum: ## summerize a timing.csv file
python metacells/scripts/timing.py combine timing.csv \
| python metacells/scripts/timing.py sum \
| column -t -s,