Creating a C program "from the source tree" using GNU make

I would like to create a C project for my microcontroller using the GNU make tool. I would like to do this in a clean way so that the source code is not cluttered with object files and other materials after assembly. So, imagine that I have a project folder called "myProject" with two folders in it:

- myProject | |---+ source | '---+ build 

The build folder contains only the makefile. The following figure shows what should happen when I run the GNU authoring tool:

enter image description here

So, GNU make must create an object file for each source .c file that it can find in the source folder. Object files must be structured in a directory tree, which is similar to the structure in the source folder.

GNU make must also make a .d dependency file (in fact, the dependency file is some kind of makefile itself) for each .c source file. The dependency file is described in the GNU 4.14 manual, "Creating Prerequisites Automatically":

For each source file name.c there is a makefile name.d file that lists which files the name.o object file has .

From the next Stackoverflow question About GNU make * .d dependency files , I learned that adding the -MMD and -MP options to the CFLAGS GNU gcc compiler can help automate this.

So now the question is. Does anyone have a makefile sample that does this build outside the source? Or some useful tips on how to get started?

I am sure that most of the people who wrote such a makefile are Linux people. But the microcontroller project should also be built on a Windows machine. In any case, even if your makefile is Linux only, it provides a good starting point; -)

PS: I would like to avoid additional tools like CMake, Autotools or anything related to the IDE. Just pure GNU make.

I would be very grateful: -)


Dependency File Update
Please take a look at this question: What is the whole chain of events when GNU does updates of .d files?

+5
source share
4 answers

Here's the Makefile I added to the documentation (currently in the review, so I'll post it here):

 # Set project directory one level above the Makefile directory. $(CURDIR) is a GNU make variable containing the path to the current working directory PROJDIR := $(realpath $(CURDIR)/..) SOURCEDIR := $(PROJDIR)/Sources BUILDDIR := $(PROJDIR)/Build # Name of the final executable TARGET = myApp.exe # Decide whether the commands will be shown or not VERBOSE = TRUE # Create the list of directories DIRS = Folder0 Folder1 Folder2 SOURCEDIRS = $(foreach dir, $(DIRS), $(addprefix $(SOURCEDIR)/, $(dir))) TARGETDIRS = $(foreach dir, $(DIRS), $(addprefix $(BUILDDIR)/, $(dir))) # Generate the GCC includes parameters by adding -I before each source folder INCLUDES = $(foreach dir, $(SOURCEDIRS), $(addprefix -I, $(dir))) # Add this list to VPATH, the place make will look for the source files VPATH = $(SOURCEDIRS) # Create a list of *.c sources in DIRS SOURCES = $(foreach dir,$(SOURCEDIRS),$(wildcard $(dir)/*.c)) # Define objects for all sources OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o)) # Define dependencies files for all objects DEPS = $(OBJS:.o=.d) # Name the compiler CC = gcc # OS specific part ifeq ($(OS),Windows_NT) RM = del /F /Q RMDIR = -RMDIR /S /Q MKDIR = -mkdir ERRIGNORE = 2>NUL || true SEP=\\ else RM = rm -rf RMDIR = rm -rf MKDIR = mkdir -p ERRIGNORE = 2>/dev/null SEP=/ endif # Remove space after separator PSEP = $(strip $(SEP)) # Hide or not the calls depending of VERBOSE ifeq ($(VERBOSE),TRUE) HIDE = else HIDE = @ endif # Define the function that will generate each rule define generateRules $(1)/%.o: %.c @echo Building $$@ $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP), $$@ ) $$(subst /,$$(PSEP),$$<) -MMD endef # Indicate to make which targets are not files .PHONY: all clean directories all: directories $(TARGET) $(TARGET): $(OBJS) $(HIDE)echo Linking $@ $(HIDE)$(CC) $(OBJS) -o $(TARGET) # Include dependencies -include $(DEPS) # Generate rules $(foreach targetdir, $(TARGETDIRS), $(eval $(call generateRules, $(targetdir)))) directories: $(HIDE)$(MKDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE) # Remove all objects, dependencies and executable files generated during the build clean: $(HIDE)$(RMDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE) $(HIDE)$(RM) $(TARGET) $(ERRIGNORE) @echo Cleaning done ! 

Main functions

  • Automatically detect C sources in specified folders
  • Multiple Source Folders
  • Multiple Target Folders for Object and Dependency Files
  • Automatically create rules for each target folder
  • Create destination folders when they do not exist
  • Managing Dependencies with gcc : Create Only What You Need
  • Works on Unix and DOS systems
  • Written for GNU Make

How to use this makefile

To adapt this Makefile to your project, you must:

  • Change the TARGET variable to your target name
  • Change the name of the Sources and Build folders to SOURCEDIR and BUILDDIR
  • Change the detail level of the Makefile in the Makefile itself or in the make call ( make all VERBOSE=FALSE )
  • Change the folder name in DIRS according to your sources and create folders
  • Change the compiler and flags as needed

In this Makefile, Folder0 , Folder1 and Folder2 equivalent to your FolderA , FolderB and FolderC .

Please note that I did not have the opportunity to test it on a Unix system at the moment, but it works correctly on Windows.


Explanation of several complex parts:

Ignoring Windows mkdir Errors

 ERRIGNORE = 2>NUL || true 

This has two effects: First, 2>NUL - redirect the error output to NUL, since it is not included in the console.

Second, || true || true prevents the error level from rising. This is non-Makefile-related Windows material here because the Windows mkdir command raises errors if we try to create an existing folder, while we don't care if it really exists. A common solution is to use an if not exist structure, but not UNIX compatible, so even if it's complicated, I find my solution more understandable.


Creating an OBJS containing all the object files with their correct path

 OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o)) 

Here we want OBJS to contain all the object files with their paths, and we already have SOURCES that contain all the source files with their paths. $(SOURCES:.c=.o) changes * .c to * .o for all sources, but the path is still one of the sources. $(subst $(SOURCEDIR),$(BUILDDIR), ...) will simply subtract the entire source path using the build path, so we will finally get a variable containing .o files with their paths.


Work with path separators in the style of Windows and Unix

 SEP=\\ SEP = / PSEP = $(strip $(SEP)) 

It only exists to allow the Makefile to work on Unix and Windows, as Windows uses the backslash in the path, while everyone else uses slashes.

SEP=\\ Here, a double backslash is used to remove the backslash character, which make usually refers to the ignore newline character to allow writing on multiple lines.

PSEP = $(strip $(SEP)) This will remove the char space of the SEP variable, which was added automatically.


Automatically generate rules for each target folder

 define generateRules $(1)/%.o: %.c @echo Building $$@ $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP), $$@ ) $$(subst /,$$(PSEP),$$<) -MMD endef 

This may be the trick that is most related to your usecase. This is a rule template that can be generated using $(eval $(call generateRules, param)) , where param is what you can find in the template as $(1) . This will basically populate the Makefile with these rules for each target folder:

 path/to/target/%.o: %.c @echo Building $@ $(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP), $@ ) $(subst /,$(PSEP),$<) -MMD 
+11
source

This pretty minimal makefile should do the trick:

 VPATH = ../source OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o CPPFLAGS = -MMD -MP all: init myProgram myProgram: $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) .PHONY: all init init: mkdir -p FolderA mkdir -p FolderB -include $(OBJS:%.o=%.d) 

The main tricky part is ensuring that FolderA and FolderB exist in the build directory before attempting to run the compiler that will write to them. The above code will work sequentially for assembly, but the first run may fail with -j2 because the compiler in one thread may try to open the output file before another thread creates a directory. Its also somewhat unclean. Typically, using GNU tools, you have a script configuration that will create these directories (and the makefile) for you before you even try to run make. autoconf and automake can build this for you.

An alternative way to work with parallel assemblies is to override the standard rule for compiling C files:

 VPATH = ../source OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o CPPFLAGS = -MMD -MP myProgram: $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) %.o: %.c mkdir -p $(dir $@ ) $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< -include $(OBJS:%.o=%.d) 

Whoever has the drawback is that you also need to override the built-in rules for any other source file that you want to compile

+2
source

Here's the basic one I use all the time, it's pretty much a skeleton, but it works great for simple projects. For more complex projects, this certainly needs to be adapted, but I always use this as a starting point.

 APP=app SRC_DIR=src INC_DIR=inc OBJ_DIR=obj BIN_DIR=bin CC=gcc LD=gcc CFLAGS=-O2 -c -Wall -pedantic -ansi LFLGAS= DFLAGS=-g3 -O0 -DDEBUG INCFLAGS=-I$(INC_DIR) SOURCES=$(wildcard $(SRC_DIR)/*.c) HEADERS=$(wildcard $(INC_DIR)/*.h) OBJECTS=$(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) DEPENDS=$(OBJ_DIR)/.depends .PHONY: all all: $(BIN_DIR)/$(APP) .PHONY: debug debug: CFLAGS+=$(DFLAGS) debug: all $(BIN_DIR)/$(APP): $(OBJECTS) | $(BIN_DIR) $(LD) $(LFLGAS) -o $@ $^ $(OBJ_DIR)/%.o: | $(OBJ_DIR) $(CC) $(CFLAGS) $(INCFLAGS) -o $@ $< $(DEPENDS): $(SOURCES) | $(OBJ_DIR) $(CC) $(INCFLAGS) -MM $(SOURCES) | sed -e 's!^!$(OBJ_DIR)/!' > $@ ifneq ($(MAKECMDGOALS),clean) -include $(DEPENDS) endif $(BIN_DIR): mkdir -p $@ $(OBJ_DIR): mkdir -p $@ .PHONY: clean clean: rm -rf $(BIN_DIR) $(OBJ_DIR) 
+1
source

I would not manipulate the Makefile directly and use CMake instead. Just describe the source files in CMakeLists.txt, as shown below:

Create a file MyProject / source / CMakeLists.txt containing;

 project(myProject) add_executable(myExec FolderA/fileA1.c FolderA/fileA2.c FolderB/fileB1.c) 

In the MyProject / build section, run

 cmake ../source/ 

Now you will get a Makefile. To create build / in the same directory

 make 

You can also switch to the building tool for building a lightning fast ninja by simply adding a switch, as shown below.

 cmake -GNinja .. ninja 
-3
source

All Articles