author |
Steve Losh <steve@stevelosh.com> |
date |
Mon, 12 Jul 2010 16:34:29 -0400 |
parents |
6bd31aa2672a |
children |
(none) |
# 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
all: # Ensure that this is the default target.
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.0a3
# 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))'
ifneq '$(call SHOULD_INSTALL_ASIS_P,$(1))' ''
@cp '$(1)' '$(2)'
else
@sed -e 's/0.0.6/$(version)/' '$(1)' >'$(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