Wednesday, July 15, 2015

Yet Another Make Tutorial

YAMT, an abbreviation that won't stick.

This tutorial on make may be too little, too late, as other tools are replacing make, Python, CMake, etc.

But I will push on with this tutorial to create a fairly general purpose production make file.

The reader is expected to have a bit of working knowledge of using gcc from the command line, how to create an object file and how to link them together to form a program.

At the end of this tutorial is a list of links where some of the features of make I use were found, along with other references.

All the files shown in the tutorial are found at this BitBucket site. https://bitbucket.org/FernLeaf_07/makefiletutorial.

This is a seven part tutorial.

Topics covered. gcc -mmd, make patterns, automatic variables, make variables, VPATH, patsubst, -include

Introduction

If you landed here not knowing much about make, this very brief introduction to make will get us started. If you are versed in the world of make, ctrl-f to 'Advanced Make'. If you just want the punch line, ctrl-f to 'makefile10a'.

make is a program that builds things, usually programs. make saves time. If the input file to make is written properly, then just those files that need to be compiled since the last compilation will be compiled. The purpose of using make is to define how a program is built and to save time building a program after an update to the source.

NOTE: The make files in this tutorial are NOT POSIX compliant.

Target and Dependency

The fundamental concept of make is that files are targets and have dependencies.

If a file is a target and it doesn't exist or is older in terms of date time than its associated dependency files, then a command is executed to create a new version of the target file.

The syntax to define a target/dependency pair is

target : dependency ; command
<tab>command

The target is first, starting in column 1, followed by a colon, followed by one or more dependencies.

Each target/dependency/command is separated by at least one blank line.

The command to execute can follow the dependency list, separated by a semi-colon, or put on the next line. The common convention is to put the command on the next line.

If the command is put on the next line, then a tab character MUST BE THE ONLY CHARACTER before the command.

Again, if the command is on its own line, the tab character MUST BE THE ONLY CHARACTER before the command.

make does not output good error messages when the tab character is omitted or a space is used instead.

With whatever editor you use, display spaces and tabs so that you make sure you have the tab where it belongs.

NOTE: Make sure you use a TAB CHARACTER to start the command line of a target/dependency definition.

targets don't have to be files, but usually are.

dependencies don't have to be files, but usually are.

There can be more than one target, but this tutorial will not use that feature.

make does not execute the commands in a make file sequentially. The make file defines target/dependency pairs.  At least one target/dependency pair has to have associated with it a command, otherwise the make file will do nothing. When make sees that the target is out of date with respect to its dependencies,  the command or commands are executed. When these commands are executed has no relationship to its position in the file.

Note: make really doesn't like spaces in filenames. Many have asked 'the Internet' how to work around this problem. Any exercise for the reader or a future blog.

Make Command Line Syntax.

A bit of make command line syntax.

make

This command assumes there is a file name makefile in the current directory and all will be the default .PHONY word. makefile.mak can also be used. (all and .PHONY discussed soon.)

make all

Same as the make command above.

In this tutorial, each make file will get its own unique name.

make -f makefile1

The -f argument informs make to use makefile1 instead of makefile as the input file.

There is only one input file to make, at the command line.

Simple

gcc main.c

This command causes gcc to compile main.c and create a.out (or a.exe for the Cygwin/MinGW crowd).

No need for make here. Just one file. No way to save time. Just the one file to compile.

gcc main.c suba.c subb.c subc.c

This command will build another a.out from the list of files. Now the need for make and a make file begins.

If you change just suba.c, then all the other files are built too. The same amount of time is used no matter how big or small the changes.

A shell script (or batch) can hold the complete command to save typing, but not time. Just ./buildit.sh (buildit.bat).

But what if your project has dozens and dozens of files? Instead of taking mere moments to build, it will take an hour, even if all you added was one comment.

Let's break down the long gcc command.

gcc -c main.c
gcc -c suba.c
gcc -c subb.c
gcc -c subc.c
gcc main.o suba.o subb.o subc.o -o aprogram

Again, a shell script could be created to execute all 5 lines, but make can do better.

The five commands above builds our program, aprogram, one step at a time. This is the key to a make file. Break the build of your program into individual steps.

Here is the source files we will be using for this tutorial. The program is a scaffolding. It doesn't do anything. It is just code that has relationships to illustrate make and make's features.

// main.c
#include "suba.h"
#include "subb.h"
#include "subc.h"
int main(void)
{
    suba();
    subb();
    subc();

    return 1;
}

// suba.c
#include "suba.h"
void suba(void)
{
}

// subb.c
#include "subb.h"
void subb(void)
{
}

// subc.c
#include "subc.h"
void subc(void)
{
}

// suba.h
void suba(void);

// subb.h
void subb(void);

// subc.h
void subc(void);

Here is the make file version of the above 5 commands.

# makefile1
.PHONY: all clean

all: aprogram

clean:
@rm -f *.o
@rm -f aprogram*

aprogram: main.o suba.o subb.o subc.o
gcc $^ -o $@

main.o: main.c
gcc -c $<

suba.o: suba.c
gcc -c $<

subb.o: subb.c
gcc -c $<

subc.o: subc.c
gcc -c $<

Let's examine each line of the make file and get familiar with terminology and structure.

.PHONY - This keyword tells make where to start. Each of the words on the .PHONY line are arguments to the make program to instruct make where to start.

.PHONY keyword defines the top of a dependency tree.

all is a reserved .PHONY word. If make is executed with no arguments, all is the default .PHONY word.

clean is a word, used by convention, to remove all files built by using the all keyword.

Other keywords can be defined. No additional keywords will be defined in this tutorial.

all: aprogram

This line defines our first target/dependency pair. all is the target. aprogram is the dependency. There is no command associated with this pair. The statement defines the dependency pair and the start of the dependency tree.

We will skip clean for now.

aprogram: main.o suba.o subb.o subc.o
<tab>gcc $^ -o $@

This is the first dependency pair with a target, a dependency list, and a command. (note the <tab> reminding you to use a tab character before the command.

The $^ and $@ are make automatic variables. make has several automatic variables. They define strings based on the targets and dependencies.

$^ is the list of dependencies, all of them.

$@ is the target.

Expanding the command we would get the following.

gcc main.o suba.o subb.o subc.o -o aprogram

The automatic variables save typing, and later, we will see they are mandatory when using patterns to define dependency pairs.

We have defined all as a target. We have defined aprogram as its dependency. We have defined aprogram as a target, the next level in the dependency tree. We have defined a list of object files as dependencies of aprogram.

Now we define each object file as a target.

main.o : main.c
<tab>gcc -c $<

suba.o : suba.c
<tab>gcc -c $<

subb.o : subb.c
<tab>gcc -c $<

subc.o : subc.c
<tab>gcc -c $<

Each object file has a dependency on a c file. The command to create the target object file, compile the c file is the same for each object and c file pair.

$< is the automatic variable that represents the first dependency. Initially it looks like we could also use $^, as there is only one dependency, but later we will see it is better to use $<.

The c files do not need a dependency. They do not need to be created. They already exist.

Thus the dependency tree is complete.

[make does allow for  dependency pattern, *.o : *.c. Thus all for lines of x.o : x.c are 'implied'. A large number of patterns are pre-defined. This topic discussed in later tutorials.]

clean

clean:

This is a target/dependency pair with no dependency. Thus the commands associated with it are always executed.

@rm -f *.o
@rm -f aprogram*

The command rm (remove files) remove all the object files. The -f switch says to not report an error if the file does not exist. The @ in front of rm suppresses any output from the program.

The second line removes aprogram or aprogram.exe, doesn't matter.

clean removes all files created by all.

Execute makefile1

make -f makefile1

Running the above command we get the following output.










We see that all four files are compiled and aprogram is built.

touch suba.c

make -f makefile1







Now we see the power of make. The touch command updated the date time of suba.c. suba.c is now newer than its object file. make scans the target/dependencies and determines that the command associated with suba.o needs to be executed. Since suba.o is a dependency of aprogram, the command for aprogram is executed next.

Only two commands are executed instead of five.

make -f makefile1 clean

This command specifies clean as the top level dependency .PHONY word.



Include Files

What about the include files?

An observant reader will notice that the include files are not listed in makefile1.

Let's update that now.

# makefile2
.PHONY: all clean

all: aprogram

clean:
@rm -f *.o
@rm -f aprogram*

aprogram: main.o suba.o subb.o subc.o
gcc $^ -o $@

main.o: main.c suba.h subb.h subc.h
gcc -c $<

suba.o: suba.c suba.h
gcc -c $<

subb.o: subb.c subb.h
gcc -c $<

subc.o: subc.c subc.h
gcc -c $<

[Note: Red is used to show how the make files are updated from step to step in this tutorial.]

Each object file now has the corresponding include file as a dependency file.

We now have more than one dependency for each object file, but we still want the command to compile just the c file. The automatic variable $< still uses just the c file, not the additional include files added (as long as the c file remains the first dependency).

touch suba.h

make -f makefile2








If suba.h is changed, then both suba.o and main.o are rebuilt, thus aprogram is rebuilt.

Note that repeating the command, make reports nothing to be done, a test the make file is correct.

Simple Summary

For really simple programs, I mean really simple programs, makefile2 provides a guide to creating useful make files.

But production quality programs are not this simple.

The problem with makefile2 is the file is brittle. If a new include file is created, or a new c file is created or deleted, the dependency tree has to be updated, by hand. This is tedious and error prone.

The rest of the tutorial is how to create a make file that automates the building of the list of files to be compiled and linked.

Advanced Make

Variables

Make is an interpretive programming language. Not a 'typical' programming language, but a programming language. (probably not Turing complete :) ).

Just like all programming languages, make has variables. We saw a few automatic variables above.

Let's change makefile2 to use a variable or two.

# makefile3
OBJS = main.o suba.o subb.o subc.o
TARGET = aprogram
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f *.o
@rm -f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

main.o: main.c suba.h subb.h subc.h
gcc -c $<

suba.o: suba.c suba.h
gcc -c $<

subb.o: subb.c subb.h
gcc -c $<

subc.o: subc.c subc.h
gcc -c $<

The string of object files is now a variable, OBJS. The name of our program is now a variable, TARGET.

To use a variable name, the name is preceded by $( and followed by ). The automatic variables were preceded  with a $. So any variable in make is referenced by using a $. An automatic variable is one of several letters. A user defined variable is surrounded with parentheses. Brackets { } can be used in place of parentheses, but parentheses are much more commonly used.

Variables will play a major role in the final production quality make file.

makefile3 is no less brittle than makefile2.


Patterns

make allows for patterns to be used in defining a target/dependency pair.

%.o : %.c

The %.o pattern states any file that ends in a two characters .o is a target. The corresponding dependency file uses the same % string for the root name but changes the .o to .c.

Let's use the above pattern to change makefile3.

# makefile4
OBJS = main.o suba.o subb.o subc.o
TARGET = aprogram
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f *.o
@rm -f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

%.o: %.c
gcc -c $<

makefile4 is a lot shorter than makefile3. The automatic variable $< is required when using a pattern as the actual name of the dependency is not known until make runs.

A bit of automation has been added. If we add a new c file or delete a c file, we only have to add or remove from the corresponding object file in OBJS.

Let's review how the dependency tree is built. all is dependent on $(TARGET). $(TARGET) is dependent on the list $(OBJS). The pattern dependency then generates the c file dependencies for each object file in the $(OBJS) list. The object file targets used by the pattern come from the make file. Remember the object files do not exist the first time.

But we have lost the dependency relationship with the include files.







Touching  suba.h does not result in rebuilding suba.o and aprogram.

DEPS

Let's do a quick and dirty remedy.

#makefile5
OBJS := main.o suba.o subb.o subc.o
DEPS := suba.h subb.h subc.h
TARGET := aprogram
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f *.o
@rm -f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

%.o: %.c $(DEPS)
gcc -c $<


We created the variable DEPS to hold all the include files. We made this list a dependent on any object file. DEPS is a variable name, used by convention, for include files.

This update works. However, if just an include file is changed, then all the objects and aprogram are rebuilt. aprogram is built properly, but make builds more object files than is necessary.












We are back to an inefficient make file, but are now using variables to localize the files.

We will come back and update the make file so that the minimum number of object files are built for just an include file change.


Recursively Defined and Simply Defined Variables

Another subtle, but important change was made. Equal signs (=) were changed to colon equal signs (:=).

make has two types of variables, recursively defined variables and simply defined variables. If the string being assigned to a variable can be determined without recursion, statically, then use the simply defined variable notation, colon equal (:=). It is faster than recursively defined variables.

All variables in this tutorial are simply defined variables. It was difficult to find a good example of a recursively defined variable, and they are not needed here.

Directories

Another observant reader will notice that the object files and aprogram are being created in the same directory as the source files.

This is considered poor form. It is messy. Production make files have the object files in one directory, the source in another directory, and the program in a third directory. A convention is followed where the object files and the program are put in the same directory. This tutorial will have the program and object files in separate directories.

We move all the sources to a sub-directory named src. The object files will be placed in the sub-directory obj, and the program will be placed in the sub-directory target.

Several make tutorials talk about it being easiest to design a make file when the make file runs in the directory where the object files are to be built.

Yes, this can be easier when building the make file. The make file is just like any other source file. It will be archived in source control. If it exists in a directory where all the other files are intermediate, temporary files, one can accidentally delete all the files in the obj directory, requiring the make file to be retrieved from the source control archive.

The following make file is expected to be run in the parent directory of src, obj, and target.

Let's update the various filenames in the make file with the new directory names.

#makefile6 - doesn't work
OBJS := obj/main.o obj/suba.o obj/subb.o obj/subc.o
DEPS := src/suba.h src/subb.h src/subc.h
TARGET := target/aprogram
.PHONY: all clean

all: $(TARGET)

clean:
@rm –f obj/*.o
@rm –f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

obj/%.o: %.c $(DEPS)
gcc –c $< -o $@


The include files have the src directory pre-pended. The object files have the obj directory pre-pended. aprogram has target pre-pended.

However, as the comment states, makefile6 doesn't work.






There are two reasons makefile6 does not work. A simple change will correct one problem.

Change


obj/%.o: %.c $(DEPS)
  gcc –c $< -o $@

to


obj/%.o: src/%.c $(DEPS)
  gcc –c $< -o $@


This change illustrates the literal nature of the make pattern.

obj/%.o specifies any file in obj where the pattern (%) is the root portion of the name.

%.c then says take the found object root name and simply change .o to .c.

If we start with obj/main.o for a target. The dependency is then main.c.

gcc -c main.c -o obj/main.o

The source files do not exist in the parent directory, thus make cannot find the dependency.

With the update of src/%.c the command is now

gcc -c src/main.c -o obj/main.o







There is still an error. This time from gcc, not make. The error 'No such file or directory' is being reported because the obj directory does not exist, not that the file obj/main.o does not exist. A bit confusing, but par for make error messages.

gcc expects the obj directory (and the target directory) to exist before it will write main.o into the obj directory. gcc does not create directories if they are missing.


VPATH and vpath

We take a step back and solve the issue not finding the sources a different way than adding src/ to the pattern.

Enter make's vpath feature.

make contains a variable and a function to define a virtual path to look for dependencies.

VPATH is a reserved variable. It may contain a list of one or more directories to look for dependencies.

vpath, all lowercase, is a function that has two arguments, a pattern and a list of directories.

There is only one VPATH variable. There may be as many vpath pattern/directory pairs as you need.

Update makefile6 to use vpath.

#makefile7 - does not work
OBJS := obj/main.o obj/suba.o obj/subb.o obj/subc.o
DEPS := src/suba.h src/subb.h src/subc.h
TARGET := target/aprogram
vpath %.c src
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f obj/*.o
@rm -f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

obj/%.o: %.c $(DEPS)
gcc -c $< -o $@

Now the command

gcc -c main.c -o obj/main.o

is still generated, but vpath instructs make to look in the src directory if a dependent file with a .c extension is not found by default.

makefile7 still does not work, We'll fix the missing directories next.





Creating Missing Directories

The make file must define the directories that need to be created and create them prior to compiling the c files.

There are two directories to create, obj and target. We need to have a command to create these directories, if they don't exist.

mkdir is the command to create a directory. mkdir is an external command. It is not built into make, just like gcc.

mkdir -p <directory> will create <directory> if it does not exist and not report an error if it does.

There are two approaches to updating the make file to create the missing directories. Approach 1 is to add the missing directories to the dependency tree. Approach 2 is to use variables to create the directories.

Missing Directory - Approach 1

all: required_dirs $(TARGET)

required_dirs : $(OBJ_DIR) $(TARGET_DIR)

$(OBJ_DIR) :
<tab>@mkdir –p $@

$(TARGET_DIR) :
<tab>@mkdir –p $@

The new target, required_dirs is added to the list of dependencies for all. Each directory in turn has a target/command pair. Without a dependency the command, mkdir, is always executed. If the directory does not exist, it is created.

Missing Directory - Approach 2


REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
do \
mkdir -p $$d; \
done)


The variable REQUIRED_DIRS is now a list of the required directories.
_MKDIRS is a variable that is assigned the result of the shell command.

make allows scripts to be run using the function shell.
Note that shell is surrounded by $( ). Essentially the return code of shell is a variable.

The above shell script is a do loop that iterates over the list of directories, creates a shell variable d and creates each directory if it does not exist. The $$ is so that d is used as a shell variable, not a make file variable.

Note: Tabs are not required here. This is a definition of variables, not a target/pair dependency.

Let's incorporate Approach 2 into makefile7.

#makefile8
OBJ_DIR := obj
OBJS := $(OBJ_DIR)/main.o $(OBJ_DIR)/suba.o $(OBJ_DIR)/subb.o $(OBJ_DIR)/subc.o
DEPS := src/suba.h src/subb.h src/subc.h
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c src
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o
@rm -f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c $(DEPS)
gcc -c $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do \ mkdir -p $$d; \  done)

Added the variables OBJ_DIR and TARGET_DIR. Updated OJBS and TARGET to use these new variables. Finally added the variables to run the shell script to create the missing directories, if needed.

Success. The object files are created in the obj directory. aprogram is created in the target directory and gcc does not complain about directories missing.



Wildcard

Now let's start to get some automation into the make file.

There is a make function, wildcard, which will create a list of files from a pattern.

SRC_DIR := src
$(wildcard $(SRC_DIR)/*.c)

This function looks for all c files in the SRC_DIR directory and creates a space separated list of all the files found.

Let's assign this list to the variable SRC_LIST.

SRC_LIST := $(wildcard $(SRC_DIR)/*.c)

SRC_LIST can be used to create the list of object files.

Using the make function patsubt, short for pattern substitution, we can create OBJS from SRC_LIST.

OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))

Automation. Whatever files are in SRC_DIR, the corresponding list of object files is created.

#makefile9
SRC_DIR := src
OBJ_DIR := obj
SRC_LIST := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(SRC_DIR)/suba.h $(SRC_DIR)/subb.h $(SRC_DIR)/subc.h
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o
@rm -f $(TARGET)*

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c $(DEPS)
gcc -c $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
do \
mkdir -p $$d; \
done)

Good make file. Simply add a new c file or delete the file from SRC_DIR and the appropriate object file will be created and linked.

However, the include files are still not automated.

Let's finally solve this problem.

Automated Include Files

There are three features that are required to automate the include files. One feature is from gcc and two features are from make.

gcc -MMD switch

gcc has a command line switch -MMD. It is specifically designed to assist in the creation of automated include file dependencies.

Note on User Header and System Header Files.

gcc -MMD scans c file for user header files, not system file headers.

If you use  #include "stdio.h", you need to use #include <stdio.h>. If quotes are used, make will evenutyally look for stdio.h, which will not be in SRC and the make file will fail with unable to find dependency.

 --  end note --

Using -MMD, gcc creates, in addition to the object file, an include dependency file, *.d file, in the same directory as the object file.

The include dependency file is a make file with no commands. The target is the object file created. The dependencies are the include files used by the c file compiled. The include file dependencies is the complete include calling tree. If include files are nested in include files, the nested files are in the dependency list.






Next we need a list of these include dependency files.

Short cut string replacement

DEPS := $(OBJS:.o=.d)

The above assignment changes every .o in OBJS to .d. This is simpler, but not as flexible, as patsubst.

include function

Now that we have created the include dependency files and we have a list of these files, we need to put these files into the dependency tree.

The make function include will perform this step.

-include $(DEPS)

include causes the files listed to be included within the make file. As each include dependency file has a target and dependency pair, each pair now part of the dependency tree.

The target is the object file, which is already dependent on the c file via the pattern. The object files are now also dependent on the include files associated with the c file, as generated by gcc.

This shows that there may be more than one target/dependency pair that refers to the same target.

The minus sign in front of include is important. If the include dependency file does not exist, include will not report an error. This will be true the first time make is run or after a clean.

Notice that the order of dependencies is definitely not the order the dependencies are found in the file.

#makefile10
SRC_DIR := src
OBJ_DIR := obj
SRC_LIST := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
do \
mkdir -p $$d; \
done)

-include $(DEPS)

Added -MMD switch to gcc. Updated the DEPS variable and included all the include dependency files.













The include file dependency problem is solved.

There is one shortcoming with makefile10OBJ_DIR and TARGET_DIR are not deleted when clean is run. We said clean removes all files created by all. The directories are created by all, but not removed by clean.

#makefile10a
SRC_DIR := src
OBJ_DIR := obj
SRC_LIST := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
 @rmdir $(OBJ_DIR)
 @rmdir $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
do \
mkdir -p $$d; \
done)

-include $(DEPS)

Fin. makefile10a is completely automated. Give a list of source files in the SRC_DIR directory that will link, as object files, to create aprogram, no changes to makefile10a are required as new c files or include files are added or deleted.














Include File Dependency Details

You may be wondering if the update for include files works for all circumstances. Let's discuss it in a bit more detail.

When the make file is run for the first time, there are no *.d files, so the only dependent file for an object file is the c file. Since the object file does not exist, the c file is compiled.

The second running of the make file, the *.d files exist and the include files are added to the dependencies of the object file. If an include file is changed, then the appropriate object file is created.

If an include is added or deleted, then some file, a c file or another include file must have been changed. The appropriate object file is created.

Finally, what if a c file is deleted, the corresponding object file is not deleted. No it is not, but the old object file is no longer a member of the list of object files, OBJS, used to link aprogram. An extra file exists in the OBJ_DIR directory, but does not get used.

The next time clean is run the extra object files will be removed.

Again, makefile10a is very good general purpose make file.

Change the target and you are ready to use makefile10a for another program.

There are is, however, a limitation with makefile10a. The sources must all be in SRC_DIR. There can be no sub-directories to SRC_DIR containing source. The problem is wildcard does not recursively descend directories looking for files.

The next posting of this blog shows how to extend makefile10a to use sub-directories of SRC_DIR.

Next post: Yet Another Make Tutorial II

References and Links

This is a listing of various URLs that discusses section of this document and where the ideas came from.




Note about putting –include at the end of the file comes from here. http://www.microhowto.info/howto/automatically_generate_makefile_dependencies.html


No comments:

Post a Comment