# mduem - Little utility for little software development
#
# This is a library Makefile to maintain software development, especially for
# Vim script.  Note that this Makefile requires GNU make to use.
# Coding Rules  #{{{1
#
# - Use non-empty string as true and empty string as false.
#
#
# Naming Rules:
#
# - Use UPPER_CASE variables to be configured by user.
#
# - Use lower_case variables for internal use of mduem.
#
# - Use suffix "_p" to indicate that a boolean value is resulted from
#   a variable.
#   Example: SHOULD_INSTALL_ASIS_P
#
# - Use noun for ordinary variables.
#   Example: repos_name, TARGETS_GENERATED
#
# - Use verb for variables as functions.
#   Example: resolve_dep_uri, RENAME_TARGET
#
# - Use prefix "generate_rule_" for variables to generate make rules.
#   Example: generate_rule_to_install_a_target
#
# - Use abbreviations for words which names are too long to code.
#   Example: dependency => dep, directory => dir, repository => repos
#
# - Use lower-case names for phony targets.
#
# - Use verb for phony targets.
#   Example: clean, install, pack, ...
#
# - Use hyphens to join words in names of phony targets.
#   Example: clean-junks, fetch-deps
#
# - Use prefix "," for names of files which are automatically generated by
#   mduem and they are temporary ones.
#   Example: test/,good-case.output
#
# - Use directory ".mduem" to contain stuffs for internal use.
#   Example: .mduem/cache/
#
# - All rules may be violated if there is a strong custom from old times.
#   Example: all (phony target)








# Common  #{{{1

# Use "all" as the default target if no targets were specified on the command
# line or any other rules were defined before including this Makefile.
.DEFAULT_GOAL := all
all:

SHELL := /bin/bash
this_makefile := $(lastword $(MAKEFILE_LIST))
cache_makefile := .mduem/cache/Makefile.variables
user_makefiles := $(filter-out \
                    $(this_makefile) $(cache_makefile), \
                    $(MAKEFILE_LIST))

not = $(if $(1),,t)
toplevel_dir := $(shell git rev-parse --show-toplevel 2>/dev/null)
inner_dir := $(shell git rev-parse --show-prefix 2>/dev/null)
git_controlled_p := $(toplevel_dir)
toplevel_dir_p := $(and $(git_controlled_p),$(call not,$(inner_dir)))

ifneq '$(git_controlled_p)' ''
$(cache_makefile): \
		$(toplevel_dir)/.git/config \
		$(toplevel_dir)/.git/index \
		$(this_makefile)
	@echo 'GENERATE $@'
	@mkdir -p '$(dir $@)'
	@{ \
	   current_branch="$$(git symbolic-ref -q HEAD \
	                      | sed -e 's|^refs/heads/||')"; \
	   _origin_name="$$(git config "branch.$$current_branch.remote")"; \
	   origin_name="$${_origin_name:-origin}"; \
	   _origin_uri="$$(git config "remote.$$origin_name.url")"; \
	   origin_uri="$${_origin_uri:-../.}"; \
	   \
	   echo "all_files_in_repos := \
	         $(filter-out .gitmodules \
	                      $(shell cd $(toplevel_dir) && \
	                              git submodule foreach 'echo "$$path"'),\
	           $(shell git ls-files))"; \
	   echo "current_branch := $${current_branch}"; \
	   echo "origin_name := $${origin_name}"; \
	   echo "origin_uri := $${origin_uri}"; \
	   echo 'repos_name := $(notdir $(shell pwd))'; \
	   echo 'version := $(shell git describe --tags --always --dirty)'; \
	 } >'$@'
endif
include $(cache_makefile)

	# The type of a repository.  It must be one of the following values:
	#
	# generic       For any software.
	# vim-script    For Vim plugins, etc.
REPOS_TYPE ?= $(if $(filter vim-%,$(repos_name)),vim-script,generic)
vim_script_repos_p := $(filter vim-script,$(REPOS_TYPE))








# all  #{{{1

.PHONY: all
all: build








# build  #{{{1

TARGETS_ARCHIVED ?= $(all_files_in_repos)
TARGETS_GENERATED ?=# Empty
TARGETS_STATIC ?=# Empty

targets_all_installed := $(TARGETS_GENERATED) $(TARGETS_STATIC)
targets_all_archived := $(sort \
                          $(TARGETS_ARCHIVED) \
                          $(targets_all_installed) \
                          $(cache_makefile) \
                          )




.PHONY: build
build: $(targets_all_installed)








# clean  #{{{1

.PHONY: clean
clean: clean-generated clean-junks

.PHONY: clean-generated
clean-generated:
	@echo 'CLEAN-GENERATED'
	@rm -rf $(TARGETS_GENERATED)
	@find -name '.mduem' | xargs rm -rf

.PHONY: clean-junks
clean-junks:
	@echo 'CLEAN-JUNKS'
	@find -name '*~' -or -name ',*' | xargs rm -rf








# fetch-deps  #{{{1

DEPS ?=# Empty
vim_script_deps := $(if $(vim_script_repos_p),vim-vspec vimup,)
all_deps := $(vim_script_deps) $(DEPS)

DEP_vim_vspec_URI ?= ../vim-vspec
DEP_vim_vspec_VERSION ?= 0.0.3

DEP_vimup_URI ?= ../vimup
DEP_vimup_VERSION ?= 0.0.1


	# BUGS: This resolves "../" just once, but it's enough for usual cases.
resolve_dep_uri = $(strip $(if $(filter ../%,$(1)), \
                            $(dir $(origin_uri))$(1:../%=%), \
                            $(1)))
normalize_dep_name = $(subst -,_,$(1))
get_dep_raw_uri = $(DEP_$(call normalize_dep_name,$(1))_URI)
get_dep_dir_name = $(patsubst %.git,%,$(notdir $(call get_dep_uri,$(1))))

get_dep_uri = $(call resolve_dep_uri,$(call get_dep_raw_uri,$(1)))
get_dep_version = $(DEP_$(call normalize_dep_name,$(1))_VERSION)
get_dep_dir = .mduem/deps/$(call get_dep_dir_name,$(1))




.PHONY: fetch-deps
fetch-deps: $(all_deps:%=.mduem/deps/,%)

# FIXME: Update for changes on only DEPS and other values.
.mduem/deps/,%: $(user_makefiles)
	@echo 'FETCH-DEP $*'
	@mkdir -p '$(dir $@)'
	@   ( \
	         if [ -d '$(call get_dep_dir,$*)' ] \
	      ;  then \
	           cd './$(call get_dep_dir,$*)' \
	      &&   git fetch \
	      &&   git checkout -f mduem-master \
	      ;  else \
	           git clone '$(call get_dep_uri,$*)' '$(call get_dep_dir,$*)'\
	      &&   cd './$(call get_dep_dir,$*)' \
	      &&   git checkout -b mduem-master \
	      ;  fi \
	      && git reset --hard '$(call get_dep_version,$*)' \
	 ;  ) &>'$@.log' \
	 || { cat '$@.log'; false; }
	@touch '$@'








# install  #{{{1
# Core  #{{{2

INSTALLATION_DIR ?= $(error Please set INSTALLATION_DIR)

RENAME_TARGET ?= $(patsubst %,$(INSTALLATION_DIR)/%,$(1))
SHOULD_INSTALL_ASIS_P ?=# All files are version-filtered by default.




.PHONY: install
install: build


define generate_rule_to_install_a_target  # (build_target, install_target)
install: $(2)
$(2): $(1)
	@echo 'INSTALL $(1)'
	@mkdir -p '$(dir $(2))'
	@cp '--preserve=mode,ownership' '$(1)' '$(2)'
ifeq '$(call SHOULD_INSTALL_ASIS_P,$(1))' ''
	@sed -i -e 's/0.2.10/$(version)/' '$(2)'
endif

endef
$(eval \
  $(foreach t, \
    $(targets_all_installed), \
    $(call generate_rule_to_install_a_target,$(t),$(call RENAME_TARGET,$(t)))))


# This should be placed at the last to ensure that post-install is executed
# after any other rules to install.
install: post-install




# post-install  #{{{2

TARGETS_POST_INSTALL ?=# Empty
targets_post_install_builtin :=# Empty


ifneq '$(vim_script_repos_p)' ''
target_vim_helptags := $(call RENAME_TARGET,doc/tags)
$(target_vim_helptags): $(filter doc/%.txt,$(targets_all_installed))
	@echo 'POST-INSTALL vim helptags'
	@vim -n -N -u NONE -U NONE -e -c 'helptags $(dir $@) | qall!'

targets_post_install_builtin += $(target_vim_helptags)
endif


.PHONY: post-install
post-install: $(targets_post_install_builtin) $(TARGETS_POST_INSTALL)








# pack  #{{{1

archive_basename = $(repos_name)-$(version)
archive_name = $(archive_basename).zip


.PHONY: pack
pack: $(archive_name)

$(archive_name): $(cache_makefile)
	rm -rf '$(archive_basename)' '$(archive_name)'
	$(MAKE) \
	  'INSTALLATION_DIR=$(archive_basename)' \
	  'targets_all_installed=$(targets_all_archived)' \
	  install
	zip -r $(archive_name) $(archive_basename)/
	rm -rf '$(archive_basename)'








# release  #{{{1

.PHONY: release
release: $(if $(vim_script_repos_p),release-vim-script,release-default)


.PHONY: release-default
release-default:
	@echo 'Rules to release are not defined.'


.PHONY: release-vim-script
release-vim-script: fetch-deps $(repos_name).vimup pack
	./.mduem/deps/vimup/vimup update-script $(repos_name)
	rm $(repos_name).vimup

.PHONY: release-new-vim-script
release-new-vim-script: fetch-deps $(repos_name).vimup pack
	./.mduem/deps/vimup/vimup new-script $(repos_name)
	rm $(repos_name).vimup

$(repos_name).vimup: $(firstword $(sort $(filter doc/%.txt, \
                                                 $(all_files_in_repos))))
	./.mduem/deps/vimup/vimup-info-generator \
	  <$< \
	  >$(repos_name).vimup








# test  #{{{1

test_cases := $(patsubst test/%.expected,%, \
                $(filter test/%.expected,$(all_files_in_repos)))


default_test_rule_deps := $(MAKEFILE_LIST)
define default_test_rule
	source './$<' &>'$@' || { cat '$@'; false; }
endef

ifneq '$(vim_script_repos_p)' ''
all_vim_scripts := $(filter %.vim,$(all_files_in_repos))
vim_script_test_rule_deps := .mduem/deps/vim-vspec/bin/vspec $(all_vim_scripts)
define vim_script_test_rule
	./$(call get_dep_dir,vim-vspec)/bin/vspec \
	  $< \
	  "$$PWD" \
	  $(foreach d,$(all_deps),$(call get_dep_dir,$(d))) \
	  &>$@
endef
endif

TEST_RULE ?= $(if $(vim_script_repos_p), \
               $(vim_script_test_rule), \
               $(default_test_rule))
TEST_RULE_DEPS ?=# Empty

builtin_test_rule_deps := $(if $(vim_script_repos_p), \
                            $(vim_script_test_rule_deps), \
                            $(default_test_rule_deps))
all_test_rule_deps := $(builtin_test_rule_deps) $(TEST_RULE_DEPS)




.PHONY: test
test: fetch-deps test/,ok

test/,ok: $(test_cases:%=test/,%.ok)
	@echo 'ALL TESTS ARE PASSED.'
	@touch $@

test/,%.ok: test/%.input $(all_test_rule_deps)
	@echo -n 'TEST $* ... '
	@$(MAKE) --silent '$(@:.ok=.diff)'
	@if ! [ -s $(@:.ok=.diff) ]; then \
	   echo 'OK'; \
	 else \
	   echo 'FAILED'; \
	   cat $(@:.ok=.diff); \
	   echo 'END'; \
	   false; \
	 fi
	@touch $@

test/,%.diff: test/%.expected test/,%.output
	@diff -u $^ >$@; true

test/,%.output: test/%.input $(all_test_rule_deps)
	@$(TEST_RULE)








# __END__  #{{{1
# vim: foldmethod=marker
