Makefile Modular Compile

Dec.01.2021 | 4m Read | ^DevOps

Make & Makefiles

Make: a software build tool by Stuart Feldman in 1976 at Bell Labs (to unify and abstract common build scripts). It uses a single Makefile for your project instructions (but may load from other Makefiles). Originally it was written by & for the C language.

▼ Common Make types:

  • ☑⁡⁡ GNU Make ↗: version often found on Linux OS for the gcc compiler & GNU open source toolset.
  • ☑⁡⁡ Visual Studio nmake ↗: variant for Windows OS which is mostly compatible with GNU Make & has additional features.
  • ☑⁡⁡ Android Kati ↗: mobile OS tool that converts makefiles into faster ninja-build ninja ↗.
  • ☑⁡⁡ SCons ↗: written in the Python language for C/C++, D, Java, Fortran, Yacc, Lex, Qt,SWIG, & TeX or LaTeX docs.

Why Modular?

Refactoring or fixing-up a large code base often involves both renaming and splitting into new files. Inside the Makefile, this can become verbose & wordy as files are explicitly defined each line.

However, Make supports wildcards & modular compiling. This means you can just break your Makefile up into paths with wildcards ('*' or '%') and should apply the rules to both the headers, code, and outputted objects. You should even be able to apply this to multiple subpaths so multitudes of files will be auto-gathered for you.

With all these benefits, one wonders why this is not a common way that Makefiles are set up. In fact it is difficult to find information online. So very thin is the information, I could even find the answer after I detailed a question with on Stackoverflow.com.

Don't worry though, through trial and error (and perhaps using Stackoverflow to rubberduck or bounce ideas off) I did reach the solution.

Examples

Let's say we have 2 apps to build. App #1 is a network host (or server) that runs by Command Line Interface in a terminal. App #2 is a user (or client) that can also host or run a server, but has additional features like a Graphic User Interface with windows & buttons.

Naturally, we want to share nearly all of the code from the host and place it inside the user.

Traditional hardcoded & verbose Makefile:

HOST_OBJECTS = \
  $(HOST_BUILDPATH)/hostFile0000.o \
  $(HOST_BUILDPATH)/hostFile0001.o \
  $(HOST_BUILDPATH)/hostFile0002.o \
  $(HOST_BUILDPATH)/hostFile0003.o \
  $(HOST_BUILDPATH)/hostFile0004.o \
  $(HOST_BUILDPATH)/hostFile0005.o \
  $(HOST_BUILDPATH)/hostFile0006.o \
  ...
# On & on seemingly forever...

USER_OBJECTS = \
  $(USER_BUILDPATH)/hostFile0000.o \
  $(USER_BUILDPATH)/hostFile0001.o \
  $(USER_BUILDPATH)/hostFile0002.o \
  $(USER_BUILDPATH)/hostFile0003.o \
  $(USER_BUILDPATH)/hostFile0004.o \
  $(USER_BUILDPATH)/hostFile0005.o \
  $(USER_BUILDPATH)/hostFile0006.o \

# On & on seemingly forever + still going for the user!

  $(USER_BUILDPATH)/userFile0000.o \
  $(USER_BUILDPATH)/userFile0001.o \
  $(USER_BUILDPATH)/userFile0002.o \
  $(USER_BUILDPATH)/userFile0003.o \
  $(USER_BUILDPATH)/userFile0004.o \
  $(USER_BUILDPATH)/userFile0005.o \
  $(USER_BUILDPATH)/userFile0006.o \

########################################################################
# Rules:
########################################################################

$(HOST_BUILDPATH)/%.o: $(PATH0000)/%.s
	$(DO_HOST_CC)

$(HOST_BUILDPATH)/%.o: $(PATH0002)/%.s
	$(DO_HOST_CC)
	
$(HOST_BUILDPATH)/%.o: $(PATH0002)/%.s
	$(DO_HOST_CC)

$(HOST_BUILDPATH)/%.o: $(PATH0003)/%.s
	$(DO_HOST_CC)

# On & on seemingly forever + still going for the user rules!

$(USER_BUILDPATH)/%.o: $(PATH0000)/%.s
	$(DO_USER_CC)

$(USER_BUILDPATH)/%.o: $(PATH0001)/%.s
	$(DO_USER_CC)
	
$(USER_BUILDPATH)/%.o: $(PATH0002)/%.s
	$(DO_USER_CC)

$(USER_BUILDPATH)/%.o: $(PATH0003)/%.s
	$(DO_USER_CC)
	
# On & on seemingly forever...





















Modular paths with wildcards reduces down to this:

CORE_CODE            := $(wildcard $(PATH0000)/*/*/*.c) \
                        $(wildcard $(PATH0001)/*/*/*.c) \
                        $(wildcard $(PATH0002)/*/*.c) \
                        $(wildcard $(PATH0003)/*/*/*/*.c) \
                        $(wildcard $(PATH0004)/*/*/*.c) \
                        $(wildcard $(HOST_PATH0000)/*/*.c) 
                        
        
HOST_CODE            := $(wildcard $(HOST_PATH0001)/*/*.c) \
                         $(CORE_CODE)

HOST_OBJECTS          = $(patsubst $(MAIN_PATH)/%.c,        $(HOST_BUILDPATH)/%.o,             $(HOST_CODE))

# Or to append:
#HOST_OBJECTS         += $(patsubst $(MAIN_PATH)/%.c,        $(HOST_BUILDPATH)/%.o,             $(HOST_CODE))


USER_CODE            := $(wildcard $(USER_PATH0000)/*/*.c) \
                        $(wildcard $(USER_PATH0001)/*/*.c) \
                        $(wildcard $(USER_PATH0002)/*.c) \
                        $(wildcard $(USER_PATH0003)/*/*.c) \
                        $(wildcard $(USER_PATH0004)/*/*.c) \
                        $(wildcard $(USER_PATH0005)/*/*/*.c) \
                        $(CORE_CODE)

USER_OBJECTS          = $(patsubst $(MAIN_PATH)/%.c,        $(USER_BUILDPATH)/%.o,             $(USER_CODE))

# Or to append:
#USER_OBJECTS         += $(patsubst $(MAIN_PATH)/%.c,        $(USER_BUILDPATH)/%.o,             $(USER_CODE))

########################################################################
# Rules:
########################################################################

$(HOST_BUILDPATH)/%.o:            $(MAIN_PATH)/%.c
	$(DO_HOST_CC)

$(USER_BUILDPATH)/%.o:            $(MAIN_PATH)/%.c
	$(DO_USER_CC)

# !!! DONE !!!


More info...

00 makefile master

Feel free to read my Stackoverflow Q&A ↗.

Like this post? Read more from the ^DevOps topic.


       : NEWS