#
# \brief  Common steps of creating an archive within the depot
# \author Norman Feske
# \date   2017-03-17
#
# Variables that must be defined when including this file:
#
#   ARCHIVE         - archive name, corresponds to recipe name
#   RECIPE_DIR      - location of the archive recipe within the source tree
#   DEPOT_SUB_DIR   - archive-type-dependent destination within the depot
#   TAG_FILE        - file within the archive used as tag for make dependencies
#   UPDATE_VERSIONS - update recipe hash file if archive content changed
#


##
## Obtain information about the user-specified archive recipe
##
## Validate 'RECIPE_DIR', determine the version and content hash as given in
## the recipe, and define 'RECIPE_HASH_VALUE' and 'RECIPE_VERSION'. Rules using
## those definitions should depend on 'checked_recipe_hash_value_exists'.
##

checked_recipe_dir_specified:
ifeq ($(RECIPE_DIR),)
	@$(ECHO) "Error: recipe for '$(TARGET)' is missing"; false
endif

$(TARGET): checked_recipe_dir_specified

checked_recipe_is_unique: checked_recipe_dir_specified
ifneq ($(RECIPE_DIR),$(firstword $(RECIPE_DIR)))
	@echo "Error: recipe for '$(TARGET)' is ambiguous, candidates are:";\
	 for dir in $(RECIPE_DIR_CANDIDATES); do \
	   echo "       $$dir"; done; \
	 false
RECIPE_DIR_CANDIDATES := $(subst %,&,$(RECIPE_DIR))
RECIPE_DIR :=
endif

$(TARGET): checked_recipe_is_unique

#
# Determine hash file of current archive version as defined by the recipe
#

ifneq ($(RECIPE_DIR),)
EXPECTED_RECIPE_HASH_FILE := $(RECIPE_DIR)/hash
RECIPE_HASH_FILE          := $(wildcard $(EXPECTED_RECIPE_HASH_FILE))
endif

checked_recipe_hash_file_exists: checked_recipe_is_unique
ifeq ($(RECIPE_HASH_FILE),)
	@$(ECHO) "Error: Recipe hash file is missing,\n" \
	          "      expected at '$(EXPECTED_RECIPE_HASH_FILE)'"; false
endif

$(TARGET): checked_recipe_hash_file_exists

#
# Obtain hash value and version identifier from hash file. If no version
# identifier is present, the hash value is taken as version ('lastword' is
# the same as 'firstword'). This is the normal case for source archives.
#

ifneq ($(RECIPE_HASH_FILE),)
_RECIPE_HASH_FILE_CONTENT = $(call file_content,$(RECIPE_HASH_FILE))
RECIPE_HASH_VALUE         = $(lastword  $(_RECIPE_HASH_FILE_CONTENT))
RECIPE_VERSION            = $(firstword $(_RECIPE_HASH_FILE_CONTENT))
endif

checked_recipe_hash_value_exists:
ifeq ($(RECIPE_HASH_VALUE),)
	@$(ECHO) "Error: archive hash is undefined"; false
endif

#
# Remember content hash at invocation time, to detect a potential update caused
# by the 'UPDATE_VERSIONS' feature.
#
ORIG_RECIPE_HASH_VALUE := $(RECIPE_HASH_VALUE)

#
# Define name of temporary archive directory that we use until we know the
# archive hash.
#

DEPOT_ARCHIVE_DIR := $(DEPOT_SUB_DIR)/$(ARCHIVE)/incomplete

reset_stale_temporary_archive_dir:
ifneq ($(wildcard $(DEPOT_ARCHIVE_DIR)),)
	$(VERBOSE)rm -rf $(DEPOT_ARCHIVE_DIR); mkdir -p $(DEPOT_ARCHIVE_DIR)
endif

$(DEPOT_ARCHIVE_DIR)/$(TAG_FILE): reset_stale_temporary_archive_dir


##
## Create archive
##

#
# Rename archive to the hashed name after successful completion
#
$(ARCHIVE): _rename_to_final_archive


#
# Hook for executing a command whenever an archive version is automatically
# updated. It is used by 'extract_src_archive' to wipe stale binary archives
# that may already exist with the name of the new version. Such a version
# collision can happen when switching between topic branches. If it happens,
# the depot/create tool (called with 'FORCE=1 REBUILD=') would miss rebuilding
# the binary archive for the new version because an archive with the name
# already exists.
#
VERSION_UPDATED_CMD ?= true


#
# Rename archive name from the temporary name to the hashed name. However,
# if the hashed archive name already exists, keep the existing one and
# discard the just-built archive.
#
_rename_to_final_archive: _check_hash
	$(VERBOSE)final_name=$(ARCHIVE)/$(RECIPE_VERSION); \
	          rm -rf $(DEPOT_SUB_DIR)/$$final_name; \
	          mv $(DEPOT_ARCHIVE_DIR) $(DEPOT_SUB_DIR)/$$final_name; \
	          hash=$$(< $(DEPOT_ARCHIVE_DIR).hash); hint=""; \
	          test $$hash = $(ORIG_RECIPE_HASH_VALUE) || \
	             hint=" $(BRIGHT_COL)(new version)$(DEFAULT_COL)"; \
	          test $$hash = $(ORIG_RECIPE_HASH_VALUE) || \
	             $(VERSION_UPDATED_CMD); \
	          rm -f $(DEPOT_ARCHIVE_DIR).hash; \
	          $(ECHO) "$(DARK_COL)created$(DEFAULT_COL)" \
	                  "$(USER)/$(notdir $(DEPOT_SUB_DIR))/$$final_name$$hint"; \
	          true;

#
# Generate suggested version name for 'HASH_OUT_OF_DATE_MESSAGE'
#
# We suggest to use the current date as version. If there is already a version
# with the current date, we add a single-letter suffix to distinguish the new
# version. If we run out of letters or if the old recipe's version cannot be
# compared with the current date, we suggest to append a '-x' to the old
# recipe's version. In the latter case (e.g., when using a versioning scheme
# not based on dates), the package creator may want to manually adjust the
# version identifier anyway.
#
suffix_from_list = $(subst $(EMPTY) $(EMPTY),,$(strip $1))
suffixed_version = $1$(if $(call suffix_from_list,$2),-$(call suffix_from_list,$2),)

_string_higher_than = $(filter-out $(firstword $(sort $1 $2)),$1)
_higher_string      = $(if $(call _string_higher_than,$1,$2),$1,$2)

VERSION_CMD ?= $(strip $(shell date --iso-8601))

_version_higher_than_recipe = $(call _string_higher_than,\
                                 $(call suffixed_version,$1,$2),$(RECIPE_VERSION))

_next_version = $(eval FOUND := $(if $(call _version_higher_than_recipe,$1,$2),\
                                   $(call suffixed_version,$1,$2)))\
                $(foreach C,a b c d e f g h i j k l m n o p q r s t u v w x y z,\
                   $(if $(FOUND),,\
                      $(if $(call _version_higher_than_recipe,$1,$2 $C),\
                           $(eval FOUND := $(call suffixed_version,$1,$2 $C)))))\
                $(if $(FOUND),$(FOUND),$(addsuffix -x,$(RECIPE_VERSION)))

next_version = $(strip $(call _next_version,$(VERSION_CMD)))

#
# Message presented to the user whenever a recipe hash is out of date
#
define HASH_OUT_OF_DATE_MESSAGE

Error: $(RECIPE_HASH_FILE) is out of date.

This error indicates that the archived source code has changed, which should
be reflected by incrementing the archive version and updating the hash of
the recipe. You may update the recipe hash via the following command:

  echo" "$(next_version)" $$hash "> $(RECIPE_HASH_FILE)

The above command takes the current date as version identifier. Should this
not be your intention, please adjust the hash file manually.

endef


ifeq ($(UPDATE_VERSIONS),)
HASH_MISMATCH_CMD = $(ECHO) "$(subst $(NEWLINE),\n,$(HASH_OUT_OF_DATE_MESSAGE))"; false
else
HASH_MISMATCH_CMD = version=$(next_version); \
                    echo "$$version $$hash" > $(RECIPE_HASH_FILE);
endif

#
# Check the consistency between the hash of the archive recipe and the actual
# hash of the generated archive. If both hash values differ, we print the
# expected hash value and remove the archive. The user is expected to
# update the recipe hash before attempting the archive creation again.
#
_check_hash: $(DEPOT_ARCHIVE_DIR).hash checked_recipe_hash_value_exists
	$(VERBOSE)hash=$$(< $(DEPOT_ARCHIVE_DIR).hash); \
	          test $$hash = $(RECIPE_HASH_VALUE) || ($(HASH_MISMATCH_CMD))

#
# Shell command used to calculate the hash of an archive
#
# The command is invoked from within the archive directory within the depot.
#
# The hash value depends on both the file names (the first 'find' command)
# found in the archive as well as the content of the files (the second 'find'
# command).
#
HASH_CMD := cd $(DEPOT_ARCHIVE_DIR); \
            (find . -type f | sort; \
             find . -type f | sort | xargs -d '\n' cat) \
            | $(HASHSUM) | sed "s/ .*//"

#
# Generate the hash from the archive content
#
$(DEPOT_ARCHIVE_DIR).hash: $(DEPOT_ARCHIVE_DIR)/$(TAG_FILE)
	$(VERBOSE)$(HASH_CMD) > $@

$(DEPOT_ARCHIVE_DIR):
	$(VERBOSE)mkdir -p $@

$(DEPOT_ARCHIVE_DIR)/$(TAG_FILE): $(DEPOT_ARCHIVE_DIR)

