@@ -8,18 +8,23 @@ OWNER?=jupyter
88
99# Need to list the images in build dependency order
1010
11- # These are images we can cross-build
12- CROSS_IMAGES: = base-notebook \
11+ # Images supporting the following architectures:
12+ # - linux/amd64
13+ # - linux/arm64
14+ MULTI_IMAGES: = \
15+ base-notebook \
1316 minimal-notebook
14- # These images that aren't currently supported for cross-building, your help is welcome.
15- X86_IMAGES: = r-notebook \
17+ # Images that can only be built on the amd64 architecture (aka. x86_64)
18+ AMD64_ONLY_IMAGES: = \
19+ r-notebook \
1620 scipy-notebook \
1721 tensorflow-notebook \
1822 datascience-notebook \
1923 pyspark-notebook \
2024 all-spark-notebook
2125# All of the images
22- ALL_IMAGES: =base-notebook \
26+ ALL_IMAGES: = \
27+ base-notebook \
2328 minimal-notebook \
2429 r-notebook \
2530 scipy-notebook \
@@ -31,42 +36,92 @@ ALL_IMAGES:=base-notebook \
3136# Enable BuildKit for Docker build
3237export DOCKER_BUILDKIT: =1
3338
39+
40+
3441# https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
3542help :
3643 @echo " jupyter/docker-stacks"
3744 @echo " ====================="
38- @echo " Replace % with a stack directory name (e.g., make build-cross /minimal-notebook)"
45+ @echo " Replace % with a stack directory name (e.g., make build-multi /minimal-notebook)"
3946 @echo
4047 @grep -E ' ^[a-zA-Z0-9_%/-]+:.*?## .*$$' $(MAKEFILE_LIST ) | sort | awk ' BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
4148
42- build-x86/% : DARGS?=
43- build-x86/% : # # build the latest image for a stack on x86 only
44- docker buildx build $(DARGS ) --rm --force-rm -t $(OWNER ) /$(notdir $@ ) :latest ./$(notdir $@ ) --build-arg OWNER=$(OWNER ) --platform " linux/amd64" --push
49+
50+
51+ build/% : DARGS?=
52+ build/% : # # build the latest image for a stack on amd64 only
53+ @echo " ::group::Build $( OWNER) /$( notdir $@ ) (amd64)"
54+ docker build $(DARGS ) --rm --force-rm -t $(OWNER ) /$(notdir $@ ) :latest ./$(notdir $@ ) --build-arg OWNER=$(OWNER )
4555 @echo -n " Built image size: "
4656 @docker images $(OWNER ) /$(notdir $@ ) :latest --format " {{.Size}}"
47-
48- build-cross/% : DARGS?=
49- build-cross/% : # # build the latest image for a stack on x86 and ARM
50- docker buildx build $(DARGS ) --rm --force-rm -t $(OWNER ) /$(notdir $@ ) :latest ./$(notdir $@ ) --build-arg OWNER=$(OWNER ) --platform " linux/amd64,linux/arm64" --push
57+ @echo " ::endgroup::Build $( OWNER) /$( notdir $@ ) (amd64)"
58+ build-all : $(foreach I,$(ALL_IMAGES ) , build/$(I ) ) # # build all stacks
59+
60+ # Limitations on docker buildx build (using docker/buildx 0.5.1):
61+ #
62+ # 1. Can't --load and --push at the same time
63+ #
64+ # 2. Can't --load multiple platforms
65+ #
66+ # What does it mean to --load?
67+ #
68+ # - It means that the built image can be referenced by `docker` CLI, for example
69+ # when using the `docker tag` or `docker push` commands.
70+ #
71+ # Workarounds due to limitations:
72+ #
73+ # 1. We always make a dedicated amd64 build named as OWNER/<stack>-notebook so
74+ # we always can reference that image no matter what.
75+ #
76+ # 2. We always also build a multi-platform image during build-multi that will be
77+ # inaccessible with `docker tag` and `docker push` etc, but this will help us
78+ # test the build on the different platform and provide cached layers for
79+ # later.
80+ #
81+ # 3. We let push-multi refer to rebuilding a multi image with `--push`.
82+ #
83+ # We now rely on the cached layer.
84+ #
85+ # Outcomes of the workaround:
86+ #
87+ # 1. We can keep using the previously defined Makefile commands that doesn't
88+ # include `-multi` suffix as before.
89+ #
90+ # 2. Assuming we have setup docker/dockerx properly to build in arm64
91+ # architectures as well, then we can build and publish such images via the
92+ # `-multi` suffix without needing a local registry.
93+ #
94+ build-multi/% : DARGS?=
95+ build-multi/% : # # build the latest image for a stack on amd64 and arm64
96+ @echo " ::group::Build $( OWNER) /$( notdir $@ ) (amd64)"
97+ docker buildx build $(DARGS ) --rm --force-rm -t $(OWNER )$(notdir $@ ) :latest ./$(notdir $@ ) --build-arg OWNER=$(OWNER ) --platform " linux/amd64" --load
5198 @echo -n " Built image size: "
5299 @docker images $(OWNER ) /$(notdir $@ ) :latest --format " {{.Size}}"
100+ @echo " ::endgroup::Build $( OWNER) /$( notdir $@ ) (amd64)"
101+
102+ @echo "::group::Build $(OWNER)/$(notdir $@) (amd64,arm64)"
103+ docker buildx build $(DARGS) --rm --force-rm -t build-multi-tmp-cache/$(notdir $@):latest ./$(notdir $@) --build-arg OWNER=$(OWNER) --platform "linux/amd64,linux/arm64"
104+ @echo "::endgroup::Build $(OWNER)/$(notdir $@) (amd64,arm64)"
105+ build-all-multi : $(foreach I,$(MULTI_IMAGES ) , build-multi/$(I ) ) $(foreach I,$(AMD64_ONLY_IMAGES ) , build/$(I ) ) # # build all stacks
106+
53107
54- build-all : $(foreach I,$(CROSS_IMAGES ) , build-cross/$(I ) ) $(foreach I,$(X86_IMAGES ) , build-x86/$(I ) ) # # build all stacks
55108
56109check-outdated/% : # # check the outdated conda packages in a stack and produce a report (experimental)
57110 @TEST_IMAGE=" $( OWNER) /$( notdir $@ ) " pytest test/test_outdated.py
58111check-outdated-all : $(foreach I,$(ALL_IMAGES ) , check-outdated/$(I ) ) # # check all the stacks for outdated conda packages
59112
60- cont-clean-all : cont-stop-all cont-rm-all # # clean all containers (stop + rm)
61113
114+
115+ cont-clean-all : cont-stop-all cont-rm-all # # clean all containers (stop + rm)
62116cont-stop-all : # # stop all containers
63117 @echo " Stopping all containers ..."
64118 -docker stop -t0 $(shell docker ps -a -q) 2> /dev/null
65-
66119cont-rm-all : # # remove all containers
67120 @echo " Removing all containers ..."
68121 -docker rm --force $(shell docker ps -a -q) 2> /dev/null
69122
123+
124+
70125dev/% : ARGS?=
71126dev/% : DARGS?=-e JUPYTER_ENABLE_LAB=yes
72127dev/% : PORT?=8888
@@ -76,49 +131,63 @@ dev/%: ## run a foreground container for a stack
76131dev-env : # # install libraries required to build docs and run tests
77132 @pip install -r requirements-dev.txt
78133
134+
135+
79136docs : # # build HTML documentation
80137 sphinx-build docs/ docs/_build/
81138
139+
140+
82141hook/% : WIKI_PATH?=../wiki
83142hook/% : # # run post-build hooks for an image
84143 python3 -m tagging.tag_image --short-image-name " $( notdir $@ ) " --owner " $( OWNER) " && \
85144 python3 -m tagging.create_manifests --short-image-name " $( notdir $@ ) " --owner " $( OWNER) " --wiki-path " $( WIKI_PATH) "
86-
87145hook-all : $(foreach I,$(ALL_IMAGES ) ,hook/$(I ) ) # # run post-build hooks for all images
88146
89- img-clean : img-rm-dang img-rm # # clean dangling and jupyter images
90147
148+
149+ img-clean : img-rm-dang img-rm # # clean dangling and jupyter images
91150img-list : # # list jupyter images
92151 @echo " Listing $( OWNER) images ..."
93152 docker images " $( OWNER) /*"
94-
95153img-rm : # # remove jupyter images
96154 @echo " Removing $( OWNER) images ..."
97155 -docker rmi --force $(shell docker images --quiet "$(OWNER ) /* ") 2> /dev/null
98-
99156img-rm-dang : # # remove dangling images (tagged None)
100157 @echo " Removing dangling images ..."
101158 -docker rmi --force $(shell docker images -f "dangling=true" -q) 2> /dev/null
102159
160+
161+
103162pre-commit-all : # # run pre-commit hook on all files
104163 @pre-commit run --all-files || (printf " \n\n\n" && git --no-pager diff --color=always)
105-
106164pre-commit-install : # # set up the git hook scripts
107165 @pre-commit --version
108166 @pre-commit install
109167
168+
169+
110170pull/% : DARGS?=
111171pull/% : # # pull a jupyter image
112172 docker pull $(DARGS ) $(OWNER ) /$(notdir $@ )
113-
114- pull-all : $(foreach I,$(ALL_IMAGES ) ,pull/$(I ) ) # # pull all images
173+ pull-all : $(foreach I,$(ALL_IMAGES ) ,pull/$(I ) ) # # pull all images
115174
116175push/% : DARGS?=
117176push/% : # # push all tags for a jupyter image
177+ @echo " ::group::Push $( OWNER) /$( notdir $@ ) (amd64)"
118178 docker push --all-tags $(DARGS ) $(OWNER ) /$(notdir $@ )
119-
179+ @echo " ::endgroup::Push $( OWNER ) / $( notdir $@ ) (amd64) "
120180push-all : $(foreach I,$(ALL_IMAGES ) ,push/$(I ) ) # # push all tagged images
121181
182+ push-multi/% : DARGS?=
183+ push-multi/% : # # push all tags for a jupyter image that support multiple architectures
184+ @echo " ::group::Push $( OWNER) /$( notdir $@ ) (amd64,arm64)"
185+ docker buildx build $(DARGS ) --rm --force-rm $($(subst -,_,$(notdir $@ ) ) _EXTRA_TAG_ARGS) -t $(OWNER ) /$(notdir $@ ) :latest ./$(notdir $@ ) --build-arg OWNER=$(OWNER ) --platform " linux/amd64,linux/arm64"
186+ @echo " ::endgroup::Push $( OWNER) /$( notdir $@ ) (amd64,arm64)"
187+ push-all-multi : $(foreach I,$(MULTI_IMAGES ) ,push-multi/$(I ) ) $(foreach I,$(AMD64_ONLY_IMAGES ) ,push/$(I ) ) # # push all tagged images
188+
189+
190+
122191run/% : DARGS?=
123192run/% : # # run a bash in interactive mode in a stack
124193 docker run -it --rm $(DARGS ) $(OWNER ) /$(notdir $@ ) $(SHELL )
@@ -127,8 +196,11 @@ run-sudo/%: DARGS?=
127196run-sudo/% : # # run a bash in interactive mode as root in a stack
128197 docker run -it --rm -u root $(DARGS ) $(OWNER ) /$(notdir $@ ) $(SHELL )
129198
199+
200+
130201test/% : # # run tests against a stack (only common tests or common tests + specific tests)
202+ @echo " ::group::test/$( OWNER) /$( notdir $@ ) "
131203 @if [ ! -d " $( notdir $@ ) /test" ]; then TEST_IMAGE=" $( OWNER) /$( notdir $@ ) " pytest -m " not info" test ; \
132204 else TEST_IMAGE=" $( OWNER) /$( notdir $@ ) " pytest -m " not info" test $(notdir $@ ) /test; fi
133-
205+ @echo " ::endgroup::test/ $( OWNER ) / $( notdir $@ ) "
134206test-all : $(foreach I,$(ALL_IMAGES ) ,test/$(I ) ) # # test all stacks
0 commit comments