#  -*- Makefile -*-
#
#  A U T O M A T E D  D E P E N D E N C I E S . M K
#
#  $Id: AutomatedDependencies.mk,v 0590294bcb57 2023/06/03 00:24:15 all208@g.harvard.edu $
#
#  Alexis Layton
#  Harvard University Extension Csi-E28  Spring 2022
#
#  This is an example of one way of automating the generation of C header
#  dependencies, with comments describing the mechanism.

# In order to make this work, it is necessary to extend the mechanism the
# makefile uses to compile C code. For this example it is based on the
# standard GNU make default rules and variables.
#
# Since we're using the default rules and variables, it presumes the use
# of standard make variables like CFLAGS, CPPFLAGS, etc.

CFLAGS   = -g -Wall -Wextra -std=gnu11
CPPFLAGS = $(INCLUDES) $(DEFINES)

# The individual file dependencies are stored in a subdirectory named .deps;
# this directory is destroyed and re-created by the make clean rule below.
#
DEPDIR   = .deps

# These flags are added to the compilation of each .c file to create
# the dependency information. A brief explanation for each option:
#
#    -MT $@	specifies the target of the dependency, here $@ means that
#		the normal target of the compilation
#
#    -MMD	generate the file dependencies (but not for the system
#		headers) based on the files being used for the compilation
#
#    -MP	generates a dummy target for each header file
#
#    -MF $(DEPDIR)/$*.Td
#		specifies the file to write the dependencies to; here we
#		write the dependencies for foo.c to .deps/foo.Td, which is
#		a temporary name for the dependencies as being generated.
#		This temporary name is used in case parallel makes are
#		being executed simultaneously. See POSTCOMPILE.
#
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td

# The following lists are used in some rules in the Makefile.

# List of all non-test programs to be produced
PROGS = foo bar

# List of all test programs to be produced
TESTS =

# List of C source files (tests included). In longer makefiles I keep this
# list alphabetical to avoid duplication and confusion. Note as with all
# makefile line continuations, a backslash is needed at the end of every
# line except the last.
#
SRCS =	bar.c					\
	foo.c					\
	fum.c					\
	oog.c					\
	zug.c

# List of the object files corresponding to the C source file list
OBJS = $(patsubst %.c,%.o,$(SRCS))

# Top-level rule
.DEFAULT_GOAL := all
all: $(PROGS) $(TESTS)

# To build the non-test programs
.PHONY: progs
progs: $(PROGS)

# To build and run the test programs
.PHONY: tests
tests: $(TESTS)
	@for t in $^;				\
	do					\
	    echo running $$t...;		\
	    $$t;				\
	done

# At this point in the makefile would be the rules for the individual
# C programs and then the tests:

foo: foo.o oog.o
	$(LINK.c) $^ -o $@

# If a program has more object files than will fit on a line, I will create
# a variable to hold the list:
BAR =	bar.o \
	fum.o \
	oog.o \
	zug.o
bar: $(BAR)
	$(LINK.c) $^ -o $@

# This declaration tells make to preserve the .d files in case the build
# is interrupted, and provides an empty pattern rule for them. This helps
# prevent loss of the information
.PRECIOUS: $(DEPDIR)/%.d
$(DEPDIR)/%.d: ;

# The modified clean rule here deletes the normal things, but also
# deletes and re-created an empty dependency directory.
.PHONY: clean
clean:
	$(RM) $(OBJS) $(PROGS) $(TESTS) $(LIBS) *.o
	$(RM) -r $(DEPDIR)
	mkdir -p $(DEPDIR)

# This redefines the default C compilation method used by GNU make, adding
# the use of the DEPFLAGS. The rest is the standard value of COMPILE.c
COMPILE.c   = $(CC) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

# This is the command used to rename a temporary dependency file to its
# final name after the compile command has finished.
POSTCOMPILE = mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d

# This command tells make to create the dependency directory if it is not
# present at this point in the makefile's processing.
$(shell mkdir -p $(DEPDIR) >/dev/null)

# Delete the old .c compilation rule
%.o : %.c
# Define a new .c compilation rule which depends on the particular .c files
# dependency file as well as the Makefile itself
%.o : %.c $(DEPDIR)/%.d Makefile
	$(COMPILE.c) $<
	$(POSTCOMPILE)

# Finally, include all the the dependency files for the object files listed
# into the makefile
-include $(patsubst %,$(DEPDIR)/%.d,$(basename $(SRCS)))
