code: https://github.com/webd90kb/webd/tree/master/codes/c_project_template
Creating a well-structured C project can greatly enhance code maintainability, readability, and reusability. This blog post will guide you through a practical example of a C project with a complete directory structure, including a functional Makefile.
Simplified Makefile for Easy Customization
Below is the essential Makefile for our C project. As you can see, it is very simple and easy to modify according to your code structure. The EXE section lists the executable files to be compiled, and the OBJ section contains the shared object files used by multiple executables.
EXE=\
exe1\
a/exe2\
a/exe3\
OBJ=\
mod1\
liba/mod2\
liba/mod3\
include inc.mak
The 'EXE' section lists the executable files that the project will compile. The backslashes (\) allow for multi-line definitions, making it easy to add or remove executables as needed.
The 'OBJ' section lists the object files that are shared across multiple executables. These object files contain common code that can be reused, reducing redundancy and simplifying maintenance.
The 'include inc.mak' line includes the inc.mak file, which contains additional configurations and generic rules for compiling and linking the project. By separating these rules into a different file, the main Makefile remains clean and focused on the specific targets and objects.
By structuring your Makefile in this way, you can easily add or remove executables and object files as your project evolves. This approach not only keeps the Makefile simple but also makes it highly adaptable to various project structures.
Detailed inc.mak
for Simplifying the Main Makefile
The inc.mak
file contains the more complex rules and settings that allow the main Makefile
to remain simple and easy to use. While it includes advanced Makefile syntax, you don't need to fully understand it to benefit from its functionality. Here's a brief overview to help you with potential customization and modifications.
ifneq ($(V), 1)
Q := @
endif
# PREFIX ?= arm-none-eabi-
CC = $(Q)$(PREFIX)gcc
SIZE = $(Q)$(PREFIX)size
LD = $(CC)
CFLAGS = -Wall -Wextra -MMD
LDFLAGS = -Wl,--gc-sections
OBJ:=$(OBJ:=.o)
ifeq ($(OS),Windows_NT)
EXE:=$(EXE:=.exe)
endif
$(EXE): $(OBJ)
all: $(EXE)
.DEFAULT_GOAL := all
ifeq ($(OS),Windows_NT)
$(EXE): %.exe: %.o
@echo $@
$(LD) $^ $(LDFLAGS) -o $@
$(SIZE) $@
else
$(EXE): %: %.o
@echo $@
$(LD) $^ $(LDFLAGS) -o $@
$(SIZE) $@
endif
%.o:%.c
@echo $<
$(CC) $(CFLAGS) -c $< -o $@
.PHONY:clean
clean:
ifeq ($(OS),Windows_NT)
@rm -fv $(EXE:.exe=) $(EXE:.exe=.o) $(EXE:.exe=.d)
else #($(OS),Windows_NT)
@rm -fv $(EXE:=.o) $(EXE:=.d)
endif #($(OS),Windows_NT)
@rm -fv $(EXE) $(OBJ) $(OBJ:.o=.d)
ifeq ($(OS),Windows_NT)
-include $(EXE:.exe=.d)
else
-include $(EXE:=.d)
endif
-include $(OBJ:.o=.d)
Key Components of inc.mak
Silent Mode Toggle:
ifneq ($(V), 1) Q := @ endif
This part allows you to toggle the verbosity of the make process. By default, it runs silently.
Compiler and Tools:
CC = $(Q)$(PREFIX)gcc SIZE = $(Q)$(PREFIX)size LD = $(CC)
Sets up the compiler (
gcc
), size utility, and linker. ThePREFIX
variable can be used to specify a toolchain prefix.Compiler and Linker Flags:
CFLAGS = -Wall -Wextra -MMD LDFLAGS = -Wl,--gc-sections
Defines the flags for compiling and linking.
-MMD
: This is a GCC-specific flag used to automatically generate.d
dependency files. These files contain dependency information thatmake
uses to determine which files need to be recompiled. By using-MMD
, you don't have to manually handle dependency tracking, which simplifies the build process and ensures that your dependencies are always up to date.Object and Executable File Naming:
OBJ:=$(OBJ:=.o) ifeq ($(OS),Windows_NT) EXE:=$(EXE:=.exe) endif
Ensures that object files have a
.o
extension and, on Windows, executables have a.exe
extension.Build Rules:
$(EXE): $(OBJ) all: $(EXE) .DEFAULT_GOAL := all ifeq ($(OS),Windows_NT) $(EXE): %.exe: %.o @echo $@ $(LD) $^ $(LDFLAGS) -o $@ $(SIZE) $@ else $(EXE): %: %.o @echo $@ $(LD) $^ $(LDFLAGS) -o $@ $(SIZE) $@ endif
Defines how to build the executables from the object files. There are specific rules for Windows to account for the
.exe
extension.Compiling Source Files:
%.o:%.c @echo $< $(CC) $(CFLAGS) -c $< -o $@
A generic rule for compiling
.c
files into.o
object files.Clean Target:
.PHONY:clean clean: ifeq ($(OS),Windows_NT) @rm -fv $(EXE:.exe=) $(EXE:.exe=.o) $(EXE:.exe=.d) else @rm -fv $(EXE:=.o) $(EXE:=.d) endif @rm -fv $(EXE) $(OBJ) $(OBJ:.o=.d)
Defines a
clean
target to remove executables and object files. It includes specific rules for Windows.Dependency Inclusion:
ifeq ($(OS),Windows_NT) -include $(EXE:.exe=.d) else -include $(EXE:=.d) endif -include $(OBJ:.o=.d)
Includes dependency files to manage build dependencies automatically.
By using inc.mak
, you can manage complex build rules and configurations separately, keeping the main Makefile
clean and straightforward. This setup allows for easy customization and adaptation to different project needs.