#
# \brief  Download and patch port of 3rd-party source code
# \author Norman Feske
# \date   2014-05-07
#
# This makefile must be invoked from the port directory.
#
# Arguments:
#
# PORT - port description file
#
# This makefile includes the port description file. The namespace for variables
# is organized as follows: Variables and functions that are private to the
# prepare_port tool are prefixed with '_'. Port description files must not
# declare any variable with a leading '_'. Variables that interface the
# prepare_port tool with the port description file are written uppercase. Local
# helper variables in the port description files should be written lowercase.
#

# XXX remove this line when the tool has stabilized
STRICT_HASH ?= no

PORTS_TOOL_DIR ?= $(GENODE_DIR)/tool/ports

#
# Utility to check if a python module is installed
#
check_python_module = $(if $(shell python -c "import $(1)" 2>&1),$(error Need to have python module '$(1)' installed.),)

default:

.NOTPARALLEL: default

# repository that contains the port description, used to look up patch files
REP_DIR := $(realpath $(dir $(PORT))/..)

#
# Check presence of argument $1. Back out with error message $2 if not defined.
#
_assert = $(if $(strip $1),$1,$(info Error: $(strip $2))$(error ))

#
# Helper function that returns $1 if defined, otherwise it returns $2
#
_prefer = $(if $1,$1,$2)

#
# Include common definitions
#
include $(PORTS_TOOL_DIR)/mk/common.inc

#
# Include definitions provided by the port description file
#
include $(PORT)

$(call check_tool,wget)
$(call check_tool,patch)
$(call check_tool,sha256sum)

#
# Assertion for the presence of a LICENSE and VERSION declarations in the port
# description
#
ifeq ($(LICENSE),)
default: license_undefined
license_undefined:
	@$(ECHO) "Error: License undefined"; false
endif

ifeq ($(VERSION),)
default: version_undefined
version_undefined:
	@$(ECHO) "Error: Version undefined"; false
endif

_DST_HASH_FILE := $(notdir $(PORT:.port=)).hash


#
# Default rule that triggers the actual preparation steps
#
default: $(DOWNLOADS) _patch $(_DST_HASH_FILE) _dirs

_dirs: $(DOWNLOADS)


##
## Generate the HASH file
##

include $(PORTS_TOOL_DIR)/mk/hash.inc


##
## Apply patches
##

# default arguments for the patch command
PATCH_OPT ?= -p0

#
# Helper function to obtain options for applying a patch
#
_patch_opt = $(call _prefer,$(PATCH_OPT($1)),$(PATCH_OPT))

#
# Helper function to look up the input file for a patch
#
_patch_input = $(wildcard $1 $(REP_DIR)/$1)

#
# Rules for applying patches
#
# The 'patch' rule is a phony rule that is used as default rule. It triggers
# all other steps such as downloading and hash-sum checks. For each patch, there
# dependency to a phony rule.
#
_PATCH_TARGETS := $(addprefix phony/patches/,$(PATCHES))

_patch: $(_PATCH_TARGETS)

$(_PATCH_TARGETS): $(DOWNLOADS)

phony/patches/%:
	@$(MSG_APPLY)$*
	$(VERBOSE)test -f $(firstword $(call _patch_input,$*) fail) ||\
		($(ECHO) "Error: Could not find patch $*"; false)
	$(VERBOSE)for i in $(call _patch_input,$*); do\
	              patch -s $(call _patch_opt,$*) < $$i; done


##
## Assemble custom directory structures within the port directory
##

_DIR_TARGETS := $(addprefix phony/dirs/,$(DIRS))

_dirs: $(_DIR_TARGETS)

$(_DIR_TARGETS): _patch

_dir_content = $(call _assert,$(DIR_CONTENT($1)), \
                              Missing declaration of DIR_CONTENT($1))

phony/dirs/%:
	@$(MSG_INSTALL)$*
	$(VERBOSE)mkdir -p $*
	$(VERBOSE)cp -Lrf $(call _dir_content,$*) $*


##
## Obtain source codes from a Git repository
##

_git_dir = $(call _assert,$(DIR($1)),Missing declaration of DIR($*))

%.git:
	$(VERBOSE)test -n "$(REV($*))" ||\
		($(ECHO) "Error: Undefined revision for $*"; false);
	$(VERBOSE)test -n "$(URL($*))" ||\
		($(ECHO) "Error: Undefined URL for $*"; false);
	$(VERBOSE)dir=$(call _git_dir,$*);\
		test -d $$dir || $(MSG_DOWNLOAD)$(URL($*)); \
		test -d $$dir || git clone $(URL($*)) $$dir &> >(sed 's/^/$(MSG_GIT)/'); \
		$(MSG_UPDATE)$$dir; \
		cd $$dir && $(GIT) fetch && $(GIT) reset -q --hard HEAD && $(GIT) checkout -q $(REV($*))


##
## Obtain source codes from a Git repository, subdirectory, using sparse-checkout
##

%.sparse-git:
	$(VERBOSE)test -n "$(REV($*))" ||\
		($(ECHO) "Error: Undefined revision for $*"; false);
	$(VERBOSE)test -n "$(URL($*))" ||\
		($(ECHO) "Error: Undefined URL for $*"; false);
	$(VERBOSE)test -n "$(SPARSE_PATH($*))" ||\
		($(ECHO) "Error: Undefined SPARSE_PATH for $*"; false);
	$(VERBOSE)dir=$(call _git_dir,$*);\
		sparse_sane=$$(echo $(SPARSE_PATH($*)) |  sed -e 's-^/--') ;\
		test -d $$dir || ( $(MSG_DOWNLOAD)$(URL($*)); \
			tmp=$$(mktemp -d); \
			git clone --depth 1 --filter=blob:none --sparse $(URL($*)) $$tmp &> >(sed 's/^/$(MSG_GIT)/'); \
			$(GIT) -C $$tmp sparse-checkout set $$sparse_sane && \
			$(GIT) -C $$tmp checkout -q $(REV($*)); \
			mkdir -p $$dir; \
			mv $$tmp/$$sparse_sane/* $$dir; rm -rf $$tmp; )


##
## Obtain source codes from Subversion repository
##

_svn_dir = $(call _assert,$(DIR($1)),Missing declaration of DIR($*))

%.svn:
	$(VERBOSE)test -n "$(REV($*))" ||\
		($(ECHO) "Error: Undefined revision for $*"; false);
	$(VERBOSE)test -n "$(URL($*))" ||\
		($(ECHO) "Error: Undefined URL for $*"; false);
	$(VERBOSE)dir=$(call _svn_dir,$*);\
		rm -rf $$dir; \
		$(MSG_DOWNLOAD)$(URL($*)); \
		svn export -q $(URL($*))@$(REV($*)) $$dir;


##
## Download a plain file
##

_file_name = $(call _prefer,$(NAME($1)),$(notdir $(URL($1))))

#
# Some downloads are available via HTTPS only, but wget < 3.14 does not support
# server-name identification, which is used by some sites. So, we disable
# certificate checking in wget and check the validity of the download via SIG
# or SHA.
#
# Successful and integrity-checked downloads are cached at the
# GENODE_CONTRIB_CACHE directory. The combination of 'cp' and 'mv' when
# populating the cache prevents corrupted files in the cache when the disk is
# full.
#

%.file:
	$(VERBOSE)test -n "$(URL($*))" ||\
		($(ECHO) "Error: Undefined URL for $(call _file_name,$*)"; false);
	$(VERBOSE)mkdir -p $(dir $(call _file_name,$*))
	$(VERBOSE)name=$(call _file_name,$*); cached_name=$(GENODE_CONTRIB_CACHE)/$(SHA($*))_`basename $$name`; \
		(test -f $$name || ! test -f $$cached_name || cp $$cached_name $$name); \
		(test -f $$name || $(MSG_DOWNLOAD)$(URL($*))); \
		(test -f $$name || wget --quiet --no-check-certificate $(URL($*)) -O $$name) || \
			($(ECHO) Error: Download for $* failed; false)
	$(VERBOSE)\
		($(ECHO) "$(SHA($*))  $(call _file_name,$*)" |\
		sha256sum -c > /dev/null 2> /dev/null) || \
			($(ECHO) Error: Hash sum check for $* failed; false)
	$(VERBOSE)name=$(call _file_name,$*); cached_name=$(GENODE_CONTRIB_CACHE)/$(SHA($*))_`basename $$name`; \
		mkdir -p $(GENODE_CONTRIB_CACHE); \
		(test -f $$cached_name || (cp $$name $$cached_name.tmp && mv $$cached_name.tmp $$cached_name))


##
## Download and unpack an archive
##

_archive_name = $(call _prefer,$(NAME($1)),$(notdir $(URL($1))))
_archive_dir  = $(call _assert,$(DIR($1)),Missing definition of DIR($*) in $(PORT))

_tar_opt   = $(call _prefer,$(TAR_OPT($1)),--strip-components=1)
_unzip_opt = $(call _prefer,$(UNZIP_OPT($1)),$(UNZIP_OPT))

#
# Archive extraction functions for various archive types
#
_extract_function(tar)     = tar xmf  $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1)
_extract_function(tgz)     = tar xmfz $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1)
_extract_function(tar.gz)  = tar xmfz $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1)
_extract_function(tar.xz)  = tar xmfJ $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1)
_extract_function(tar.bz2) = tar xmfj $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1)
_extract_function(txz)     = tar xmfJ $(ARCHIVE) -C $(DIR) $(call _tar_opt,$1)
_extract_function(zip)     = unzip -o -q -d $(DIR) $(call _unzip_opt,$1) $(ARCHIVE)

_ARCHIVE_EXTS := tar tar.gz tar.xz tgz tar.bz2 txz zip

#
# Function that returns the matching extraction function for a given archive
#
# Because this function refers to the 'ARCHIVE' variable, it is only supposed
# to work from the scope of the %.archive rule.
#
_extract_function = $(call _assert,\
                           $(foreach E,$(_ARCHIVE_EXTS),\
                                     $(if $(filter %.$E,$(ARCHIVE)),\
                                          $(_extract_function($E)),)),\
                           Don't know how to extract $(ARCHIVE))

%.archive: ARCHIVE = $(call _archive_name,$*)
%.archive: DIR     = $(call _archive_dir,$*)

#
# Quirk for automake
#
# If both 'Makefile.in' and 'Makefile.am' are present, and 'Makefile.am'
# happens to have the more recent timestamp (by chance, using 'tar -m'),
# a rule in the resulting 'Makefile' make will try to run automake to
# re-generate 'Makefile.in' from 'Makefile.am'. This step is brittle
# because it requires a specific automake version on the host.
#
# We rename the rule target to discharge this magic and keep using the
# provided 'Makefile.in'. A similar quirk is required for 'aclocal.m4'.
#
_DISCHARGE_PATTERN  =  /Makefile\.in:.*Makefile\.am/s/^/IGNORE-/
_DISCHARGE_PATTERN += ;/(ACLOCAL_M4):.*am__aclocal_m4_deps)/s/^/IGNORE-/
_discharge_automake = ( find $(DIR) -name "Makefile.in" |\
                        xargs -r sed -i "$(_DISCHARGE_PATTERN)" )

%.archive: %.file
	@$(MSG_EXTRACT)"$(ARCHIVE) ($*)"
	$(VERBOSE)\
		mkdir -p $(DIR);\
		$(call _extract_function,$*);\
		$(_discharge_automake)