# This is EasyMake, a fully configurable, recursive multi-architecture
# makefile that autmatically respects dependencies.
# By default, it compiles and links everything in and below its own
# directory, putting all derived objects into DERIVEDDIR (defaults
# to ../derived). It does a good job in guessing the tools to be
# used from boh the source file extensions and target extension.
# While the makefile may well be used standalone using EasyMake's sane
# defaults, it allows easy but still comprehesive configuration in both
# an architecture independent (user.mk) and architecture specific 
# (<platform>.mk) files. Please refer to the supplied sample user.mk
# for details and any further documentation.
# Generally, the user.mk file will show and document an example for the
# use of every EasyMake variable. The best way to learn how each of
# these variables affect the build (besides reading the related
# explaination) is probably to start with a user.mk file that has all
# the example variables commented out, and see what a "make", "make clean"
# and "make cleanall" are doing by default. Then, you may remove the
# comments one after the other to see what changes. Together with the
# description of each EasyMake variable, this should give you a good
# idea w.r.t. each variable's purpose.
# Finally, note that you may create any intermediate derived object by
# just passing it to make as usual (e.g. "make tst_c.o"). 
#
# Please report any bugs or improvement suggestions to cwarlich@gmx.de
#
# Credits for the multi-architecture part and the dependency generation
# belong to Paul Smith (see http://mad-scientist.net/make/multi-arch.html
# and http://mad-scientist.net/make/autodep.html to grasp the ideas).
# I would never have been able to write EasyMake without his excellent
# documentation of these concepts.
#
# ToDo: This is a list of possible improvements that may become part of
# EasyMake in the future if I find the time and / or if they may be needed:
# - Extend the default compiler list to support other typical file types
#   like lex and yacc, other compilers, Tex, ...
# - Test EasyMake on Windows and with VisualC.
# - Allow a list of default platforms instead of just one.
# - Add generic postprocessing.
# - End when any submake fails.
_TMP_PREREQ:=/tmp/prerequisites.
ifeq (,$(_SRCDIR))
  ### This section is evaluated when make is called in the source directory.
  .PHONY: superall
  superall: all
  # Do not add any ONCE sections here.
  ONCE:=true
  # Claculate the default name of the target:
  TARGET:=$(if $(notdir $(CURDIR)),$(notdir $(CURDIR)),application)
  DERIVEDDIR:=../derived
  -include user.mk
  ifneq (,$(PLATFORM))
    include $(PLATFORM).mk
  endif
  ifneq (,$(PLATFORM))
    _DERIVEDDIR:=$(DERIVEDDIR)/$(PLATFORM)
  else
    _DERIVEDDIR:=$(DERIVEDDIR)
  endif
  .SUFFIXES:
  PID:=$(shell echo $$$$)
  # Define the rules to build in the target subdirectories.
  .PHONY: all
  all: $(_DERIVEDDIR)
	@rm -f $(_TMP_PREREQ)$(PID)
	@if [ "$(MAKELEVEL)" != "0" -a -n "$(TARGET)" -a -n "$(DERIVEDDIR)" ]; then\
	  cd $(_DERIVEDDIR);\
	  for i in "$(TARGET)"; do\
	    echo `pwd`/$i >> $(_TMP_PREREQ)$(PPID);\
	  done;\
	fi
	@for i in $(PREREQUISITES); do $(MAKE) PLATFORM=$(PLATFORM) PPID=$(PID) -C $$i $(MAKECMDGOALS); done
	@if [ -n "$(TARGET)" -a -n "$(DERIVEDDIR)" ]; then\
	  $(MAKE) --no-print-dir -C $(_DERIVEDDIR) -f $(CURDIR)/Makefile\
	  _SRCDIR=$(CURDIR) TARGET="$(TARGET)" PID=$(PID) $(MAKECMDGOALS);\
	fi
	@rm -f $(_TMP_PREREQ)$(PID)
  ifneq (,$(DERIVEDDIR))
    $(_DERIVEDDIR): ; @mkdir -p $@
  else
    $(_DERIVEDDIR): ;
  endif
  # These rules keep make from trying to use the match-anything rule below to
  # rebuild the makefiles--ouch!  Obviously, if you don't follow my convention
  # of using a `.mk' suffix on all non-standard makefiles you'll need to change
  # the pattern rule.
  Makefile : ;
  %.mk :: ;
  # Anything we don't know how to build will use this rule.  The command is a
  # do-nothing command, but the prerequisites ensure that the appropriate
  # recursive invocations of make will occur.
  % :: all ;
  # The clean rule is best handled from the source directory: since we're
  # rigorous about keeping the target directories containing only target files
  # and the source directory containing only source files, `clean' is as trivial
  # as removing the target directories!
  .PHONY: _SELF
  .PHONY: _SELFALL
  ifneq (,$(DERIVEDDIR))
    _SELF:
	rm -rf $(_DERIVEDDIR)
    _SELFALL:
	rm -rf $(DERIVEDDIR)
  else
    _SELF: ;
    _SELFALL: ;
  endif
  .PHONY: clean
  clean: _SELF
	@for i in $(PREREQUISITES); do $(MAKE) PLATFORM=$(PLATFORM) -C $$i $(MAKECMDGOALS); done
  .PHONY: cleanall
  cleanall: _SELFALL
	@for i in $(PREREQUISITES); do $(MAKE) PLATFORM=$(PLATFORM) -C $$i $(MAKECMDGOALS); done
else
  ### This section is for the architecture-specific target directory.
  .PHONY: superall
  superall: all
  ## Definitions that are independent from the user configuration:
  VPATH:=$(_SRCDIR)
  # Dependency calculation.
  define _DEPPOSTPROC
	@if [ -e "$(1).d" ]; then\
	  cp $(1).d $(1).tmp &&\
	  sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$$$//' \
              -e '/^$$$$/ d' -e 's/$$$$/ :/' < $(1).tmp >>$(1).d &&\
	  rm -f $(1).tmp;\
	fi
    -include $(1).d
  endef
  # Rule for compilation. Parameter: source-file source-extension derived-extension compile-command
  define _COMPILE
    $(1:$(2)=$(3)): $(1) | $(abspath $(dir $(1)))
	$(4)
	$(call _DEPPOSTPROC,$(basename $(1:$(2)=$(3))))
  endef
  # Reverse words in list.
  _REVERSE = $(if $(1),$(call _REVERSE,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1))
  _ABSDERIVEDDIR:=$(CURDIR)
  _SPACE:= 
  _SPACE+=
  # Avoids incomplete or corrupt targets. Every makefile should contain this line!
  .DELETE_ON_ERROR:
  ## Load user configuration to start the user configuration dependent part:
  -include $(_SRCDIR)/user.mk
  ifneq (,$(PLATFORM))
    include $(_SRCDIR)/$(PLATFORM).mk
  endif
  # Allows to add things that must only be included once, e.g. rules.
  -include once.mk
  # Don't include any ONCE sections again.
  ONCE:=true
  # Now we know TARGET ...
  .PHONY: all
  all :: $(TARGET)
  # and PLATFORM ...
  ifneq (,$(PLATFORM))
    _ABSDERIVEDDIR:=$(dir $(_ABSDERIVEDDIR))
  endif
  # and DEFINES ...
  _DEFINES:=$(addprefix -D,$(DEFINES))
  # and INCLUDES ...
  $(foreach i,$(INCLUDES),$(eval $(if $(filter /%,$(i)),_INCLUDES+=-I$(i),_INCLUDES+=-I$(_SRCDIR)/$(i))))
  # and anything to set up the default compiler list.
  COMPILERS:=$(CC) $(CFLAGS) $(_DEFINES) $(_INCLUDES) -MD -c -o $$@ $$< §.c §_c.o;\
             $(CXX) $(CXXFLAGS) $(_DEFINES) $(_INCLUDES) -MD -c -o $$@ $$< §.cpp §_cpp.o;
  # Paranoia: Escape any hash just in case I put one (which must already
  # be escaped there nevertheless!) into the default compiler list.
  COMPILERS:=$(subst \#,\\\#,$(COMPILERS))
  # We include the .mk files again to allow modification of the COMPILERS
  # variable. But note that the other variables that may be used in any
  # of the .mk files may be corrupt without reinitializing them before!
  -include $(_SRCDIR)/user.mk
  ifneq (,$(PLATFORM))
    include $(_SRCDIR)/$(PLATFORM).mk
  endif
  # Again escape any hash that may e.g. be used in regular expressions or sed scripts.
  COMPILERS:=$(subst \#,\\\#,$(COMPILERS))
  _COMPILERLIST:=$(subst $(_SPACE),?,$(COMPILERS))
  _COMPILERLIST:=$(subst ;,$(_SPACE),$(_COMPILERLIST))
  # Create SOURCES<extension> variables.
  $(foreach i,$(_COMPILERLIST),\
    $(eval _TMP:=$(subst §, ,$(i)))\
    $(eval _SRCEXT:=$(strip $(subst ?,$(_SPACE),$(word 2,$(_TMP)))))\
    $(eval SOURCES$(_SRCEXT):=$(subst $(_SRCDIR)/,,\
                                      $(shell find $(_SRCDIR)\
				                   -path $(abspath $(_ABSDERIVEDDIR)) -prune\
                                                   -o -name "*$(_SRCEXT)" -print))\
     )\
    $(eval _EXTENSIONS+=$(_SRCEXT))\
   )
  # Include again to allow modigication of the SOURCES variables.
  -include $(_SRCDIR)/user.mk
  ifneq (,$(PLATFORM))
    include $(_SRCDIR)/$(PLATFORM).mk
  endif
  # Sort according to longest matching extension.
  ifneq (,$(SORTED))
    _EXTENSIONS:=$(shell (for i in $(_EXTENSIONS); do echo $$i | rev; done) | sort | rev)
  endif
  # Remove all files that are in more than one list:
  _EXTENSIONS:=$(call _REVERSE,$(_EXTENSIONS))
  $(foreach i,$(_EXTENSIONS),\
    $(eval _DONE+=$(i))\
    $(foreach j,$(filter-out $(_DONE),$(_EXTENSIONS)),\
      $(eval SOURCES$(j):=$(if $(DUPLICATES),$(SOURCES$(j)),$(filter-out $(SOURCES$(i)),$(SOURCES$(j)))))\
     )\
   )
  # Sort compiler list according to _EXTENSIONS. This step is only needed
  # to guarantee the right order of the rules when both SORTED and DUPLICATES
  # are not empty _and_ warnings about overwriten rules are issued by make.
  _NCL:=
  _COMPILERLIST:=$(subst $$,$$$$,$(_COMPILERLIST))
  $(foreach i,$(_EXTENSIONS),\
    $(foreach j,$(_COMPILERLIST),\
      $(eval _TMP:=$(subst §, ,$(j)))\
      $(eval _SRCEXT:=$(strip $(subst ?,$(_SPACE),$(word 2,$(_TMP)))))\
      $(eval $(if $(filter xx,x$(subst $(i),,$(_SRCEXT))$(subst $(_SRCEXT),,$(i))x),_NCL+=$(j)))\
     )\
   )
  _COMPILERLIST:=$(call _REVERSE,$(subst \#,\\\#,$(_NCL)))
  # Create list of objects.
  OBJECTS:=
  $(foreach i,$(_COMPILERLIST),\
    $(eval _TMP:=$(subst §, ,$(i)))\
    $(eval _SRCEXT:=$(strip $(subst ?,$(_SPACE),$(word 2,$(_TMP)))))\
    $(eval _TGTEXT:=$(strip $(subst ?,$(_SPACE),$(word 3,$(_TMP)))))\
    $(eval _SOURCES$(_SRCEXT):=$(SOURCES$(_SRCEXT)))\
    $(eval OBJECTS+=$(_SOURCES$(_SRCEXT):$(_SRCEXT)=$(_TGTEXT)))\
   )
  # Create rules to make objects from sources.
  $(foreach i,$(_COMPILERLIST),\
    $(eval _TMP:=$(subst §, ,$(i)))\
    $(eval _SRCEXT:=$(strip $(subst ?,$(_SPACE),$(word 2,$(_TMP)))))\
    $(eval _TGTEXT:=$(strip $(subst ?,$(_SPACE),$(word 3,$(_TMP)))))\
    $(foreach j,$(_SOURCES$(_SRCEXT)),\
              $(eval $(call _COMPILE,$(j),$(_SRCEXT),$(_TGTEXT),\
	                    $(strip $(subst ?, ,$(word 1,$(subst §, ,$(i)))))\
                      )\
               )\
     )\
   )
  # Needed below and maybe in any of the .mk files.
  COMMA:=,
  # Deduce Linker from the target extension.
  _SOFLAGS:=
  ifeq ($(suffix $(TARGET)),.a)
    LINKER:=$(AR) $(ARFLAGS)
  else
    ifeq ($(suffix $(TARGET)),.so)
      _SOFLAGS:=-shared -fPIC
    endif
    # Try to guess the linker from the sources (only C or including C++ code):
    ifeq (,$(SOURCES.cpp))
      LINKER:=$(CC) $(_SOFLAGS) $(LDFLAGS) $(addprefix -Wl$(COMMA),$(LINKERFLAGS)) $(addprefix -l,$(LIBRARIES)) $(addprefix -L,$(LIBDIRS)) -o
    else
      LINKER:=$(CXX) $(_SOFLAGS) $(LDFLAGS) $(addprefix -Wl$(COMMA),$(LINKERFLAGS)) $(addprefix -l,$(LIBRARIES)) $(addprefix -L,$(LIBDIRS)) -o
    endif
  endif
  # Retrieve the prerequisites:
  _TARGETS:=$(shell if [ -e $(_TMP_PREREQ)$(PID) ]; then cat $(_TMP_PREREQ)$(PID); fi) 
  # Include again to modify OBJECTS and / or TARGETS.
  -include $(_SRCDIR)/user.mk
  ifneq (,$(PLATFORM))
    include $(_SRCDIR)/$(PLATFORM).mk
  endif
  # Rules to create the target:
  ifneq (,$(LINKER))
    $(TARGET): $(OBJECTS) ; $(LINKER) $@ $^
  endif
  $(foreach i,$(_TARGETS),$(eval all :: $(i)))
  # Rules to create the directories for the derived objects.
  _OBJDIRS:=$(abspath $(filter-out ./,$(sort $(dir $(OBJECTS)))))
  $(foreach i,$(_OBJDIRS),$(eval $(i): ; @mkdir -p $$@))
  # Remake eveything if Makefile, user.mk or the related <platform>.mk changes.
  $(OBJECTS): $(_SRCDIR)/Makefile $(if $(PLATFORM),$(_SRCDIR)/$(PLATFORM).mk) $(wildcard $(_SRCDIR)/user.mk)
endif
