The make (e.g. the GNU implementation) system is a very widespread of managing builds of compiled programs and some other data processing (e.g. builds of documentation from restructured text/markdown, sometimes builds of docker containers etc).

In typical usage make detects that a file input to the build has changed (by observing its timestamp relative to the output) and uses this to decide that a rebuild of this part of the system is needed. But in this usage make will not detect changes in the way the output is built, e.g., a change in the optimisation flags to the compiler, or change in the architecture, etc.

Here is a simple technique suitable for smaller projects that will detect changes in build flags (e.g., a change in optimisation level, change in CPU that is targeted etc). It can be simply extended to detect change in the compiler version and most other typical changes. For large projects I recommend a more fully featured build systems.

Principle of operation

The idea is simple:

  1. Construct a string containing the contents of all relevant variables; in case below all variables that end with FLAGS but can include the value of say CC F77 etc

  2. Compute the hash of this string using external program sha256sum

  3. All objects (i.e. compilation outputs) to be build automatically have this has appended to their file (base) name

  4. If flags change there the hash will change, and in turn, the dependencies will be rebuilt correctly

Here is an implementation which builds a very simple program:

CFLAGS= -O1

srcfiles:=t1.c

allflagsc:=$(foreach v,$(filter %FLAGS,$(.VARIABLES)),$($(v)))
flaghash:=$(strip $(shell echo $(allflagsc) | sha256sum | cut -d " " -f1 ) )

objfiles := $(foreach f,$(srcfiles), $(basename $f)-$(flaghash).o)

%-$(flaghash).o: %.c
	gcc $(CFLAGS)  -c $< -o $@

app: $(objfiles)
	gcc -o app $^

Some notes on how this works:

  1. The special variable .VARIABLES holds the names of all Makefile variables. We filter on this ($(filter %FLAGS,...)) to select all variables which end with “FLAGS” and then construct a string of their values.

  2. This string is hashed using the external program sha256sum ($(shell ...))

  3. The names of object files are appending the hash to the name of their source files

  4. The implicit pattern %-$(flaghash).o: %.c separates out the hash part from the stem of the object name to be able to specify the source files as dependencies