# Makefile for C project with auto-dependencies and multiple build options
# Copyright (C) 2000 Marc Mongenet

###############################################################################
# USER DEFINED VARIABLES
# The following variable values must be suited to the build environment.
###############################################################################

# name of the file to build
EXE = mf

# source files suffix (all source files must have the same suffix)
SOURCE_SUFFIX = c

# C compiler
CC = gcc

# source files directory
srcdir = .

# build directory
builddir = .

# preprocessor options to find all included files (no value if unsure)
INC_PATH = -I$(srcdir)

# libraries link options (no value if unsure)
LNK_LIBS = -lm

# other compilation options (no value if unsure)
COMPILE_OPTS =

# basic compiler warning options (for GOAL_EXE)
BWARN_OPTS = -Wall -Wno-uninitialized -ansi -pedantic

# extented compiler warning options (for GOAL_DEBUG)
EWARN_OPTS = $(BWARN_OPTS)\
             -Wchar-subscripts -Wcomment -Wimplicit -Wreturn-type -Wswitch\
             -Wformat -Wreorder -Wtraditional -Wpointer-arith\
             -Wbad-function-cast -Wcast-qual -Wcast-align\
             -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes\
             -Wredundant-decls -W -Winline

###############################################################################
# INTERNAL VARIABLES
# The following variable values should not depend on the build environment.
# You may safely stop the configuration at this line.
###############################################################################

# You may freely change the following goal names if you dislike them.
GOAL_DEBUG = debug
GOAL_PROF = prof
GOAL_EXE = all

# build options for GOAL_DEBUG (executable for debugging) goal
ifeq "$(MAKECMDGOALS)" "$(GOAL_DEBUG)"

 # specific options for debugging
 GOAL_OPTS = -g
 # compilation verification options
 WARN_OPTS = $(EWARN_OPTS)
 # optimization options
 OPTIMISE_OPTS =
 # dependencies must be up to date
 CHECK_DEPS = yes

else

 # build options for GOAL_PROF (executable for profiling) goal
 ifeq "$(MAKECMDGOALS)" "$(GOAL_PROF)"

  # specific options for profiling
  GOAL_OPTS = -pg
  # compilation verification options
  WARN_OPTS = $(BWARN_OPTS)
  # optimization options
  OPTIMISE_OPTS = -O2
  # dependencies must be up to date
  CHECK_DEPS = yes

 else

  # build options for GOAL_EXE (optimized executable) goal
  ifeq "$(MAKECMDGOALS)" "$(GOAL_EXE)"

   # specific options for optimized executable
   GOAL_OPTS = -s
   # compilation verification options
   WARN_OPTS = $(BWARN_OPTS)
   # optimization options
   OPTIMISE_OPTS = -O3 -fomit-frame-pointer
   # dependencies must be up to date
   CHECK_DEPS = yes

  else

   # Other goals do not require up to date dependencies.
   CHECK_DEPS = no

  endif
 endif
endif

# preprocessor options
CPPOPTS = $(INC_PATH)

# compiler options
COPTS = $(GOAL_OPTS) $(COMPILE_OPTS) $(WARN_OPTS) $(OPTIMISE_OPTS)

# linker options
LDOPTS = $(GOAL_OPTS) $(LNK_LIBS)

# source files in this project
sources := $(wildcard $(srcdir)/*.$(SOURCE_SUFFIX))

# object files in this project
objs := $(notdir $(sources))
objs := $(addprefix $(builddir)/, $(objs))
objs := $(objs:.$(SOURCE_SUFFIX)=.o)

# executable with full path
exe = $(builddir)/$(EXE)

# This makefile creates and includes makefiles containing actual dependencies.
# For every source file a dependencies makefile is created and included.
# The deps variable contains the list of all dependencies makefiles.
deps_suffix = d
deps := $(objs:.o=.$(deps_suffix))

# To detect goal changes (for instance from GOAL_DEBUG to GOAL_EXE)
# between invocations, this makefile creates an empty file (the goal flag
# file) which suffix is the goal name.
goal_flag_file_prefix = $(builddir)/Last_make_goal_was_
goal_flag_file = $(goal_flag_file_prefix)$(MAKECMDGOALS)

###############################################################################
# TARGETS
###############################################################################

# Delete the target file of a rule if the command used to update it failed.
# Do that because the newly generated target file may be corrupted but appear
# up to date.
.DELETE_ON_ERROR:

# Clear default suffix list to disable all implicit rules.
.SUFFIXES:

# usage message for this makefile
.PHONY:	usage
usage:
	@echo "GOAL	EFFECT"
	@echo "----	------"
	@echo "usage	print this message"
	@echo "list	list the source files"
	@echo "$(GOAL_EXE)	create the goal file"
	@echo "$(GOAL_DEBUG)	create the goal file with debug options"
	@echo "$(GOAL_PROF)	create the goal file with profiling options"
	@echo "clean	remove all files that are normally created by\
	       building the program"

# If source files exist then build the EXE file.
.PHONY:	$(GOAL_EXE)
ifneq "$(strip $(sources))" ""
$(GOAL_EXE):	$(exe)
else
$(GOAL_EXE):
	@echo "No source file found."
endif

# GOAL_DEBUG and GOAL_PROF targets use the same rules than GOAL_EXE.
.PHONY:	$(GOAL_DEBUG)
$(GOAL_DEBUG):	$(GOAL_EXE)

.PHONY:	$(GOAL_PROF)
$(GOAL_PROF):	$(GOAL_EXE)

###############################################################################
# BUILDING
# Note: CPPFLAGS, CFLAGS or LDFLAGS are not used but may be specified by the
# user at make invocation.
###############################################################################

# linking
$(exe):	$(objs)
	$(CC) $^ -o $@ $(LDOPTS) $(LDFLAGS)

# explicit definition of the implicit rule used to compile source files
$(builddir)/%.o:	$(srcdir)/%.$(SOURCE_SUFFIX)
	$(CC) -c $< $(CPPOPTS) $(COPTS) $(CPPFLAGS) $(CFLAGS) -o $@

# Rule to build our included dependencies makefiles.
# This rule is used by GNU Make because it automatically tries to (re)build
# obsolete or non-existent included makefiles.
# These files are created with one line of the form:
# 1.o 1.d: $(goal_flag_file) 1.cc 1.h 2.h 3.h g.h
# The implicit rule previously defined will be used for compilation.
# Note that the dependencies can be goal specific.
# The goal_flag_file is determined at run time because it must be the current
# goal and not the goal in use when the dependencies makefile was created.
$(builddir)/%.$(deps_suffix):	$(srcdir)/%.$(SOURCE_SUFFIX) $(goal_flag_file)
	$(SHELL) -ec '$(CC) -MM $(CPPOPTS) $(CPPFLAGS) $< |\
	sed '\''s@\($*\)\.o[ :]*@$(builddir)/\1.o $@: $$(goal_flag_file) @g'\'' > $@;\
	[ -s $@ ] || rm -f $@'

# If dependencies have to be up to date then include dependencies makefiles.
ifeq "$(CHECK_DEPS)" "yes"
 ifneq "$(strip $(sources))" ""
  include $(deps)
 endif
endif

# Rule to produce the goal flag file.
# If the goal has changed then we must rebuild on a clean state because
# pre-processor DEFINE's may have changed.
$(goal_flag_file):
	rm -f $(exe) $(goal_flag_file_prefix)* $(objs) $(deps)
	touch $@

###############################################################################
# NON-BUILD TARGETS
###############################################################################

# List the source files
.PHONY:	list
list:
	@ls $(sources)

# Remove all files that are normally created by building the program.
.PHONY:	clean
clean:
	rm -f $(exe) $(goal_flag_file_prefix)* $(objs) $(deps)
