-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMakefile
1742 lines (1634 loc) · 64.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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# SPDX-FileCopyrightText: 2023 Ross Patterson <[email protected]>
#
# SPDX-License-Identifier: MIT
# Development, build, and maintenance tasks:
#
# To ease discovery for contributors, place option variables affecting behavior at the
# top. Skip down to `## Top-level targets:` to find targets intended for use by
# developers. The recipes for real targets that follow the top-level targets do the real
# work. If making changes here, start by reading the philosophy commentary at the bottom
# of this file.
# Project specific values:
export PROJECT_NAMESPACE=rpatterson
export PROJECT_NAME=prunerr
NPM_SCOPE=rpattersonnet
export DOCKER_USER=merpatterson
# TEMPLATE: See comments towards the bottom and update.
GPG_SIGNING_KEYID=2EFF7CCE6828E359
export DOWNLOAD_VOLUME=$(CHECKOUT_DIR)/var-docker/media/Library
PRUNERR_CMD=exec
PRUNERR_ARGS=$(PRUNERR_CMD)
# Option variables that control behavior:
export TEMPLATE_IGNORE_EXISTING=false
# https://devguide.python.org/versions/#supported-versions
PYTHON_SUPPORTED_MINORS=3.11 3.12 3.10 3.9 3.8
### "Private" Variables:
# Variables not of concern those running and reading top-level targets. These variables
# most often derive from the environment or other values. Place variables holding
# literal constants or option variables intended for use on the command-line towards the
# top. Otherwise, add variables to the appropriate following grouping. Make requires
# defining variables referenced in targets or prerequisites before those references, in
# contrast with references in recipes. As a result, the Makefile can't place these
# further down for readability and discover.
# Defensive settings for make:
# https://tech.davis-hansson.com/p/make/
SHELL:=bash
.ONESHELL:
.SHELLFLAGS:=-eu -o pipefail -c
.SILENT:
.DELETE_ON_ERROR:
MAKEFLAGS+=--warn-undefined-variables
MAKEFLAGS+=--no-builtin-rules
PS1?=$$
EMPTY=
COMMA=,
# Values used to install host operating system packages:
HOST_PREFIX=/usr
HOST_PKG_CMD_PREFIX=sudo
HOST_PKG_BIN=apt-get
HOST_PKG_INSTALL_ARGS=install -y
HOST_PKG_NAMES_ENVSUBST=gettext-base
HOST_PKG_NAMES_PIP=python3-pip
HOST_PKG_NAMES_DOCKER=docker-ce-cli docker-compose-plugin
HOST_PKG_NAMES_GPG=gnupg
HOST_PKG_NAMES_GHCLI=gh
HOST_PKG_NAMES_CURL=curl
HOST_PKG_NAMES_APG=apg
ifneq ($(shell which "brew"),)
HOST_PREFIX=/usr/local
HOST_PKG_CMD_PREFIX=
HOST_PKG_BIN=brew
HOST_PKG_INSTALL_ARGS=install
HOST_PKG_NAMES_ENVSUBST=gettext
HOST_PKG_NAMES_PIP=python
HOST_PKG_NAMES_DOCKER=docker docker-compose
else ifneq ($(shell which "apk"),)
HOST_PKG_BIN=apk
HOST_PKG_INSTALL_ARGS=add
HOST_PKG_NAMES_ENVSUBST=gettext
HOST_PKG_NAMES_PIP=py3-pip
HOST_PKG_NAMES_DOCKER=docker-cli docker-cli-compose
HOST_PKG_NAMES_GHCLI=github-cli
endif
HOST_PKG_CMD=$(HOST_PKG_CMD_PREFIX) $(HOST_PKG_BIN)
# Detect Docker command-line baked into the build-host image:
HOST_TARGET_DOCKER:=$(shell which docker)
ifeq ($(HOST_TARGET_DOCKER),)
HOST_TARGET_DOCKER=$(HOST_PREFIX)/bin/docker
endif
HOST_TARGET_PIP:=$(shell which pip3)
ifeq ($(HOST_TARGET_PIP),)
HOST_TARGET_PIP=$(HOST_PREFIX)/bin/pip3
endif
# Values derived from the environment:
USER_NAME:=$(shell id -u -n)
USER_FULL_NAME:=$(shell \
getent passwd "$(USER_NAME)" | cut -d ":" -f 5 | cut -d "," -f 1)
ifeq ($(USER_FULL_NAME),)
USER_FULL_NAME=$(USER_NAME)
endif
USER_EMAIL:=$(USER_NAME)@$(shell hostname -f)
export PUID:=$(shell id -u)
export PGID:=$(shell id -g)
export CHECKOUT_DIR=$(PWD)
# Managed user-specific directory out of the checkout:
# https://specifications.freedesktop.org/basedir-spec/0.8/ar01s03.html
STATE_DIR=$(HOME)/.local/state/$(PROJECT_NAME)
TZ=Etc/UTC
ifneq ("$(wildcard /usr/share/zoneinfo/)","")
TZ:=$(shell \
realpath --relative-to=/usr/share/zoneinfo/ \
$(firstword $(realpath /private/etc/localtime /etc/localtime)) \
)
endif
export TZ
export DOCKER_GID:=$(shell getent group "docker" | cut -d ":" -f 3)
# Values related to supported Python versions:
# Use the same Python version tox would as a default.
# https://tox.wiki/en/latest/config.html#base_python
PYTHON_HOST_MINOR:=$(shell \
pip3 --version | sed -nE 's|.* \(python ([0-9]+.[0-9]+)\)$$|\1|p;q')
export PYTHON_HOST_ENV=py$(subst .,,$(PYTHON_HOST_MINOR))
# Find the latest installed Python version of the supported versions:
PYTHON_BASENAMES=$(PYTHON_SUPPORTED_MINORS:%=python%)
PYTHON_AVAIL_EXECS:=$(foreach \
PYTHON_BASENAME,$(PYTHON_BASENAMES),$(shell which $(PYTHON_BASENAME)))
PYTHON_LATEST_EXEC=$(firstword $(PYTHON_AVAIL_EXECS))
PYTHON_LATEST_BASENAME=$(notdir $(PYTHON_LATEST_EXEC))
PYTHON_MINOR=$(PYTHON_HOST_MINOR)
ifeq ($(PYTHON_MINOR),)
# Fallback to the latest installed supported Python version
PYTHON_MINOR=$(PYTHON_LATEST_BASENAME:python%=%)
endif
PYTHON_DEFAULT_MINOR=$(firstword $(PYTHON_SUPPORTED_MINORS))
PYTHON_DEFAULT_ENV=py$(subst .,,$(PYTHON_DEFAULT_MINOR))
PYTHON_MINORS=$(PYTHON_SUPPORTED_MINORS)
ifeq ($(PYTHON_MINOR),)
PYTHON_MINOR=$(firstword $(PYTHON_MINORS))
else ifeq ($(findstring $(PYTHON_MINOR),$(PYTHON_MINORS)),)
PYTHON_MINOR=$(firstword $(PYTHON_MINORS))
endif
export PYTHON_MINOR
export PYTHON_ENV=py$(subst .,,$(PYTHON_MINOR))
PYTHON_SHORT_MINORS=$(subst .,,$(PYTHON_MINORS))
PYTHON_ENVS=$(PYTHON_SHORT_MINORS:%=py%)
PYTHON_ALL_ENVS=$(PYTHON_ENVS) build
PYTHON_EXTRAS=test devel
PYTHON_PROJECT_PACKAGE=$(subst -,,$(PROJECT_NAME))
PYTHON_PROJECT_GLOB=$(subst -,?,$(PROJECT_NAME))
export PYTHON_WHEEL=
# Values derived from Version Control Systems (VCS):
VCS_LOCAL_BRANCH:=$(shell git branch --show-current)
CI_COMMIT_BRANCH=
GITHUB_REF_TYPE=
GITHUB_REF_NAME=
ifeq ($(VCS_LOCAL_BRANCH),)
ifneq ($(CI_COMMIT_BRANCH),)
VCS_LOCAL_BRANCH=$(CI_COMMIT_BRANCH)
else ifeq ($(GITHUB_REF_TYPE),branch)
VCS_LOCAL_BRANCH=$(GITHUB_REF_NAME)
endif
endif
VCS_TAG=
CI_COMMIT_TAG=
ifeq ($(VCS_TAG),)
ifneq ($(CI_COMMIT_TAG),)
VCS_TAG=$(CI_COMMIT_TAG)
else ifeq ($(GITHUB_REF_TYPE),tag)
VCS_TAG=$(GITHUB_REF_NAME)
endif
endif
ifeq ($(VCS_LOCAL_BRANCH),)
# Guess branch name from tag:
ifneq ($(shell echo "$(VCS_TAG)" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$'),)
# Publish final releases from the `main` branch:
VCS_LOCAL_BRANCH=main
else ifneq ($(shell echo "$(VCS_TAG)" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+.+$$'),)
# Publish pre-releases from the `develop` branch:
VCS_LOCAL_BRANCH=develop
endif
endif
# Reproduce Git branch and remote configuration and logic:
VCS_CLONE_REMOTE:=$(shell git config "clone.defaultRemoteName")
ifeq ($(VCS_CLONE_REMOTE),)
VCS_CLONE_REMOTE=origin
endif
VCS_PUSH_REMOTE:=$(shell git config "branch.$(VCS_LOCAL_BRANCH).pushRemote")
ifeq ($(VCS_PUSH_REMOTE),)
VCS_PUSH_REMOTE:=$(shell git config "remote.pushDefault")
endif
ifeq ($(VCS_PUSH_REMOTE),)
VCS_PUSH_REMOTE=$(VCS_CLONE_REMOTE)
endif
VCS_UPSTREAM_REMOTE:=$(shell git config "branch.$(VCS_LOCAL_BRANCH).remote")
ifeq ($(VCS_UPSTREAM_REMOTE),)
VCS_UPSTREAM_REMOTE:=$(shell git config "checkout.defaultRemote")
endif
VCS_UPSTREAM_REF:=$(shell git config "branch.$(VCS_LOCAL_BRANCH).merge")
VCS_UPSTREAM_BRANCH=$(VCS_UPSTREAM_REF:refs/heads/%=%)
# Find the remote and branch for `v*` tags versioning data:
VCS_REMOTE=$(VCS_PUSH_REMOTE)
VCS_BRANCH=$(VCS_LOCAL_BRANCH)
export VCS_BRANCH
# Find the remote and branch for conventional commits release data:
VCS_COMPARE_REMOTE=$(VCS_UPSTREAM_REMOTE)
ifeq ($(VCS_COMPARE_REMOTE),)
VCS_COMPARE_REMOTE=$(VCS_PUSH_REMOTE)
endif
VCS_COMPARE_BRANCH=$(VCS_UPSTREAM_BRANCH)
ifeq ($(VCS_COMPARE_BRANCH),)
VCS_COMPARE_BRANCH=$(VCS_BRANCH)
endif
# Under CI, verify commits and release notes by comparing this branch with the branch
# maintainers would merge this branch into:
CI=false
ifeq ($(CI),true)
ifeq ($(VCS_COMPARE_BRANCH),develop)
VCS_COMPARE_BRANCH=main
else ifneq ($(VCS_BRANCH),main)
VCS_COMPARE_BRANCH=develop
endif
# If pushing to upstream release branches, get release data compared to the preceding
# release:
else ifeq ($(VCS_COMPARE_BRANCH),develop)
VCS_COMPARE_BRANCH=main
endif
VCS_BRANCH_SUFFIX=upgrade
VCS_MERGE_BRANCH=$(VCS_BRANCH:%-$(VCS_BRANCH_SUFFIX)=%)
# Tolerate detached `HEAD`, such as during a rebase:
VCS_FETCH_TARGETS=
ifneq ($(VCS_BRANCH),)
# Assemble the targets used to avoid redundant fetches during release tasks:
VCS_FETCH_TARGETS+=./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH)
ifneq ($(VCS_REMOTE)/$(VCS_BRANCH),$(VCS_COMPARE_REMOTE)/$(VCS_COMPARE_BRANCH))
VCS_FETCH_TARGETS+=./var/git/refs/remotes/$(VCS_COMPARE_REMOTE)/$(VCS_COMPARE_BRANCH)
endif
# Also fetch develop for merging back in the final release:
VCS_RELEASE_FETCH_TARGETS=./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH)
ifeq ($(VCS_BRANCH),main)
VCS_RELEASE_FETCH_TARGETS+=./var/git/refs/remotes/$(VCS_COMPARE_REMOTE)/develop
ifneq ($(VCS_REMOTE)/$(VCS_BRANCH),$(VCS_COMPARE_REMOTE)/develop)
ifneq ($(VCS_COMPARE_REMOTE)/$(VCS_COMPARE_BRANCH),$(VCS_COMPARE_REMOTE)/develop)
VCS_FETCH_TARGETS+=./var/git/refs/remotes/$(VCS_COMPARE_REMOTE)/develop
endif
endif
endif
ifneq ($(VCS_MERGE_BRANCH),$(VCS_BRANCH))
VCS_FETCH_TARGETS+=./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_MERGE_BRANCH)
endif
# The sequence of branches from which to find closest existing build artifacts, such as
# container images:
VCS_BRANCHES=$(VCS_BRANCH)
ifneq ($(VCS_BRANCH),main)
ifneq ($(VCS_BRANCH),develop)
VCS_BRANCHES+=develop
endif
VCS_BRANCHES+=main
endif
endif
# Run Python tools in isolated environments managed by Tox:
# Values used to run Tox:
TOX_ENV_LIST=$(subst $(EMPTY) ,$(COMMA),$(PYTHON_ENVS))
TOX_RUN_ARGS=run-parallel --parallel auto --parallel-live
ifeq ($(words $(PYTHON_MINORS)),1)
TOX_RUN_ARGS=run
endif
ifneq ($(PYTHON_WHEEL),)
TOX_RUN_ARGS+= --installpkg "$(PYTHON_WHEEL)"
endif
export TOX_RUN_ARGS
# The options that support running arbitrary commands in the venvs managed by tox
# without Tox's startup time:
TOX_EXEC_OPTS=--no-recreate-pkg --skip-pkg-install
TOX_EXEC_ARGS=tox exec $(TOX_EXEC_OPTS) -e "$(PYTHON_DEFAULT_ENV)"
TOX_EXEC_BUILD_ARGS=tox exec $(TOX_EXEC_OPTS) -e "build"
PIP_COMPILE_EXTRA=
# Values used to build Docker images:
DOCKER_FILE=./Dockerfile
export DOCKER_BUILD_ARGS=
export DOCKER_BUILD_PULL=false
# Values used to tag built images:
export DOCKER_VARIANT=
DOCKER_VARIANT_PREFIX=
ifneq ($(DOCKER_VARIANT),)
DOCKER_VARIANT_PREFIX=$(DOCKER_VARIANT)-
endif
export DOCKER_BRANCH_TAG=$(subst /,-,$(VCS_BRANCH))
GITLAB_CI=false
GITHUB_ACTIONS=false
CI_PROJECT_NAMESPACE=$(CI_UPSTREAM_NAMESPACE)
CI_TEMPLATE_REGISTRY_HOST=registry.gitlab.com
ifeq ($(GITHUB_ACTIONS),true)
DOCKER_REGISTRY_HOST=ghcr.io
else
DOCKER_REGISTRY_HOST=$(CI_TEMPLATE_REGISTRY_HOST)
endif
export DOCKER_REGISTRY_HOST
CI_REGISTRY=$(CI_TEMPLATE_REGISTRY_HOST)/$(CI_PROJECT_NAMESPACE)
CI_REGISTRY_IMAGE=$(CI_REGISTRY)/$(CI_PROJECT_NAME)
DOCKER_REGISTRIES=DOCKER GITLAB GITHUB
export DOCKER_REGISTRY=$(firstword $(DOCKER_REGISTRIES))
DOCKER_IMAGE_DOCKER=$(DOCKER_USER)/$(CI_PROJECT_NAME)
DOCKER_IMAGE_GITLAB=$(CI_REGISTRY_IMAGE)
DOCKER_IMAGE_GITHUB=ghcr.io/$(CI_PROJECT_NAMESPACE)/$(CI_PROJECT_NAME)
DOCKER_IMAGE=$(DOCKER_IMAGE_$(DOCKER_REGISTRY))
DOCKER_IMAGES=
ifeq ($(GITLAB_CI),true)
DOCKER_IMAGES+=$(DOCKER_IMAGE_GITLAB)
else ifeq ($(GITHUB_ACTIONS),true)
DOCKER_IMAGES+=$(DOCKER_IMAGE_GITHUB)
else
DOCKER_IMAGES+=$(DOCKER_IMAGE_DOCKER)
endif
# Values used to run built images in containers:
DOCKER_COMPOSE_RUN_ARGS=
DOCKER_COMPOSE_RUN_ARGS+= --rm
ifeq ($(shell tty),not a tty)
DOCKER_COMPOSE_RUN_ARGS+= -T
endif
export DOCKER_PASS
# Values derived from or overridden by CI environments:
CI_UPSTREAM_NAMESPACE=$(PROJECT_NAMESPACE)
CI_PROJECT_NAME=$(PROJECT_NAME)
ifeq ($(CI),true)
TEMPLATE_IGNORE_EXISTING=true
endif
GITHUB_REPOSITORY_OWNER=$(CI_UPSTREAM_NAMESPACE)
# Is this checkout a fork of the upstream project?:
CI_IS_FORK=false
ifeq ($(GITLAB_CI),true)
USER_EMAIL=$(USER_NAME)@runners-manager.gitlab.com
ifneq ($(VCS_BRANCH),develop)
ifneq ($(VCS_BRANCH),main)
DOCKER_REGISTRIES=GITLAB
endif
endif
ifneq ($(CI_PROJECT_NAMESPACE),$(CI_UPSTREAM_NAMESPACE))
CI_IS_FORK=true
DOCKER_REGISTRIES=GITLAB
DOCKER_IMAGES+=$(DOCKER_REGISTRY_HOST)/$(CI_UPSTREAM_NAMESPACE)/$(CI_PROJECT_NAME)
endif
else ifeq ($(GITHUB_ACTIONS),true)
USER_EMAIL=$(USER_NAME)@actions.github.com
ifneq ($(VCS_BRANCH),develop)
ifneq ($(VCS_BRANCH),main)
DOCKER_REGISTRIES=GITHUB
endif
endif
ifneq ($(GITHUB_REPOSITORY_OWNER),$(CI_UPSTREAM_NAMESPACE))
CI_IS_FORK=true
DOCKER_REGISTRIES=GITHUB
DOCKER_IMAGES+=ghcr.io/$(GITHUB_REPOSITORY_OWNER)/$(CI_PROJECT_NAME)
endif
endif
# Take GitHub auth from the environment under GitHub actions but from secrets on other
# project hosts:
GITHUB_TOKEN=
PROJECT_GITHUB_PAT=
ifeq ($(GITHUB_TOKEN),)
GITHUB_TOKEN=$(PROJECT_GITHUB_PAT)
else ifeq ($(PROJECT_GITHUB_PAT),)
PROJECT_GITHUB_PAT=$(GITHUB_TOKEN)
endif
GH_TOKEN=$(GITHUB_TOKEN)
export GH_TOKEN
export GITHUB_TOKEN
export PROJECT_GITHUB_PAT
# Values used for publishing releases:
# Safe defaults for testing the release process without publishing to the official
# project hosting services, indexes, and registries:
export PIP_COMPILE_ARGS=
RELEASE_PUBLISH=false
PYPI_REPO=testpypi
# Safe defaults for testing the release process without publishing to the final/official
# hosts/indexes/registries:
PYPI_HOSTNAME=test.pypi.org
# Publish releases from the `main` or `develop` branches:
ifeq ($(CI),true)
# Compile requirements on CI/CD as a test to make sure the frozen/pinned versions
# reflect all changes to dependencies, but don't upgrade packages so that external
# changes, such as new PyPI releases, don't turn CI/CD red spuriously and unrelated to
# the contributor's actual changes.
export PIP_COMPILE_ARGS=
endif
GITHUB_RELEASE_ARGS=--prerelease
DOCKER_PLATFORMS=
# Only publish releases from the `main` or `develop` branches and only under the
# canonical CI/CD platform:
ifeq ($(GITLAB_CI),true)
ifeq ($(VCS_BRANCH),main)
RELEASE_PUBLISH=true
GITHUB_RELEASE_ARGS=
else ifeq ($(VCS_BRANCH),develop)
# Publish pre-releases from the `develop` branch:
RELEASE_PUBLISH=true
endif
ifeq ($(RELEASE_PUBLISH),true)
PYPI_REPO=pypi
PYPI_HOSTNAME=pypi.org
# Only build and publish multi-platform images for the canonical Python version:
ifeq ($(PYTHON_MINOR),$(PYTHON_HOST_MINOR))
DOCKER_PLATFORMS=linux/amd64 linux/arm64 linux/arm/v7
endif
endif
endif
CI_REGISTRY_USER=$(CI_PROJECT_NAMESPACE)
# Avoid undefined variables warnings when running under local development:
PYPI_PASSWORD=
export PYPI_PASSWORD
TEST_PYPI_PASSWORD=
export TEST_PYPI_PASSWORD
VCS_REMOTE_PUSH_URL=
CODECOV_TOKEN=
DOCKER_PASS=
export DOCKER_PASS
CI_PROJECT_ID=
export CI_PROJECT_ID
CI_JOB_TOKEN=
export CI_JOB_TOKEN
CI_REGISTRY_PASSWORD=
export CI_REGISTRY_PASSWORD
GH_TOKEN=
# Override variable values if present in `./.env` and if not overridden on the
# command-line:
include $(wildcard .env)
# Finished with `$(shell)`, echo recipe commands going forward
.SHELLFLAGS+= -x
# <!--alex disable hooks-->
### Top-level targets:
.PHONY: all
## The default target.
all: build
.PHONY: start
## Run the local development end-to-end stack services in the background as daemons.
start: build-docker-$(PYTHON_MINOR) ./.env.~out~
docker compose down
docker compose up -d
.PHONY: run
## Run the local development end-to-end stack services in the foreground for debugging.
run: build-docker-$(PYTHON_MINOR) ./.env.~out~
docker compose down
docker compose up
### Build Targets:
#
# Recipes that make artifacts needed for by end-users, development tasks, other recipes.
.PHONY: build
## Set up everything for development from a checkout, local and in containers.
build: ./.git/hooks/pre-commit ./.env.~out~ $(HOST_TARGET_DOCKER) \
$(HOME)/.local/bin/tox ./var/log/npm-install.log build-docker \
$(PYTHON_ENVS:%=./.tox/%/bin/pip-compile)
$(MAKE) -e -j $(PYTHON_ENVS:%=build-requirements-%)
.PHONY: $(PYTHON_ENVS:%=build-requirements-%)
## Compile fixed/pinned dependency versions if necessary.
$(PYTHON_ENVS:%=build-requirements-%):
# Avoid parallel tox recreations stomping on each other
$(MAKE) -e "$(@:build-requirements-%=./.tox/%/bin/pip-compile)"
targets="./requirements/$(@:build-requirements-%=%)/user.txt \
$(PYTHON_EXTRAS:%=./requirements/$(@:build-requirements-%=%)/%.txt) \
./requirements/$(@:build-requirements-%=%)/build.txt"
# Workaround race conditions in pip's HTTP file cache:
# https://github.com/pypa/pip/issues/6970#issuecomment-527678672
$(MAKE) -e -j $${targets} ||
$(MAKE) -e -j $${targets} ||
$(MAKE) -e -j $${targets}
.PHONY: build-requirements-compile
## Compile the requirements for one Python version and one type/extra.
build-requirements-compile:
$(MAKE) -e "./.tox/$(PYTHON_ENV)/bin/pip-compile"
pip_compile_opts="--resolver backtracking --strip-extras $(PIP_COMPILE_ARGS)"
ifneq ($(PIP_COMPILE_EXTRA),)
pip_compile_opts+=" --extra $(PIP_COMPILE_EXTRA)"
endif
./.tox/$(PYTHON_ENV)/bin/pip-compile $${pip_compile_opts} \
--output-file "$(PIP_COMPILE_OUT)" "$(PIP_COMPILE_SRC)"
.PHONY: build-pkgs
## Update the built package for use outside tox.
build-pkgs: $(HOST_TARGET_DOCKER) \
./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH) \
./var-docker/$(PYTHON_ENV)/log/build-devel.log
# Defined as a .PHONY recipe so that more than one target can depend on this as a
# pre-requisite and it runs one time:
rm -vf ./dist/*
# Build Python packages/distributions from the development Docker container for
# consistency/reproducibility.
docker compose run $(DOCKER_COMPOSE_RUN_ARGS) $(PROJECT_NAME)-devel \
tox run -e "$(PYTHON_ENV)" --override "testenv.package=external" --pkg-only
# Copy to a location available in the Docker build context:
cp -lfv ./var-docker/$(PYTHON_ENV)/.tox/.pkg/tmp/dist/* "./dist/"
.PHONY: build-docs
## Render the static HTML form of the Sphinx documentation
build-docs: build-docs-html
.PHONY: build-docs-watch
## Serve the Sphinx documentation with live updates
build-docs-watch: $(HOME)/.local/bin/tox
mkdir -pv "./build/docs/html/"
tox exec -e "build" -- sphinx-autobuild -b "html" "./docs/" "./build/docs/html/"
.PHONY: build-docs-%
# Render the documentation into a specific format.
build-docs-%: $(HOME)/.local/bin/tox
tox exec -e "build" -- sphinx-build -b "$(@:build-docs-%=%)" -W \
"./docs/" "./build/docs/"
.PHONY: build-date
# A prerequisite that always triggers it's target.
build-date:
date
## Docker Build Targets:
#
# Strive for as much consistency as possible in development tasks between the local host
# and inside containers. To that end, most of the `*-docker` container target recipes
# should run the corresponding `*-local` local host target recipes inside the
# development container. Top level targets, such as `test`, should run as much as
# possible inside the development container.
.PHONY: build-docker
## Set up for development in Docker containers.
build-docker: $(HOME)/.local/bin/tox build-pkgs \
./var-docker/$(PYTHON_ENV)/log/build-user.log
tox run $(TOX_EXEC_OPTS) --notest -e "build"
$(MAKE) -e -j PYTHON_WHEEL="$(call current_pkg,.whl)" \
DOCKER_BUILD_ARGS="$(DOCKER_BUILD_ARGS) --progress plain" \
$(PYTHON_MINORS:%=build-docker-%)
.PHONY: $(PYTHON_MINORS:%=build-docker-%)
## Set up for development in a Docker container for one Python version.
$(PYTHON_MINORS:%=build-docker-%):
$(MAKE) -e \
PYTHON_MINORS="$(@:build-docker-%=%)" \
PYTHON_MINOR="$(@:build-docker-%=%)" \
PYTHON_ENV="py$(subst .,,$(@:build-docker-%=%))" \
"./var-docker/py$(subst .,,$(@:build-docker-%=%))/log/build-user.log"
.PHONY: build-docker-tags
## Print the list of image tags for the current registry and variant.
build-docker-tags:
$(MAKE) -e $(DOCKER_REGISTRIES:%=build-docker-tags-%)
.PHONY: $(DOCKER_REGISTRIES:%=build-docker-tags-%)
## Print the list of image tags for the current registry and variant.
$(DOCKER_REGISTRIES:%=build-docker-tags-%): $(HOME)/.local/bin/tox
test -e "./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH)"
docker_image=$(DOCKER_IMAGE_$(@:build-docker-tags-%=%))
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)$(PYTHON_ENV)-$(DOCKER_BRANCH_TAG)
ifeq ($(VCS_BRANCH),main)
# Update tags users depend on to be stable from the `main` branch:
VERSION=$$($(TOX_EXEC_BUILD_ARGS) -qq -- cz version --project)
major_version=$$(echo $${VERSION} | sed -nE 's|([0-9]+).*|\1|p')
minor_version=$$(
echo $${VERSION} | sed -nE 's|([0-9]+\.[0-9]+).*|\1|p'
)
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)$(PYTHON_ENV)-v$${minor_version}
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)$(PYTHON_ENV)-v$${major_version}
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)$(PYTHON_ENV)
endif
ifeq ($(PYTHON_MINOR),$(PYTHON_HOST_MINOR))
# Use this variant as the default used for tags such as `latest`
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)$(DOCKER_BRANCH_TAG)
ifeq ($(VCS_BRANCH),main)
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)v$${minor_version}
echo $${docker_image}:$(DOCKER_VARIANT_PREFIX)v$${major_version}
ifeq ($(DOCKER_VARIANT),)
echo $${docker_image}:latest
else
echo $${docker_image}:$(DOCKER_VARIANT)
endif
endif
endif
.PHONY: build-docker-build
## Run the actual commands used to build the Docker container image.
build-docker-build: ./Dockerfile $(HOST_TARGET_DOCKER) $(HOME)/.local/bin/tox \
$(HOME)/.local/state/docker-multi-platform/log/host-install.log \
./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH) \
./var/log/docker-login-DOCKER.log
# Workaround broken interactive session detection:
docker pull "python:$(PYTHON_MINOR)"
# Pull images to use as build caches:
docker_build_caches=""
ifeq ($(GITLAB_CI),true)
# Don't cache when building final releases on `main`
$(MAKE) -e "./var/log/docker-login-GITLAB.log" || true
ifneq ($(VCS_BRANCH),main)
if $(MAKE) -e pull-docker
then
docker_build_caches+=" --cache-from $(DOCKER_IMAGE_GITLAB):\
$(DOCKER_VARIANT_PREFIX)$(PYTHON_ENV)-$(DOCKER_BRANCH_TAG)"
fi
endif
endif
ifeq ($(GITHUB_ACTIONS),true)
$(MAKE) -e "./var/log/docker-login-GITHUB.log" || true
ifneq ($(VCS_BRANCH),main)
if $(MAKE) -e pull-docker
then
docker_build_caches+=" --cache-from $(DOCKER_IMAGE_GITHUB):\
$(DOCKER_VARIANT_PREFIX)$(PYTHON_ENV)-$(DOCKER_BRANCH_TAG)"
fi
endif
endif
# Assemble the tags for all the variant permutations:
$(MAKE) "./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH)"
docker_build_args=""
for image_tag in $$(
$(MAKE) -e --no-print-directory build-docker-tags
)
do
docker_build_args+=" --tag $${image_tag}"
done
ifeq ($(DOCKER_VARIANT),)
docker_build_args+=" --target user"
else
docker_build_args+=" --target $(DOCKER_VARIANT)"
endif
# https://github.com/moby/moby/issues/39003#issuecomment-879441675
docker buildx build $(DOCKER_BUILD_ARGS) \
--build-arg BUILDKIT_INLINE_CACHE="1" \
--build-arg PYTHON_MINOR="$(PYTHON_MINOR)" \
--build-arg PYTHON_ENV="$(PYTHON_ENV)" \
--build-arg VERSION="$$(
$(TOX_EXEC_BUILD_ARGS) -qq -- cz version --project
)" $${docker_build_args} $${docker_build_caches} --file "$(<)" "./"
.PHONY: $(PYTHON_MINORS:%=build-docker-requirements-%)
## Pull container images and compile fixed/pinned dependency versions if necessary.
$(PYTHON_MINORS:%=build-docker-requirements-%): ./.env.~out~
export PYTHON_MINOR="$(@:build-docker-requirements-%=%)"
export PYTHON_ENV="py$(subst .,,$(@:build-docker-requirements-%=%))"
$(MAKE) -e "./var-docker/$${PYTHON_ENV}/log/build-devel.log"
docker compose run $(DOCKER_COMPOSE_RUN_ARGS) $(PROJECT_NAME)-devel \
make -e PYTHON_MINORS="$(@:build-docker-requirements-%=%)" \
PIP_COMPILE_ARGS="$(PIP_COMPILE_ARGS)" \
build-requirements-py$(subst .,,$(@:build-docker-requirements-%=%))
### Test Targets:
#
# Recipes that run the test suite.
.PHONY: test
## Run the full suite of tests, coverage checks, and linters.
test: test-lint test-docker
.PHONY: test-local
## Run the full suite of tests, coverage checks, and linters on the local host.
test-local: $(HOME)/.local/bin/tox $(PYTHON_ENVS:%=build-requirements-%)
tox $(TOX_RUN_ARGS) --override "testenv.package=external" -e "$(TOX_ENV_LIST)"
.PHONY: test-lint
## Perform any linter or style checks, including non-code checks.
test-lint: $(HOST_TARGET_DOCKER) test-lint-code test-lint-docker test-lint-docs \
test-lint-prose
# Lint copyright and licensing:
docker compose run --rm -T "reuse"
.PHONY: test-lint-code
## Lint source code for errors, style, and other issues.
test-lint-code: ./var/log/npm-install.log
# Run linters implemented in JavaScript:
~/.nvm/nvm-exec npm run lint:code
.PHONY: test-lint-docs
## Lint documentation for errors, broken links, and other issues.
test-lint-docs: $(HOME)/.local/bin/tox ./requirements/$(PYTHON_HOST_ENV)/build.txt
# Run linters implemented in Python:
tox -e build -x 'testenv:build.commands=bin/test-lint-docs.sh'
.PHONY: test-lint-prose
## Lint prose text for spelling, grammar, and style
test-lint-prose: $(HOST_TARGET_DOCKER) $(HOME)/.local/bin/tox \
./requirements/$(PYTHON_HOST_ENV)/build.txt ./var/log/npm-install.log
# Lint all markup files tracked in VCS with Vale:
# https://vale.sh/docs/topics/scoping/#formats
git ls-files -co --exclude-standard -z \
':!NEWS*.rst' ':!LICENSES' ':!styles/Vocab/*.txt' ':!requirements/**' |
xargs -r -0 -- docker compose run --rm -T vale || true
# Lint all source code files tracked in VCS with Vale:
git ls-files -co --exclude-standard -z \
':!styles/*/meta.json' ':!styles/*/*.yml' |
xargs -r -0 -- \
docker compose run --rm -T vale --config="./styles/code.ini" || true
# Lint source code files tracked in VCS but without extensions with Vale:
git ls-files -co --exclude-standard -z | grep -Ez '^[^.]+$$' |
while read -d $$'\0'
do
cat "$${REPLY}" |
docker compose run --rm -T vale --config="./styles/code.ini" \
--ext=".pl"
done || true
# Run linters implemented in Python:
tox -e build -x 'testenv:build.commands=bin/test-lint-prose.sh'
# Run linters implemented in JavaScript:
~/.nvm/nvm-exec npm run lint:prose
.PHONY: test-debug
## Run tests directly on the system and start the debugger on errors or failures.
test-debug: $(HOME)/.local/bin/tox
$(TOX_EXEC_ARGS) -- pytest --pdb
.PHONY: test-docker
## Run the full suite of tests, coverage checks, and code linters in containers.
test-docker: $(HOST_TARGET_DOCKER) build-docker $(HOME)/.local/bin/tox build-pkgs
tox run $(TOX_EXEC_OPTS) --notest -e "build"
# Avoid race condition starting service dependencies:
docker compose run $(DOCKER_COMPOSE_RUN_ARGS) $(PROJECT_NAME)-daemon true
$(MAKE) -e -j PYTHON_WHEEL="$(call current_pkg,.whl)" \
DOCKER_BUILD_ARGS="$(DOCKER_BUILD_ARGS) --progress plain" \
DOCKER_COMPOSE_RUN_ARGS="$(DOCKER_COMPOSE_RUN_ARGS) -T" \
$(PYTHON_MINORS:%=test-docker-%)
.PHONY: $(PYTHON_MINORS:%=test-docker-%)
## Run the full suite of tests inside a docker container for one Python version.
$(PYTHON_MINORS:%=test-docker-%):
$(MAKE) -e \
PYTHON_MINORS="$(@:test-docker-%=%)" \
PYTHON_MINOR="$(@:test-docker-%=%)" \
PYTHON_ENV="py$(subst .,,$(@:test-docker-%=%))" \
test-docker-pyminor
.PHONY: test-docker-pyminor
## Run the full suite of tests inside a docker container for this Python version.
test-docker-pyminor: $(HOST_TARGET_DOCKER) build-docker-$(PYTHON_MINOR)
docker_run_args="--rm"
if test ! -t 0
then
# No fancy output when running in parallel
docker_run_args+=" -T"
fi
# Ensure the dist/package has been correctly installed in the image
docker compose run --no-deps $${docker_run_args} $(PROJECT_NAME)-daemon \
python -m "$(PYTHON_PROJECT_PACKAGE)" --help
docker compose run --no-deps $${docker_run_args} $(PROJECT_NAME)-daemon \
$(PROJECT_NAME) --help
# Run from the development Docker container for consistency:
docker compose run $${docker_run_args} $(PROJECT_NAME)-devel \
make -e PYTHON_MINORS="$(PYTHON_MINORS)" PYTHON_WHEEL="$(PYTHON_WHEEL)" \
test-local
# Upload any build or test artifacts to CI/CD providers
ifeq ($(GITLAB_CI),true)
ifeq ($(PYTHON_MINOR),$(PYTHON_HOST_MINOR))
ifneq ($(CODECOV_TOKEN),)
$(MAKE) "$(HOME)/.local/bin/codecov"
codecov --nonZero -t "$(CODECOV_TOKEN)" \
--file "./build/reports/$(PYTHON_ENV)/coverage.xml"
else ifneq ($(CI_IS_FORK),true)
set +x
echo "ERROR: CODECOV_TOKEN missing from ./.env or CI secrets"
false
endif
endif
endif
.PHONY: test-lint-docker
## Check the style and content of the `./Dockerfile*` files
test-lint-docker: $(HOST_TARGET_DOCKER) ./.env.~out~ ./var/log/docker-login-DOCKER.log
docker compose pull --quiet hadolint
docker compose run $(DOCKER_COMPOSE_RUN_ARGS) hadolint
docker compose run $(DOCKER_COMPOSE_RUN_ARGS) hadolint \
hadolint "./build-host/Dockerfile"
$(MAKE) -e -j $(PYTHON_MINORS:%=test-lint-docker-volumes-%)
.PHONY: $(PYTHON_MINORS:%=test-lint-docker-volumes-%)
## Prevent Docker volumes owned by `root` for one Python version.
$(PYTHON_MINORS:%=test-lint-docker-volumes-%):
$(MAKE) -e \
PYTHON_MINORS="$(@:test-lint-docker-volumes-%=%)" \
PYTHON_MINOR="$(@:test-lint-docker-volumes-%=%)" \
PYTHON_ENV="py$(subst .,,$(@:test-lint-docker-volumes-%=%))" \
test-lint-docker-volumes
.PHONY: test-lint-docker-volumes
## Prevent Docker volumes owned by `root`.
test-lint-docker-volumes: $(HOST_TARGET_DOCKER) ./.env.~out~
# Ensure that any bind mount volume paths exist in VCS so that `# dockerd` doesn't
# create them as `root`:
if test -n "$$(
./bin/docker-add-volume-paths.sh "$(CHECKOUT_DIR)" \
"/usr/local/src/$(PROJECT_NAME)"
)"
then
set +x
echo "\
ERROR: Docker bind mount paths didn't exist, force added ignore files.
Review ignores above in case they need changes or followup."
false
fi
.PHONY: test-push
## Verify commits before pushing to the remote.
test-push: $(VCS_FETCH_TARGETS) $(HOME)/.local/bin/tox
vcs_compare_rev="$(VCS_COMPARE_REMOTE)/$(VCS_COMPARE_BRANCH)"
ifeq ($(CI),true)
ifneq ($(PYTHON_MINOR),$(PYTHON_HOST_MINOR))
# Don't waste CI time, only continue for the canonical version:
exit
endif
ifeq ($(VCS_COMPARE_BRANCH),main)
# On `main`, compare with the preceding commit on `main`:
vcs_compare_rev="$(VCS_COMPARE_REMOTE)/$(VCS_COMPARE_BRANCH)^"
endif
endif
if ! git fetch "$(VCS_COMPARE_REMOTE)" "$(VCS_COMPARE_BRANCH)"
then
# For a newly created branch not yet on the remote, compare with the pre-release branch:
vcs_compare_rev="$(VCS_COMPARE_REMOTE)/develop"
fi
exit_code=0
(
$(TOX_EXEC_BUILD_ARGS) -- \
cz check --rev-range "$${vcs_compare_rev}..HEAD" &&
$(TOX_EXEC_BUILD_ARGS) -- \
python ./bin/cz-check-bump.py --compare-ref "$${vcs_compare_rev}"
) || exit_code=$$?
if (( $$exit_code == 3 || $$exit_code == 21 ))
then
exit
elif (( $$exit_code != 0 ))
then
exit $$exit_code
else
$(TOX_EXEC_BUILD_ARGS) -- \
towncrier check --compare-with "$${vcs_compare_rev}"
fi
.PHONY: test-clean
## Confirm that the checkout has no uncommitted VCS changes.
test-clean:
if test -n "$$(git status --porcelain)"
then
git status -vv
set +x
echo "WARNING: Checkout is not clean."
false
fi
### Release Targets:
#
# Recipes that make an changes needed for releases and publish built artifacts to
# end-users.
.PHONY: release
## Publish PyPI packages and Docker images if conventional commits require a release.
release: release-pkgs release-docker
.PHONY: release-pkgs
## Publish installable Python packages to PyPI if conventional commits require.
release-pkgs: $(HOME)/.local/bin/tox ~/.pypirc.~out~ $(HOST_TARGET_DOCKER) \
./var/log/git-remotes.log \
./var/git/refs/remotes/$(VCS_REMOTE)/$(VCS_BRANCH) ./.env.~out~ \
$(HOST_PREFIX)/bin/gh
# Don't release unless from the `main` or `develop` branches:
ifeq ($(RELEASE_PUBLISH),true)
# Import the private signing key from CI secrets
$(MAKE) -e "./var/log/gpg-import.log"
# Bump the version and build the final release packages:
$(MAKE) -e build-pkgs
# https://twine.readthedocs.io/en/latest/#using-twine
$(TOX_EXEC_BUILD_ARGS) -- twine check \
./var-docker/$(PYTHON_ENV)/.tox/.pkg/tmp/dist/*
# Ensure VCS has captured all the effects of building the release:
$(MAKE) -e test-clean
$(TOX_EXEC_BUILD_ARGS) -- twine upload -s -r "$(PYPI_REPO)" \
./var-docker/$(PYTHON_ENV)/.tox/.pkg/tmp/dist/*
export VERSION=$$($(TOX_EXEC_BUILD_ARGS) -qq -- cz version --project)
# Create a GitLab release:
./.tox/build/bin/twine upload -s -r "gitlab" \
./var-docker/$(PYTHON_ENV)/.tox/.pkg/tmp/dist/*
release_cli_args="--description ./NEWS-VERSION.rst"
release_cli_args+=" --tag-name v$${VERSION}"
release_cli_args+=" --assets-link {\
\"name\":\"PyPI\",\
\"url\":\"https://$(PYPI_HOSTNAME)/project/$(CI_PROJECT_NAME)/$${VERSION}/\",\
\"link_type\":\"package\"\
}"
release_cli_args+=" --assets-link {\
\"name\":\"GitLab-PyPI-Package-Registry\",\
\"url\":\"$(CI_SERVER_URL)/$(CI_PROJECT_PATH)/-/packages/\",\
\"link_type\":\"package\"\
}"
release_cli_args+=" --assets-link {\
\"name\":\"Docker-Hub-Container-Registry\",\
\"url\":\"https://hub.docker.com/r/$(DOCKER_USER)/$(CI_PROJECT_NAME)/tags\",\
\"link_type\":\"image\"\
}"
docker compose pull gitlab-release-cli
docker compose run --rm gitlab-release-cli release-cli \
--server-url "$(CI_SERVER_URL)" --project-id "$(CI_PROJECT_ID)" \
create $${release_cli_args}
# Create a GitHub release
gh release create "v$${VERSION}" $(GITHUB_RELEASE_ARGS) \
--notes-file "./NEWS-VERSION.rst" \
./var-docker/$(PYTHON_ENV)/.tox/.pkg/tmp/dist/*
endif
.PHONY: release-docker
## Publish all container images to all container registries.
release-docker: $(HOST_TARGET_DOCKER) build-docker \
$(DOCKER_REGISTRIES:%=./var/log/docker-login-%.log) \
$(HOME)/.local/state/docker-multi-platform/log/host-install.log
$(MAKE) -e -j DOCKER_COMPOSE_RUN_ARGS="$(DOCKER_COMPOSE_RUN_ARGS) -T" \
$(PYTHON_MINORS:%=release-docker-%)
.PHONY: $(PYTHON_MINORS:%=release-docker-%)
## Publish the container images for one Python version to all container registries.
$(PYTHON_MINORS:%=release-docker-%): \
$(DOCKER_REGISTRIES:%=./var/log/docker-login-%.log) \
$(HOME)/.local/state/docker-multi-platform/log/host-install.log
export PYTHON_ENV="py$(subst .,,$(@:release-docker-%=%))"
# Build other platforms in emulation and rely on the layer cache for bundling the
# native images built before into the manifests:
DOCKER_BUILD_ARGS="$(DOCKER_BUILD_ARGS) --push"
ifneq ($(DOCKER_PLATFORMS),)
DOCKER_BUILD_ARGS+=" --platform $(subst $(EMPTY) ,$(COMMA),$(DOCKER_PLATFORMS))"
else
endif
export DOCKER_BUILD_ARGS
# Push the end-user manifest and images:
PYTHON_WHEEL="$$(ls -t ./dist/*.whl | head -n 1)"
$(MAKE) -e DOCKER_BUILD_ARGS="$${DOCKER_BUILD_ARGS}\
--build-arg PYTHON_WHEEL=$${PYTHON_WHEEL}" build-docker-build
# Push the development manifest and images:
$(MAKE) -e DOCKER_VARIANT="devel" build-docker-build
# Update Docker Hub `README.md` by using the `./README.rst` reStructuredText version
# using the official/canonical Python version:
ifeq ($(VCS_BRANCH),main)
if TEST "$${PYTHON_ENV}" = "$(PYTHON_HOST_ENV)"
then
$(MAKE) -e "./var/log/docker-login-DOCKER.log"
docker compose pull --quiet pandoc docker-pushrm
docker compose up docker-pushrm
fi
endif
.PHONY: release-bump
## Bump the package version if conventional commits require a release.
release-bump: $(VCS_RELEASE_FETCH_TARGETS) $(HOME)/.local/bin/tox \
./var/log/npm-install.log \
./var-docker/$(PYTHON_ENV)/log/build-devel.log ./.env.~out~
if ! git diff --cached --exit-code
then
set +x
echo "CRITICAL: Cannot bump version with staged changes"
false
fi
# Update the local branch to the forthcoming version bump commit:
git switch -C "$(VCS_BRANCH)" "$$(git rev-parse HEAD)"
exit_code=0
if test "$(VCS_BRANCH)" = "main" &&
$(TOX_EXEC_BUILD_ARGS) -- python ./bin/get-base-version.py $$(
$(TOX_EXEC_BUILD_ARGS) -qq -- cz version --project
)
then
# Make a final release from the last pre-release:
true
else
# Do the conventional commits require a release?:
$(TOX_EXEC_BUILD_ARGS) -- python ./bin/cz-check-bump.py || exit_code=$$?
if (( $$exit_code == 3 || $$exit_code == 21 ))
then
# No commits require a release:
exit
elif (( $$exit_code != 0 ))
then
exit $$exit_code
fi
fi
# Collect the versions involved in this release according to conventional commits:
cz_bump_args="--check-consistency --no-verify"
ifneq ($(VCS_BRANCH),main)
cz_bump_args+=" --prerelease beta"
endif
ifeq ($(RELEASE_PUBLISH),true)
cz_bump_args+=" --gpg-sign"
# Import the private signing key from CI secrets
$(MAKE) -e ./var/log/gpg-import.log
endif
next_version=$$(
$(TOX_EXEC_BUILD_ARGS) -qq -- cz bump $${cz_bump_args} --yes --dry-run |
sed -nE 's|.* ([^ ]+) *→ *([^ ]+).*|\2|p;q'