# WARNING: Do not EDIT or MERGE this file, it is generated by 'packagespec lock'. # layer.mk contains the machinery to incrementally build the builder image # as separate layers, so each can be cached both locally and in CI. This serves # both to speed up builds by avoiding unnecessary repetition of work already done, # as well as to ehnance the reliability of builds by downloading external # dependencies only once per build when necessary. # # The build layers themselves can be individually exported as tarballs (by calling # make -save) for later inspection, for sharing, or for implementing # on-host caching without recourse to external docker registries. # # To use this file, include it in another makefile, and from there you must eval # calls to the LAYER macro with this syntax: # # $(eval $(call LAYER,,,,,)) # # Each layer assumes the existence of a Dockerfile named .Dockerfile in # packages.lock/layers. # It uses the to set a Docker build arg called BASE_IMAGE to the # resultant docker image ref of the named parent layer. You should use this BASE_IMAGE # in the FROM line in your image. # # There must also be a base image which has no parent, and that Dockerfile should # use a FROM line from an explicit docker image, e.g. debian:buster. # # Each image is provided only the source code identified by , minus # any source code matched by . Source code is any files which are # present and not ignored by Git. This includes cached files, modified files and new, # untracked files. The Dockerfile belonging to this layer is ALWAYS included in the # source, so you don't need to manually specify that. # # The set of source code identified by a single image layer is used to produce its # SOURCE_ID. The SOURCE_ID, when all the files are tracked by Git and are not modified # equals the latest Git commit SHA that affected any of those files or directories. # When there are any new or modified files, we take a SHA 256 sum of the latest Git # commit affecting those files concatenated with the output of git diff and the contents # of any untracked files, and prefix this with "dirty_". The SOURCE_ID is used as part of # the cache key for that layer. # # Because different combinations of source-include and source-exclude may have been # modified by the same commit, they may share the same source ID. Therefore, we also # calculate the LAYER_ID which takes into account not only the current layer's source # ID, but also its source include/exclude and the ID of its base layer. Thus any change # in any of the inputs of any base layer invalidates the cache of all subsequent layers. include $(shell git rev-parse --show-toplevel)/packages*.lock/config.mk .SECONDARY: DOCKERFILES_DIR := $(LOCKDIR)/layers _ := $(shell mkdir -p $(CACHE_ROOT)/source-archives) ### END BUILDER IMAGE LAYERS ## LAYER # The LAYER macro defines all the targets for each image defined above. # # The phony targets are the ones we typically run ourselves or in CI, they are: # # -debug : dump debug info for this image layer # -image : build the image for this image layer # -save : save the docker image for this layer as a tar.gz # -load : load this image from the saved tar.gz define LAYER LAYERS += $(1) $(1)_NAME := $(1) $(1)_TYPE := $(2) $(1)_BASE := $(3) $(1)_SOURCE_INCLUDE := $(4) $(1)_SOURCE_EXCLUDE := $(sort $(5) $(ALWAYS_EXCLUDE_SOURCE)) $(1)_CACHE_KEY_FILE := $(REPO_ROOT)/$(6) $(1)_IMAGE_ARCHIVE := $(REPO_ROOT)/$(7) $(1)_CACHE_ROOT := $(CACHE_ROOT)/layers/$$($(1)_NAME) ifneq ($$($(1)_BASE),) $(1)_BASE_CACHE_ROOT := $(CACHE_ROOT)/layers/$$($(1)_BASE) $(1)_BASE_ID_FILE := $$($(1)_BASE_CACHE_ROOT)/current-layer-id $(1)_BASE_LAYER_ID := $$(shell cat $$($(1)_BASE_ID_FILE)) $(1)_BASE_CACHE := $$($(1)_BASE_CACHE_ROOT)/$$($(1)_BASE_LAYER_ID) $(1)_BASE_IMAGE := $$($(1)_BASE_CACHE)/image.marker $(1)_BASE_IMAGE_NAME = $$(shell cat $$($(1)_BASE_IMAGE)) endif $(1)_DOCKERFILE := $(DOCKERFILES_DIR)/$$($(1)_NAME).Dockerfile # If no source is included, set source ID to none. # Note that we include the checksum of the generated Dockerfile as part of cache IDs # so we still invalidate the cache appropriately. ifeq ($$($(1)_SOURCE_INCLUDE),) $(1)_SOURCE_CMD := echo "" $(1)_SOURCE_ID := packagespec-only-$$($(1)_NAME) $(1)_SOURCE_ID_NICE_NAME := else $(1)_SOURCE_GIT = $$($(1)_SOURCE_INCLUDE) $$(call GIT_EXCLUDE_LIST,$$($(1)_SOURCE_EXCLUDE)) $(1)_SOURCE_COMMIT := $$(shell git rev-list -n1 $(GIT_REF) -- $$($(1)_SOURCE_GIT)) # If we allow dirty builds, generate the source ID as a function of the # source in in the current work tree. Where the source all happens to match a Git commit, # that commit's SHA will be the source ID. ifeq ($(ALLOW_DIRTY),YES) $(1)_SOURCE_CMD := { { \ git ls-files -- $$($(1)_SOURCE_GIT); \ git ls-files -m --exclude-standard -- $$($(1)_SOURCE_GIT); \ } | sort | uniq; } $(1)_SOURCE_MODIFIED := $$(trim $$(shell git ls-files -m -- $$($(1)_SOURCE_GIT))) $(1)_SOURCE_MODIFIED_SUM := $$(trim $$(shell git diff -- $$($(1)_SOURCE_GIT) | $(SUM))) $(1)_SOURCE_NEW := $$(trim $$(shell git ls-files -o --exclude-standard -- $$($(1)_SOURCE_GIT))) $(1)_SOURCE_NEW_SUM := $$(trim $$(shell git ls-files -o --exclude-standard -- $$($(1)_SOURCE_GIT) | $(SUM))) $(1)_SOURCE_DIRTY := $$(trim $$(shell if [ -z "$$($(1)_SOURCE_MODIFIED)" ] && [ -z "$$($(1)_SOURCE_NEW)" ]; then echo NO; else echo YES; fi)) $(1)_SOURCE_ID := $$(shell if [ -z "$$($(1)_SOURCE_MODIFIED)" ] && [ -z "$$($(1)_SOURCE_NEW)" ]; then \ echo $$($(1)_SOURCE_COMMIT); \ else \ echo -n dirty_; echo $$($(1)_SOURCE_MODIFIED_SUM) $$($(1)_SOURCE_NEW_SUM) | $(SUM); \ fi) $(1)_SOURCE_DIRTY_LIST := $$($(1)_SOURCE_MODIFIED) $$($(1)_SOOURCE_NEW) $(1)_SOURCE_ID_NICE_NAME := $$($(1)_SOURCE_ID) # No dirty builds allowed, so the SOURCE_ID is the git commit SHA, # and we list files using git ls-tree. else $(1)_SOURCE_ID := $$($(1)_SOURCE_COMMIT) $(1)_SOURCE_ID_NICE_NAME := $$($(1)_SOURCE_ID) $(1)_SOURCE_CMD := git ls-tree -r --name-only $(GIT_REF) -- $$($(1)_SOURCE_GIT) endif endif # LAYER_ID_CONTENTS dictates all the fields that can cause cache invalidation # to propagate from the current layer to all dependent layers. define $(1)_LAYER_ID_CONTENTS BASE_LAYER_ID=$$($(1)_BASE_LAYER_ID); LAYER_NAME=$$($(1)_NAME); SOURCE_ID=$$($(1)_SOURCE_ID); SOURCE_INCLUDE=$$($(1)_SOURCE_INCLUDE); SOURCE_EXCLUDE=$$($(1)_SOURCE_EXCLUDE); endef $(1)_LAYER_ID_CONTENTS_FILE := $$($(1)_CACHE_ROOT)/current-layer-id-contents $(1)_LAYER_ID_FILE := $$($(1)_CACHE_ROOT)/current-layer-id # Create cache root dir and write LAYER_ID_FILE_CONTENTS file. _ := $$(shell \ mkdir -p $$($(1)_CACHE_ROOT); \ echo "$$($(1)_LAYER_ID_CONTENTS)" > $$($(1)_LAYER_ID_CONTENTS_FILE); \ ) $(1)_LAYER_ID := $$(shell cat $$($(1)_LAYER_ID_CONTENTS_FILE) | $(SUM)) $(1)_SOURCE_ARCHIVE := $(CACHE_ROOT)/source-archives/$$($(1)_TYPE)-$$($(1)_LAYER_ID).tar $(1)_IMAGE_NAME := $(BUILDER_IMAGE_PREFIX)-$$($(1)_NAME):$$($(1)_LAYER_ID) $(1)_CACHE := $(CACHE_ROOT)/layers/$$($(1)_NAME)/$$($(1)_LAYER_ID) # Create cache dir and write Layer ID file. _ := $$(shell \ mkdir -p $$($(1)_CACHE); \ echo $$($(1)_LAYER_ID) > $$($(1)_LAYER_ID_FILE); \ ) $(1)_PHONY_TARGET_NAMES := debug id image save load $(1)_PHONY_TARGETS := $$(addprefix $$($(1)_NAME)-,$$($(1)_PHONY_TARGET_NAMES)) .PHONY: $$($(1)_PHONY_TARGETS) # File targets. $(1)_IMAGE := $$($(1)_CACHE)/image.marker $(1)_LAYER_REFS := $$($(1)_CACHE)/image.layer_refs $(1)_IMAGE_TIMESTAMP := $$($(1)_CACHE)/image.created_time $(1)_TARGETS = $$($(1)_PHONY_TARGETS) # UPDATE_MARKER_FILE ensures the image marker file has the same timestamp as the # docker image creation date it represents. This enables make to only rebuild it when # it has really changed, especially after loading the image from an archive. # It also writes a list of all the layers in this docker image's history, for use # when saving layers out to archives for use in pre-populating Docker build caches. define $(1)_UPDATE_MARKER_FILE export MARKER=$$($(1)_IMAGE); \ export LAYER_REFS=$$($(1)_LAYER_REFS); \ export IMAGE=$$($(1)_IMAGE_NAME); \ export IMAGE_CREATED; \ if ! { IMAGE_CREATED="$$$$(docker inspect -f '{{.Created}}' $$$$IMAGE 2>/dev/null)"; }; then \ if [ -f "$$$$MARKER" ]; then \ echo "==> Removing stale marker file for $$$$IMAGE" 1>&2; \ rm -f $$$$MARKER; \ fi; \ exit 0; \ fi; \ if [ ! -f "$$$$MARKER" ]; then \ echo "==> Writing marker file for $$$$IMAGE (created $$$$IMAGE_CREATED)" 1>&2; \ fi; \ echo $$$$IMAGE > $$$$MARKER; \ $(TOUCH) -m -d $$$$IMAGE_CREATED $$$$MARKER; \ echo "$$$$IMAGE" > $$$$LAYER_REFS; \ docker history --no-trunc -q $$$$IMAGE | grep -Fv '' >> $$$$LAYER_REFS; endef ## PHONY targets $(1)-debug: @echo "==> Debug info: $$($(1)_NAME) depends on $$($(1)_BASE)" @echo "$(1)_TARGETS = $$($(1)_TARGETS)" @echo "$(1)_SOURCE_CMD = $$($(1)_SOURCE_CMD)" @echo "$(1)_CACHE = $$($(1)_CACHE)" @echo "$(1)_DOCKERFILE = $$($(1)_DOCKERFILE)" @echo "$(1)_SOURCE_COMMIT = $$($(1)_SOURCE_COMMIT)" @echo "$(1)_SOURCE_ID = $$($(1)_SOURCE_ID)" @echo "$(1)_SOURCE_MODIFIED = $$($(1)_SOURCE_MODIFIED)" @echo "$(1)_SOURCE_DIRTY = $$($(1)_SOURCE_DIRTY)" @echo "$(1)_SOURCE_NEW = $$($(1)_SOURCE_NEW)" @echo "$(1)_IMAGE = $$($(1)_IMAGE)" @echo "$(1)_IMAGE_TIMESTAMP = $$($(1)_IMAGE_TIMESTAMP)" @echo "$(1)_IMAGE_ARCHIVE = $$($(1)_IMAGE_ARCHIVE)" @echo "$(1)_BASE_IMAGE = $$($(1)_BASE_IMAGE)" @echo $(1)-id: @echo $(1)-$$($(1)_SOURCE_ID) $(1)-write-cache-key: @mkdir -p $$(dir $$($(1)_CACHE_KEY_FILE)); \ cp $$($(1)_LAYER_ID_CONTENTS_FILE) $$($(1)_CACHE_KEY_FILE); \ echo "==> Cache key for $(1) written to $$($(1)_CACHE_KEY_FILE)"; \ cat $$($(1)_CACHE_KEY_FILE) $(1)-image: $$($(1)_IMAGE) @cat $$< $(1)-layer-refs: $$($(1)_LAYER_REFS) @echo $$< $(1)-save: $$($(1)_IMAGE_ARCHIVE) @echo $$< $(1)-load: @\ ARCHIVE=$$($(1)_IMAGE_ARCHIVE); \ IMAGE=$$($(1)_IMAGE_NAME); \ MARKER=$$($(1)_IMAGE); \ rm -f $$$$MARKER; \ echo "==> Loading $$$$IMAGE image from $$$$ARCHIVE"; \ docker load < $$$$ARCHIVE @$$(call $(1)_UPDATE_MARKER_FILE) ## END PHONY targets # Set the BASE_IMAGE build arg to reference the appropriate base image, # unless there is no referenced base image. $(1)_DOCKER_BUILD_ARGS = $$(shell [ -z "$$($(1)_BASE)" ] || echo --build-arg BASE_IMAGE=$$$$(cat $$($(1)_BASE_IMAGE))) $(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE := $$($(1)_CACHE)/source-archive.tar # Build the docker image. # # For dirty builds, tar up a source archive from the local filesystem. # We --ignore-failed-read so that deleted files that are not # committed do not cause problems. This should be OK for dirty builds. # # For non-dirty builds, ask Git directly for a source archive. # # We explicitly set the TAR format to ustar because this seems more compatible # with Docker than any other format. In future we should change this to POSIX # once Docker supports that properly, because ustar only supports filenames # < 256 chars which could eventually be an issue. TAR_FORMAT := --format=ustar $(1)_FULL_DOCKER_BUILD_COMMAND = docker build -t $$($(1)_IMAGE_NAME) $$($(1)_DOCKER_BUILD_ARGS) \ -f $$($(1)_DOCKERFILE) - < $$($(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE) $$($(1)_IMAGE): $$($(1)_BASE_IMAGE) @$$(call $(1)_UPDATE_MARKER_FILE) @if [ -f "$$@" ]; then exit 0; fi; \ echo "==> Building Docker image $$($(1)_IMAGE_NAME)"; \ echo " Layer name : $$($(1)_NAME)"; \ echo " Layer source ID : $$($(1)_SOURCE_ID_NICE_NAME)"; \ echo " For product revision : $(PRODUCT_REVISION_NICE_NAME)"; \ echo " For package source ID : $(PACKAGE_SOURCE_ID)"; \ if [ ! -f "$$($(1)_SOURCE_ARCHIVE)" ]; then \ if [ "$(ALLOW_DIRTY)" = "YES" ]; then \ echo "==> Building source archive from working directory: $$($(1)_SOURCE_ARCHIVE)" 1>&2; \ $$($(1)_SOURCE_CMD) | $(TAR) --create $(TAR_FORMAT) --file $$($(1)_SOURCE_ARCHIVE) --ignore-failed-read -T -; \ else \ echo "==> Building source archive from git: $$($(1)_SOURCE_ARCHIVE)" 1>&2; \ git archive --format=tar $(GIT_REF) $$($(1)_SOURCE_GIT) > $$($(1)_SOURCE_ARCHIVE); \ fi; \ fi; \ if [ ! -f "$$($(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE)" ]; then \ echo "==> Appending Dockerfile to source archive: $$($(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE)" 1>&2; \ cp $$($(1)_SOURCE_ARCHIVE) $$($(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE); \ $(TAR) --append $(TAR_FORMAT) $$($(1)_DOCKERFILE) --file $$($(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE); \ fi; \ echo $$($(1)_FULL_DOCKER_BUILD_COMMAND); \ $$($(1)_FULL_DOCKER_BUILD_COMMAND); \ $$(call $(1)_UPDATE_MARKER_FILE) # Save the docker image as a tar.gz. $$($(1)_IMAGE_ARCHIVE): | $$($(1)_IMAGE) @mkdir -p $$(dir $$@); \ IMAGE=$$$$(cat $$($(1)_IMAGE)); \ echo "==> Saving $(1) image to $$@"; \ docker save $$$$IMAGE \ $$$$(docker history -q --no-trunc $$$$IMAGE | grep -v missing) \ | gzip > $$@ $$($(1)_LAYER_REFS): @echo "$$($(1)_IMAGE_NAME)" > $$@ @docker history --no-trunc -q $$($(1)_IMAGE_NAME) | grep -Fv '' >> $$@ endef ### END LAYER # Include the generated instructions to build each layer. include $(sort $(shell find $(DOCKERFILES_DIR) -name '*.mk')) # Eagerly update the docker image marker files. _ := $(foreach L,$(LAYERS),$(shell $(call $(L)_UPDATE_MARKER_FILE))) # DOCKER_LAYER_LIST is used to dump the name of every docker ref in use # by all of the current builder images. By running 'docker save' against # this list, we end up with a tarball that can pre-populate the docker # cache to avoid unnecessary rebuilds. DOCKER_LAYER_LIST := $(CACHE_ROOT)/docker-layer-list write-cache-keys: $(addsuffix -write-cache-key,$(LAYERS)) @echo "==> All cache keys written." build-all-layers: $(addsuffix -image,$(LAYERS)) @echo "==> All builder layers built." .PHONY: debug debug: $(addsuffix -debug,$(LAYERS))