362 lines
14 KiB
Makefile
Executable File
362 lines
14 KiB
Makefile
Executable File
# ***
|
|
# WARNING: Do not EDIT or MERGE this file, it is generated by packagespec.
|
|
# ***
|
|
# 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 <layer-name>-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,<name>,<type>,<parent-name>,<source-include>,<source-exclude>))
|
|
#
|
|
# Each layer assumes the existence of a Dockerfile named <name>.Dockerfile in
|
|
# packages.lock/layers.
|
|
# It uses the <parent-name> 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 <source-include>, minus
|
|
# any source code matched by <source-exclude>. 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:
|
|
|
|
_ := $(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:
|
|
#
|
|
# <name>-debug : dump debug info for this image layer
|
|
# <name>-image : build the image for this image layer
|
|
# <name>-save : save the docker image for this layer as a tar.gz
|
|
# <name>-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
|
|
|
|
# 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 := <packagespec-only>
|
|
|
|
else
|
|
|
|
$(1)_SOURCE_GIT = $$(call QUOTE_LIST,$$($(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 := $$(shell git ls-files -m -- $$($(1)_SOURCE_GIT) | xargs)
|
|
$(1)_SOURCE_NEW := $$(shell git ls-files -o --exclude-standard -- $$($(1)_SOURCE_GIT) | xargs)
|
|
$(1)_SOURCE_DIRTY_LIST := $$(shell echo "$$($(1)_SOURCE_MODIFIED) $$($(1)_SOURCE_NEW)" | xargs)
|
|
$(1)_SOURCE_DIRTY_SUM := $$(shell [ -z "$$($(1)_SOURCE_DIRTY_LIST)" ] || cat $$($(1)_SOURCE_DIRTY_LIST) | $(SUM))
|
|
|
|
$(1)_SOURCE_ID := $$(shell if [ -z "$$($(1)_SOURCE_DIRTY_LIST)" ]; then \
|
|
echo "$$($(1)_SOURCE_COMMIT)"; \
|
|
else \
|
|
echo -n "dirty_$$($(1)_SOURCE_DIRTY_SUM)"; \
|
|
fi)
|
|
|
|
$(1)_ID_PREFIX := $$(shell [ -z "$$($(1)_SOURCE_DIRTY_LIST)" ] || echo "dirty_")
|
|
|
|
$(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
|
|
$(1)_DOCKERFILE := $$($(1)_CACHE_ROOT)/Dockerfile
|
|
|
|
# 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 := $$($(1)_ID_PREFIX)$$(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)
|
|
|
|
|
|
ifeq ($(DEBUG),YES)
|
|
$$(info ===== LAYER DEBUG INFO ($(1)) )
|
|
$$(info SOURCE_GIT=$$($(1)_SOURCE_GIT))
|
|
$$(info SOURCE_COMMIT=$$($(1)_SOURCE_COMMIT))
|
|
$$(info SOURCE_MODIFIED=$$($(1)_SOURCE_MODIFIED))
|
|
$$(info SOURCE_NEW=$$($(1)_SOURCE_NEW))
|
|
$$(info SOURCE_DIRTY_LIST=$$($(1)_SOURCE_DIRTY_LIST))
|
|
$$(info SOURCE_DIRTY_SUM=$$($(1)_SOURCE_DIRTY_SUM))
|
|
$$(info SOURCE_ID=$$($(1)_SOURCE_ID))
|
|
$$(info LAYER_ID=$$($(1)_LAYER_ID))
|
|
$$(info SOURCE_LIST=$$(shell $$($(1)_SOURCE_CMD)))
|
|
$$(info =====)
|
|
endif
|
|
|
|
|
|
# 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 '<missing>' >> $$$$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_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
|
|
|
|
$$($(1)_DOCKERFILE):
|
|
@mkdir -p "$$(dir $$(@))"
|
|
@$$(call QUERY_LOCK,.layers[] | select(.name=="$$($(1)_NAME)").dockerfile) > "$$@"
|
|
|
|
# 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
|
|
export DOCKER_BUILDKIT=1
|
|
$(1)_FULL_DOCKER_BUILD_COMMAND = docker build --ssh=default -t $$($(1)_IMAGE_NAME) $$($(1)_DOCKER_BUILD_ARGS) \
|
|
-f $$($(1)_DOCKERFILE) - < $$($(1)_SOURCE_ARCHIVE_WITH_DOCKERFILE)
|
|
|
|
$$($(1)_IMAGE): $$($(1)_BASE_IMAGE) $$($(1)_DOCKERFILE)
|
|
@$$(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 '<missing>' >> $$@
|
|
|
|
endef
|
|
|
|
### END LAYER
|
|
|
|
# Include the generated instructions to build each layer.
|
|
include $(LOCKDIR)/layers/layers.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))
|
|
|