Top-Level Makefile

Read time: 4 minutes (1115 words)

The major components of this make system are all included in the top-level project Makefile. That keeps this file extremely simple. All it has in it is a few definitions to extract the project name from the current directory, then it includes a component that detects what platform you are using.

Here is the Makefile for this test project:

Makefile
1
2
3
4
5
6
# Modular Make - top level makefile
PROJPATH = $(PWD)
PROJNAME = $(notdir $(PROJPATH))

include $(wildcard mk/*.mk)
TARGET := $(PROJNAME)$(EXT)

Detecting the OS

In order to make this system work on any platform, I separated components of the system into subdirectories so that components specific to each platform get loaded when make runs. Here is the component that figures out what system you are using:

mk/os_detect.mk
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ifeq ($(OS), Windows_NT)
	EXT = .exe
	PREFIX =
	RM	= del
	WHICH := where
	PLATFORM := Windows
	PROJPATH := $(CURDIR)
	include $(wildcard mk/pc/*.mk)
else
	EXT =
	RM 	= rm -f
	PREFIX := ./
	WHICH := which
	UNAME_S := $(shell uname -s)
	ifeq ($(UNAME_S), Linux)
		PLATFORM := Linux
		include $(wildcard mk/linux/*.mk)
	endif
	ifeq ($(UNAME_S), Darwin)
		PLATFORM := Mac
		include $(wildcard mk/mac/*.mk)
	endif
endif

Debugging the System

To assist in debugging the system, I found a neat way to print out all the user defined variables in the Makefile. Here is that code:

mk/debug.mk
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.DEFAULT_GOAL	:= all
print-%:
	@echo $* = $($*)

.PHONY:	debug
debug: ## display local make variables defined
	@$(foreach V, $(sort $(.VARIABLES)), \
		$(if $(filter-out environment% default automatic,\
			$(origin $V)), \
			$(warning $V = $($V) )) \
	)

.PHONY:	debug-all
debug-all: ## display all make variables defined
	@$(foreach V, $(sort $(.VARIABLES)), \
		$(warning $V = $($V) ) \
	)

Help System

Finally, there is a simple help system that relies on a short Python Script. Basically, on each make target line, after the dependencies, you add a comment that begins with two hash marks, then continues to the end of the line. The Python script scans all the defined Makefile components looking for these markers, then produces a table showing what is available.

help.mk
1
2
3
4
5
# Makefile help system

.PHONY: help
help:	## display help messages
	@python mk/pyhelp.py$(MAKEFILE_LIST)

And here is the heper Python scrips

mk/pytest.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import sys
import re

def main():
    help_re = re.compile(r"^([a-zA-Zi_-]*:).*?##(.*)$")

    modules = sys.argv
    del modules[0]
    for m in modules:
        fin = open(m,'r')
        lines = fin.readlines()
        for l in lines:
            m = help_re.match(l)
            if m:
                item = m.group(1).strip()
                defn = m.group(2).strip()
                print("%-20s %s" %(item,defn))

main()